Restructure the updates/channels/instances models to accommodate Update Levels

Per some recent discussion around the Update Lifecycle JEP, this introduces
update levels into the data model

Including anull Update Level which is to be used for seed purposes

Fixes JENKINS-50949
This commit is contained in:
R. Tyler Croy 2018-04-24 10:51:48 -07:00
parent ade8d97b97
commit 6e16585a3d
No known key found for this signature in database
GPG Key ID: 1426C7DC3F51E16F
16 changed files with 161 additions and 125 deletions

View File

@ -54,39 +54,48 @@ service will be receiving semi-arbitrary blobs of JSON from instances.
[source]
----
+------------+ +----------------+
| channels | | connections |
+------------+ +----------------+
| id <---+ +------------+ | id |
| name | | | instances | +----+ uuid |
| createdAt | | +------------+ | | last_connected |
| updatedAt | | | id | | | createdAt |
+------------+ | | uuid* <--+ | updatedAt |
| | timezone | ^^ +----------------+
+----------------+ +--+ channelId | || +--------------------+
| updates | +--+ updateId | || | capabilities |
+----------------+ | | createdAt | || +-------------------- +
| id <---+ | updatedAt | || | id |
| sha1 | +------------+ |+----+ uuid |
| manifest (JSON)| | | capabilities (JSON)|
| tainted | | | createdAt |
| updatedAt | | | updatedAt |
| updatedAt | | +--------------------+
+----------------+ |
|
| +-----------------------+
| | versions |
| +-----------------------+
| | id |
+-----+ uuid |
| manifest (JSON) |
| manifestSchemaVersion |
| core |
| createdAt |
| updatedAt |
+-----------------------+
+------------------------+
| versions |
+------------------------+
| id |
+--+ *uuid |
| | manifest (JSON) |
+------------+ | | manifestSchemaVersion |
| instances | | | checksum (unique) |
+------------+ | | createdAt |
| id | | | updatedAt |
| *uuid <---++ +------------------------+
| timezone | ^^
+----------------+ +---+ updateId | ||
| updates | | | createdAt | ||
+----------------+ | | updatedAt | ||
| id <---+ +------------+ ||
| commit | ||
| channel | || +---------------------+
| manifest (JSON)| || | capabilities |
| tainted | || +---------------------+
| createdAt | || | id |
| updatedAt | |+--+ *uuid |
+----------------+ | | capabilities (JSON)|
| | createdAt |
| | updatedAt |
| +---------------------+
|
|
|
| +-----------------+
| | connections |
| +-----------------+
| | id |
+---+ *uuid |
| lastConnected |
| createdAt |
| updatedAt |
+-----------------+
* denotes an index
`id` fields are always considered primary keys
----
==== Versions

View File

@ -57,7 +57,7 @@ describe('Status service acceptance tests', () => {
});
assert.ok(response);
assert.ok(response.channelId);
assert.ok(response.updateId);
});
it('should not allow creating a status for a uuid not in the JWT', () => {
@ -101,9 +101,9 @@ describe('Status service acceptance tests', () => {
it('should return the proper `channel` relationship', () => {
assert.ok(this.response);
assert.ok(this.response.channel);
assert.ok(this.response.update);
/* we expect everybody to be in the general channel by default */
assert.equal(this.response.channel.name, 'general');
assert.equal(this.response.update.channel, 'general');
});
});

View File

@ -20,9 +20,6 @@ module.exports = {
updateId: {
type: Sequelize.BIGINT
},
createdAt: {
type: Sequelize.DATE
},
updatedAt: {
type: Sequelize.DATE
},

View File

@ -11,12 +11,6 @@ module.exports = {
uuid: {
type: Sequelize.STRING
},
createdAt: {
type: Sequelize.DATE
},
core: {
type: Sequelize.STRING
},
manifest: {
type: Sequelize.JSON
},
@ -36,4 +30,4 @@ module.exports = {
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('versions');
}
};
};

View File

@ -0,0 +1,16 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.addColumn('channels', 'updateId',
{
allowNull: false,
defaultValue: 0,
type: Sequelize.INTEGER
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.removeColumn('channels', 'updateId', {});
}
};

View File

@ -0,0 +1,12 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.dropTable('channels').then((query) => {
return queryInterface.removeColumn('instances', 'channelId');
});
},
down: (queryInterface, Sequelize) => {
}
};

View File

@ -0,0 +1,17 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.addColumn('updates', 'channel',
{
defaultValue: 'general',
allowNull: false,
type: Sequelize.STRING
},
);
},
down: (queryInterface, Sequelize) => {
return queryInterface.removeColumn('updates', 'channel', {});
}
};

0
services/seeders/.gitignore vendored Normal file
View File

View File

@ -1,29 +0,0 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.bulkInsert('channels', [
{
name: 'canary',
createdAt: Sequelize.fn('NOW'),
updatedAt: Sequelize.fn('NOW')
},
{
name: 'beta',
createdAt: Sequelize.fn('NOW'),
updatedAt: Sequelize.fn('NOW')
},
{
name: 'general',
createdAt: Sequelize.fn('NOW'),
updatedAt: Sequelize.fn('NOW')
}
], {});
},
down: (queryInterface, Sequelize) => {
/* No sense having a 'down', if we don't have channels, we don't really
* have a functioning distribution system
*/
}
};

View File

