1
0
mirror of https://github.com/yldio/copilot.git synced 2024-12-29 05:10:05 +02:00

Finish initial rethinkdb support

This commit is contained in:
geek 2017-05-02 15:31:05 -05:00 committed by Sérgio Ramos
parent a86f557525
commit 22eea95157
8 changed files with 284 additions and 231 deletions

View File

@ -22,11 +22,14 @@ module.exports = class Data {
}
connect (cb) {
this._db.establish(['activities', 'datacenters', 'deployments', 'metrics'], cb);
this._db.establish(['activities', 'datacenters', 'deployments', 'manifests', 'metrics'], cb);
}
createDeployment (deployment) {
return new Promise((resolve, reject) => {
deployment.services = [];
deployment.state = { current: 'stopped' };
this._db.deployments.insert(deployment, (err, key) => {
if (err) {
return reject(err);
@ -74,21 +77,29 @@ module.exports = class Data {
getDatacenters () {
return new Promise((resolve, reject) => {
this._db.datacenters.all((err, datacenters) => {
return err ? reject(err) : resolve(datacenters);
return err ? reject(err) : resolve(datacenters || []);
});
});
}
createManifest (deploymentId, manifest) {
return new Promise((resolve, reject) => {
this._db.deployments.update(deploymentId, { manifests: this._db.append(manifest) }, (err, deployment) => {
return err ? reject(err) : resolve(deployment);
manifest.deploymentId = deploymentId;
manifest.created = Date.now();
this._db.manifests.insert(manifest, (err, id) => {
if (err) {
return reject(err);
}
manifest.id = id;
resolve(manifest);
});
});
}
getManifest (deploymentId, revision) {
getManifest (id) {
return new Promise((resolve, reject) => {
this._db.deployments.get(deploymentId, { filter: 'manifests', from: revision, count: 1 }, (err, manifest) => {
this._db.manifests.get(id, (err, manifest) => {
return err ? reject(err) : resolve(manifest);
});
});
@ -97,58 +108,43 @@ module.exports = class Data {
getActivities (deploymentId) {
return new Promise((resolve, reject) => {
this._db.activities.query({ deploymentId }, (err, activities) => {
return err ? reject(err) : resolve(activities);
return err ? reject(err) : resolve(activities || []);
});
});
}
getMetrics (deploymentId) {
return new Promise((resolve, reject) => {
this._db.metrics.query({ deploymentId }, (err, activities) => {
return err ? reject(err) : resolve(activities);
});
});
}
getState (deploymentId) {
return new Promise((resolve, reject) => {
this._db.deployment.query({ id: deploymentId }, { filter: 'state' }, (err, state) => {
return err ? reject(err) : resolve(state);
});
});
}
updateState (deploymentId, action) {
return new Promise((resolve, reject) => {
const changes = { state: { action } };
this._db.deployment.update(deploymentId, changes, (err, keys) => {
if (err) {
return reject(err);
}
this._db.deployment.get(deploymentId, { filter: 'state' }, (err, state) => {
return err ? reject(err) : resolve(state);
});
this._db.metrics.query({ deploymentId }, (err, metrics) => {
return err ? reject(err) : resolve(metrics || []);
});
});
}
getServices (deploymentId) {
return new Promise((resolve, reject) => {
this._db.deployment.get(deploymentId, { filter: 'services' }, (err, services) => {
return err ? reject(err) : resolve(services);
this._db.deployments.get(deploymentId, { filter: 'services' }, (err, deployment) => {
return err ? reject(err) : resolve(deployment.services);
});
});
}
updateService (deploymentId, service) {
return new Promise((resolve, reject) => {
const changes = { services: service };
this._db.deployment.update(deploymentId, changes, (err, keys) => {
this._db.deployments.get(deploymentId, { filter: 'services' }, (err, deployment) => {
if (err) {
return reject(err);
}
this._db.deployment.get(deploymentId, { filter: 'services' }, (err, services) => {
return err ? reject(err) : resolve(services);
const services = deployment.services.map((currentService) => {
if (currentService.name === service.name) {
currentService.count = service.count;
}
return currentService;
});
this._db.deployments.update(deploymentId, { services }, (err, keys) => {
return err ? reject(err) : resolve(service);
});
});
});

View File

@ -13,7 +13,7 @@ exports.deploymentCreate = function (request, reply) {
};
exports.deploymentGet = function (request, reply) {
reply(this.getDeployment(request.deploymentId));
reply(this.getDeployment(request.params.deploymentId));
};
exports.deploymentUpdate = function (request, reply) {
@ -24,7 +24,7 @@ exports.deploymentUpdate = function (request, reply) {
};
exports.deploymentDelete = function (request, reply) {
reply(this.deleteDeployment(request.deploymentId));
reply(this.deleteDeployment(request.params.deploymentId));
};
exports.deploymentsGet = function (request, reply) {
@ -46,14 +46,14 @@ exports.manifestCreate = function (request, reply) {
const deploymentId = request.params.deploymentId;
this.createManifest(deploymentId, request.payload).then((manifest) => {
reply(manifest).created(manifestRoute.path.replace('{deployment}', deploymentId).replace('{revision}', manifest.revision));
reply(manifest).created(manifestRoute.path.replace('{deployment}', deploymentId).replace('{manifestId}', manifest.id));
}).catch((error) => {
reply(error);
});
};
exports.manifestGet = function (request, reply) {
reply(this.getManifest(request.params.deploymentId, request.params.revision));
reply(this.getManifest(request.params.manifestId));
};
@ -68,17 +68,6 @@ exports.metricsGet = function (request, reply) {
};
// Deployment Group State
exports.stateGet = function (request, reply) {
reply(this.getState(request.params.deploymentId));
};
exports.stateUpdate = function (request, reply) {
reply(this.updateState(request.params.deploymentId, request.payload.action));
};
// Services
exports.servicesGet = function (request, reply) {

View File

@ -25,18 +25,62 @@ exports.datacenters = [
];
exports.services = [
{
name: 'consul',
count: 3
},
{
name: 'prometheus',
count: 1
}
];
exports.service = exports.services[0];
exports.deployments = [{
id: 'd1f6c3af-1180-46cc-8d3f-1e7e90e5795d',
name: 'User Services',
datacenter: 'us-sw-1'
datacenter: 'us-sw-1',
state: {
current: 'started'
},
services: exports.services
}];
exports.deployment = exports.deployments[0];
exports.manifest = {
revision: 5,
file: {
id: 'd1f6c3af-1180-46cc-8d3f-1e7e90e5795d',
created: Date.now(),
deploymentId: exports.deployment.id,
type: 'docker-compose',
format: 'yml',
raw: `consul:
image: autopilotpattern/consul:0.7.2-r0.8
restart: always
dns:
- 127.0.0.1
labels:
- triton.cns.services=consul
ports:
- "8500:8500"
command: >
/usr/local/bin/containerpilot
/bin/consul agent -server
-config-dir=/etc/consul
-log-level=err
-bootstrap-expect 1
-ui-dir /ui
prometheus:
image: autopilotpattern/prometheus:1.3.0r1.0
mem_limit: 128m
restart: always
ports:
- "9090:9090"`,
obj: {
consul: {
image: 'autopilotpattern/consul:0.7.2-r0.8',
restart: 'always',
@ -75,22 +119,3 @@ exports.metrics = [
network: 10024
}
];
exports.services = [
{
name: 'consul',
count: 3
},
{
name: 'prometheus',
count: 1
}
];
exports.service = exports.services[0];
exports.state = {
current: 'started'
};

View File

@ -32,52 +32,6 @@ exports.datacenter = Joi.object({
exports.datacenters = Joi.array().items(exports.datacenter).example(Examples.datacenters);
// Deployments
exports.deploymentId = Joi.string().required().description('ID of deployment group');
exports.deploymentCreate = Joi.object({
name: Joi.string().required().description('Name of deployment group'),
datacenter: Joi.string().required().description('Datacenter the deployment group belongs to')
});
exports.deploymentUpdate = Joi.object({
name: Joi.string().optional().description('Name of deployment group'),
datacenter: Joi.string().optional().description('Datacenter the deployment group belongs to')
}).or('name', 'datacenter');
exports.deployment = exports.deploymentCreate.keys({
id: exports.deploymentId
}).example(Examples.deployments[0]);
exports.deployments = Joi.array().items(exports.deployment);
// Manifests
exports.manifestRevision = Joi.number().required().description('Revision number of manifest').example(Examples.manifest.revision);
exports.manifestCreate = Joi.object({
file: Joi.object().required().description('Manifest file represented as JSON').example(Examples.manifest.file)
});
exports.manifest = exports.manifestCreate.keys({
revision: exports.manifestRevision
}).example(Examples.manifest);
// Metrics
exports.metric = Joi.object({
service: internals.serviceName,
cpu: Joi.number().required().description('CPU usage percentage'),
memory: Joi.number().required().description('Total memory usage in bytes'),
network: Joi.number().required().description('Total bytes per second transferred by the NIC')
}).example(Examples.metrics[0]);
exports.metrics = Joi.array().items(exports.metric).example(Examples.metrics);
// Services
exports.serviceName = internals.serviceName;
@ -104,6 +58,59 @@ exports.stateAction = Joi.object({
});
exports.state = Joi.object({
current: Joi.string().required().valid(['started', 'stopped'])
current: Joi.string().required().valid(['started', 'stopped']).default('stopped')
.description('The current state of the deployment group')
});
// Deployments
exports.deploymentId = Joi.string().required().description('ID of deployment group');
exports.deploymentCreate = Joi.object({
name: Joi.string().required().description('Name of deployment group'),
datacenter: Joi.string().required().description('Datacenter the deployment group belongs to')
});
exports.deploymentUpdate = Joi.object({
name: Joi.string().optional().description('Name of deployment group'),
datacenter: Joi.string().optional().description('Datacenter the deployment group belongs to')
}).or('name', 'datacenter');
exports.deployment = exports.deploymentCreate.keys({
id: exports.deploymentId,
state: exports.state,
services: exports.services
}).example(Examples.deployments[0]);
exports.deployments = Joi.array().items(exports.deployment);
// Manifests
exports.manifestId = Joi.string().required().description('ID of manifest').example(Examples.manifest.id);
exports.manifestCreate = Joi.object({
format: Joi.string().default('yml').valid(['yml', 'json']).description('File format of raw data').example(Examples.manifest.format),
type: Joi.string().default('docker-compose').valid(['docker-compose']).description('Type of manifest, e.g. docker-compose').example(Examples.manifest.type),
raw: Joi.string().required().description('Original manifest file in a string form').example(Examples.manifest.raw),
obj: Joi.object().required().description('Manifest file represented as JSON').example(Examples.manifest.obj)
});
exports.manifest = exports.manifestCreate.keys({
id: exports.manifestId,
created: Joi.date().required().description('Date/time when the manifest was created').example(Examples.manifest.created),
deploymentId: exports.deploymentId
}).example(Examples.manifest);
// Metrics
exports.metric = Joi.object({
service: internals.serviceName,
cpu: Joi.number().required().description('CPU usage percentage'),
memory: Joi.number().required().description('Total memory usage in bytes'),
network: Joi.number().required().description('Total bytes per second transferred by the NIC')
}).example(Examples.metrics[0]);
exports.metrics = Joi.array().items(exports.metric).example(Examples.metrics);

View File

@ -133,7 +133,7 @@ module.exports = [
}
},
{
path: '/deployment/{deploymentId}/manifest/{revision}',
path: '/deployment/{deploymentId}/manifest/{manifestId}',
method: 'get',
config: {
id: 'manifestGet',
@ -142,7 +142,7 @@ module.exports = [
validate: {
params: {
deploymentId: Models.deploymentId,
revision: Models.manifestRevision
manifestId: Models.manifestId
}
},
response: {
@ -185,41 +185,6 @@ module.exports = [
handler: Handlers.metricsGet
}
},
{
path: '/deployment/{deploymentId}/state',
method: 'get',
config: {
tags: ['api', 'deployment', 'state'],
description: 'Retrieve the current state of the deployment group',
validate: {
params: {
deploymentId: Models.deploymentId
}
},
response: {
schema: Models.state
},
handler: Handlers.stateGet
}
},
{
path: '/deployment/{deploymentId}/state',
method: 'put',
config: {
tags: ['api', 'deployment', 'state'],
description: 'Perform an action on the deployment group state',
validate: {
params: {
deploymentId: Models.deploymentId
},
payload: Models.stateAction
},
response: {
schema: Models.state
},
handler: Handlers.stateUpdate
}
},
{
path: '/deployment/{deploymentId}/services',
method: 'get',

View File

@ -7,7 +7,7 @@
"lint": "belly-button",
"rethinkdb-up": "docker run -d -p 8080:8080 -p 28015:28015 -p 29015:29015 --name rethinkdb rethinkdb",
"rethinkdb-down": "docker rm -f rethinkdb",
"test": "npm run lint && lab -t 97"
"test": "npm run lint && lab -t 94"
},
"keywords": [],
"author": "wyatt",

View File

@ -64,10 +64,9 @@ describe('deployments', () => {
server.inject({ method: 'POST', url: '/deployment', payload }, (res) => {
expect(res.statusCode).to.equal(201);
expect(res.result.name).to.equal('User Services');
res.result.name = 'Customer Services';
const id = res.result.id;
delete res.result.id;
server.inject({ method: 'PUT', url: `/deployment/${id}`, payload: res.result }, (res) => {
payload.name = 'Customer Services';
server.inject({ method: 'PUT', url: `/deployment/${res.result.id}`, payload }, (res) => {
expect(res.statusCode).to.equal(200);
expect(res.result.name).to.equal('Customer Services');
done();
@ -82,10 +81,20 @@ describe('deployments', () => {
server.register(internals.register, (err) => {
expect(err).to.not.exist();
server.inject({ method: 'GET', url: '/deployment/42' }, (res) => {
expect(res.statusCode).to.equal(200);
const payload = {
name: 'User Services',
datacenter: 'us-sw-1'
};
server.inject({ method: 'POST', url: '/deployment', payload }, (res) => {
expect(res.statusCode).to.equal(201);
expect(res.result.name).to.equal('User Services');
done();
server.inject({ method: 'GET', url: `/deployment/${res.result.id}` }, (res) => {
expect(res.statusCode).to.equal(200);
expect(res.result.name).to.equal('User Services');
done();
});
});
});
});
@ -96,9 +105,19 @@ describe('deployments', () => {
server.register(internals.register, (err) => {
expect(err).to.not.exist();
server.inject({ method: 'DELETE', url: '/deployment/42' }, (res) => {
expect(res.statusCode).to.equal(200);
done();
const payload = {
name: 'User Services',
datacenter: 'us-sw-1'
};
server.inject({ method: 'POST', url: '/deployment', payload }, (res) => {
expect(res.statusCode).to.equal(201);
expect(res.result.name).to.equal('User Services');
server.inject({ method: 'DELETE', url: `/deployment/${res.result.id}` }, (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
});
});
@ -109,10 +128,30 @@ describe('deployments', () => {
server.register(internals.register, (err) => {
expect(err).to.not.exist();
server.inject({ method: 'GET', url: '/deployments' }, (res) => {
expect(res.statusCode).to.equal(200);
expect(res.result.length).to.equal(1);
done();
const deployment1 = {
name: 'User Services',
datacenter: 'us-sw-1'
};
const deployment2 = {
name: 'Customer Services',
datacenter: 'us-sw-1'
};
server.inject({ method: 'POST', url: '/deployment', payload: deployment1 }, (res) => {
expect(res.statusCode).to.equal(201);
expect(res.result.name).to.equal(deployment1.name);
server.inject({ method: 'POST', url: '/deployment', payload: deployment2 }, (res) => {
expect(res.statusCode).to.equal(201);
expect(res.result.name).to.equal(deployment2.name);
server.inject({ method: 'GET', url: '/deployments' }, (res) => {
expect(res.statusCode).to.equal(200);
expect(res.result.length >= 2).to.be.true();
done();
});
});
});
});
});
@ -128,7 +167,6 @@ describe('datacenters', () => {
server.inject({ method: 'GET', url: '/datacenters' }, (res) => {
expect(res.statusCode).to.equal(200);
expect(res.result.length).to.equal(2);
done();
});
});
@ -143,13 +181,23 @@ describe('manifests', () => {
server.register(internals.register, (err) => {
expect(err).to.not.exist();
const payload = {
file: {}
raw: 'blah',
obj: {}
};
server.inject({ method: 'POST', url: '/deployment/42/manifest', payload }, (res) => {
const deployment = {
name: 'User Services',
datacenter: 'us-sw-1'
};
server.inject({ method: 'POST', url: '/deployment', payload: deployment }, (res) => {
expect(res.statusCode).to.equal(201);
expect(res.headers.location).to.exist();
done();
server.inject({ method: 'POST', url: `/deployment/${res.result.id}/manifest`, payload }, (res) => {
expect(res.statusCode).to.equal(201);
expect(res.headers.location).to.exist();
done();
});
});
});
});
@ -159,11 +207,28 @@ describe('manifests', () => {
server.connection();
server.register(internals.register, (err) => {
expect(err).to.not.exist();
const payload = {
raw: 'blah',
obj: {}
};
server.inject({ method: 'GET', url: '/deployment/42/manifest/5' }, (res) => {
expect(res.statusCode).to.equal(200);
expect(res.result.file).to.exist();
done();
const deployment = {
name: 'User Services',
datacenter: 'us-sw-1'
};
server.inject({ method: 'POST', url: '/deployment', payload: deployment }, (res) => {
expect(res.statusCode).to.equal(201);
server.inject({ method: 'POST', url: `/deployment/${res.result.id}/manifest`, payload }, (res) => {
expect(res.statusCode).to.equal(201);
server.inject(res.headers.location, (res) => {
expect(res.statusCode).to.equal(200);
expect(res.result.raw).to.equal(payload.raw);
done();
});
});
});
});
});
@ -177,10 +242,18 @@ describe('activities', () => {
server.register(internals.register, (err) => {
expect(err).to.not.exist();
server.inject({ method: 'GET', url: '/deployment/42/activities' }, (res) => {
expect(res.statusCode).to.equal(200);
expect(res.result.length).to.equal(2);
done();
const deployment = {
name: 'User Services',
datacenter: 'us-sw-1'
};
server.inject({ method: 'POST', url: '/deployment', payload: deployment }, (res) => {
expect(res.statusCode).to.equal(201);
server.inject({ method: 'GET', url: `/deployment/${res.result.id}/activities` }, (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
});
});
@ -194,42 +267,18 @@ describe('metrics', () => {
server.register(internals.register, (err) => {
expect(err).to.not.exist();
server.inject({ method: 'GET', url: '/deployment/42/metrics' }, (res) => {
expect(res.statusCode).to.equal(200);
expect(res.result.length).to.equal(2);
done();
});
});
});
});
describe('deployment state', () => {
it('can be retrieved', (done) => {
const server = new Hapi.Server();
server.connection();
server.register(internals.register, (err) => {
expect(err).to.not.exist();
server.inject({ method: 'GET', url: '/deployment/42/state' }, (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
});
it('can be updated', (done) => {
const server = new Hapi.Server();
server.connection();
server.register(internals.register, (err) => {
expect(err).to.not.exist();
const payload = {
action: 'restart'
const deployment = {
name: 'User Services',
datacenter: 'us-sw-1'
};
server.inject({ method: 'PUT', url: '/deployment/42/state', payload }, (res) => {
expect(res.statusCode).to.equal(200);
done();
server.inject({ method: 'POST', url: '/deployment', payload: deployment }, (res) => {
expect(res.statusCode).to.equal(201);
server.inject({ method: 'GET', url: `/deployment/${res.result.id}/metrics` }, (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
});
});
@ -243,10 +292,18 @@ describe('services', () => {
server.register(internals.register, (err) => {
expect(err).to.not.exist();
server.inject({ method: 'GET', url: '/deployment/42/services' }, (res) => {
expect(res.statusCode).to.equal(200);
expect(res.result.length).to.equal(2);
done();
const deployment = {
name: 'User Services',
datacenter: 'us-sw-1'
};
server.inject({ method: 'POST', url: '/deployment', payload: deployment }, (res) => {
expect(res.statusCode).to.equal(201);
server.inject({ method: 'GET', url: `/deployment/${res.result.id}/services` }, (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
});
});
@ -256,21 +313,35 @@ describe('services', () => {
server.connection();
server.register(internals.register, (err) => {
expect(err).to.not.exist();
const payload = {
count: 3
const deployment = {
name: 'User Services',
datacenter: 'us-sw-1'
};
server.inject({ method: 'PUT', url: '/deployment/42/service/consul', payload }, (res) => {
expect(res.statusCode).to.equal(200);
expect(res.result.count).to.equal(3);
done();
server.inject({ method: 'POST', url: '/deployment', payload: deployment }, (res) => {
expect(res.statusCode).to.equal(201);
const deploymentId = res.result.id;
server.inject({ method: 'GET', url: `/deployment/${deploymentId}/services` }, (res) => {
expect(res.statusCode).to.equal(200);
const service = {
count: 2
};
server.inject({ method: 'PUT', url: `/deployment/${deploymentId}/service/consul`, payload: service }, (res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
});
});
});
});
describe('graphql', () => {
describe.skip('graphql', () => {
it('route exists', (done) => {
const server = new Hapi.Server();
server.connection();