@ -0,0 +1,24 @@
'use strict';
/*
* This seed just creates a null Update Level which is a special case to
* indicate that the registered instance has no updates whatsoever yet
*/
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.bulkInsert('updates', [
{
commit: '',
channel: 'general',
manifest: '{}',
tainted: false,
createdAt: Sequelize.fn('NOW'),
updatedAt: Sequelize.fn('NOW')
}
]);
},
down: (queryInterface, Sequelize) => {
}
};

View File

@ -1,18 +0,0 @@
'use strict';
const Sequelize = require('sequelize');
const DataTypes = Sequelize.DataTypes;
module.exports = function (app) {
const sequelizeClient = app.get('sequelizeClient');
const channel = sequelizeClient.define('channels', {
name: DataTypes.STRING,
createdAt: DataTypes.DATE,
updatedAt: DataTypes.DATE
});
// eslint-disable-next-line no-unused-vars
channel.associate = function (models) {
};
return channel;
};

View File

@ -7,14 +7,12 @@ module.exports = function (app) {
const instance = sequelizeClient.define('instances', {
uuid: DataTypes.UUID,
timezone: DataTypes.STRING,
channelId: DataTypes.BIGINT,
updateId: DataTypes.BIGINT,
createdAt: DataTypes.DATE,
updatedAt: DataTypes.DATE
});
instance.associate = function (models) {
instance.belongsTo(models.channels);
instance.belongsTo(models.updates);
};

View File

@ -6,6 +6,7 @@ module.exports = function (app) {
const sequelizeClient = app.get('sequelizeClient');
const update = sequelizeClient.define('updates', {
commit: DataTypes.STRING,
channel: DataTypes.STRING,
manifest: DataTypes.JSON,
tainted: DataTypes.BOOLEAN,
createdAt: DataTypes.DATE,

View File

@ -6,16 +6,16 @@ module.exports = function (app) {
const sequelizeClient = app.get('sequelizeClient');
const version = sequelizeClient.define('versions', {
uuid: DataTypes.STRING,
manifest: DataTypes.JSON,
manifestSchemaVersion: DataTypes.INTEGER,
checksum: DataTypes.STRING,
createdAt: DataTypes.DATE,
updatedAt: DataTypes.DATE,
core: DataTypes.STRING,
checksum: DataTypes.STRING,
manifest: DataTypes.JSON,
manifestSchemaVersion: DataTypes.INTEGER
});
// eslint-disable-next-line no-unused-vars
version.associate = function(models) {
};
return version;
};

View File

@ -6,31 +6,28 @@ const authentication = require('@feathersjs/authentication');
const internalOnly = require('../../hooks/internalonly');
const ensureMatchingUUID = require('../../hooks/ensureuuid');
/*
* To make things easier to unit test, these hook functions are being exported
*/
module.exports = {};
/*
* Augment the inbound data to include the default channel, presumed to be
* `general`
* Include the model's associations in the output from the hook
*/
module.exports.defaultChannel = function(context) {
context.data.channelId = 3;
return context;
};
module.exports.includeAssociations = function(context) {
if (!context.params.sequelize) {
context.params.sequelize = {};
}
Object.assign(context.params.sequelize, {
include: [ context.app.get('models').channel ]
include: [ context.app.get('models').update ]
});
return context;
};
/*
* delete extra parameters included in the query string
*/
module.exports.pruneQueryParams = function(context) {
/*
* delete extra parameters included in the query string
*/
if (context.params.query) {
delete context.params.query.include;
}
@ -38,7 +35,37 @@ module.exports.pruneQueryParams = function(context) {
};
/*
* Default new instances into the latest update record in the `general` channel.
*/
module.exports.defaultUpdateLevel = async function(context) {
const updates = context.app.service('update');
const result = await updates.find({
query: {
tainted: false,
channel: 'general',
$limit: 1,
$sort: {
createdAt: -1,
}
},
});
if (result.total == 0) {
throw new Error('Failed to find the latest `general` updates for instance creation');
}
/*
* The result returned is a paginated object
*/
context.data.updateId = result.data[0].id;
return context;
};
/*
* mash everything into module.exports for easy of use in the service
* definition
*/
Object.assign(module.exports, {
before: {
all: [
@ -52,8 +79,8 @@ Object.assign(module.exports, {
create: [
ensureMatchingUUID,
module.exports.defaultChannel,
module.exports.pruneQueryParams
module.exports.defaultUpdateLevel,
module.exports.pruneQueryParams,
],
update: [

View File

@ -23,25 +23,13 @@ describe('status service hooks', () => {
});
});
describe('create hooks', () => {
let context = { data: {} };
it('should set the default channel', () => {
assert.equal(context, hooks.defaultChannel(context));
/* we're assuming the channelId of three, which is hard-coded in our
* seeds, will be the 'general' channelId
*/
assert.equal(context.data.channelId, 3);
});
});
describe('get hooks', () => {
let context = { data: {}, params: {} };
let channelModel = new Object();
let updateModel = new Object();
beforeEach(() => {
context.app = app;
app.set('models', { channel: channelModel });
app.set('models', { update: updateModel });
});
it('should include the Instance model associations', () => {
@ -49,7 +37,7 @@ describe('status service hooks', () => {
assert.ok(context.params.sequelize, 'context.params.sequelize is supposed to exist now');
let includes = context.params.sequelize.include;
assert.ok(includes);
assert.equal(includes[0], channelModel);
assert.equal(includes[0], updateModel);
});
it('should prune user-supplied query parameters', () => {