-
Notifications
You must be signed in to change notification settings - Fork 59
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2180 from flowforge/2156-ha-replicas
HA: multiple instance replica support
- Loading branch information
Showing
24 changed files
with
709 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
const { KEY_HA } = require('../../../db/models/ProjectSettings') | ||
|
||
module.exports.init = function (app) { | ||
if (app.config.driver.type === 'k8s' || app.config.driver.type === 'stub') { | ||
// Register ha flag as a private flag - no requirement to expose it in public settings | ||
app.config.features.register('ha', true) | ||
|
||
/** | ||
* Check if HA is allowed for this given team/projectType/haConfig combination | ||
* @param {*} team | ||
* @param {*} projectType | ||
* @param {*} haConfig | ||
* @returns true/false | ||
*/ | ||
async function isHAAllowed (team, projectType, haConfig) { | ||
// For initial beta release, we will support 1-2 replicas. | ||
// 1 replica is equivalent to no HA | ||
// In the future this will need to take into account the team type | ||
return (haConfig.replicas > 0 && haConfig.replicas < 3) | ||
} | ||
|
||
// Add ha functions to the Project model | ||
app.db.models.Project.prototype.getHASettings = async function () { | ||
return this.getSetting(KEY_HA) | ||
} | ||
app.db.models.Project.prototype.updateHASettings = async function (haConfig) { | ||
if (!haConfig) { | ||
return this.removeSetting(KEY_HA) | ||
} | ||
if (haConfig?.replicas > 0 && haConfig?.replicas < 3) { | ||
return this.updateSetting(KEY_HA, { | ||
replicas: haConfig.replicas | ||
}) | ||
} | ||
} | ||
|
||
app.decorate('ha', { | ||
isHAAllowed | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
module.exports = async function (app) { | ||
app.addHook('preHandler', app.verifySession) | ||
app.addHook('preHandler', async (request, reply) => { | ||
if (!app.config.features.enabled('ha')) { | ||
reply.code(404).send({ code: 'not_found', error: 'Not Found' }) | ||
} | ||
}) | ||
app.addHook('preHandler', async (request, reply) => { | ||
if (request.params.projectId !== undefined) { | ||
if (request.params.projectId) { | ||
try { | ||
request.project = await app.db.models.Project.byId(request.params.projectId) | ||
if (!request.project) { | ||
reply.code(404).send({ code: 'not_found', error: 'Not Found' }) | ||
return | ||
} | ||
if (request.session.User) { | ||
request.teamMembership = await request.session.User.getTeamMembership(request.project.Team.id) | ||
if (!request.teamMembership && !request.session.User.admin) { | ||
reply.code(404).send({ code: 'not_found', error: 'Not Found' }) | ||
return | ||
} | ||
} else if (request.session.ownerId !== request.params.projectId) { | ||
// AccesToken being used - but not owned by this project | ||
reply.code(404).send({ code: 'not_found', error: 'Not Found' }) | ||
return | ||
} | ||
} catch (err) { | ||
reply.code(404).send({ code: 'not_found', error: 'Not Found' }) | ||
} | ||
} else { | ||
reply.code(404).send({ code: 'not_found', error: 'Not Found' }) | ||
} | ||
} | ||
}) | ||
|
||
app.get('/', { | ||
preHandler: app.needsPermission('project:read') | ||
}, async (request, reply) => { | ||
reply.send(await request.project.getHASettings() || {}) | ||
}) | ||
|
||
app.put('/', { | ||
preHandler: app.needsPermission('project:edit') | ||
}, async (request, reply) => { | ||
// For 1.8, only support setting replica count to 2 | ||
if (request.body.replicas !== 2) { | ||
reply.code(409).send({ code: 'invalid_ha_configuration', error: 'Invalid HA configuration -only 2 replicas are allowed' }) | ||
return | ||
} | ||
const existingHA = await request.project.getHASettings() || {} | ||
if (existingHA.replicas !== request.body.replicas) { | ||
// This is a change in replica count. | ||
await request.project.updateHASettings({ replicas: request.body.replicas }) | ||
await applyUpdatedInstanceSettings(reply, request.project, request.session.User) | ||
} else { | ||
reply.send(await request.project.getHASettings() || {}) | ||
} | ||
}) | ||
|
||
app.delete('/', { | ||
preHandler: app.needsPermission('project:edit') | ||
}, async (request, reply) => { | ||
const existingHA = await request.project.getHASettings() || {} | ||
if (existingHA.replicas) { | ||
// This instance already has ha configured - clear the setting | ||
// and apply | ||
await request.project.updateHASettings(undefined) | ||
await applyUpdatedInstanceSettings(reply, request.project, request.session.User) | ||
} else { | ||
reply.send({}) | ||
} | ||
}) | ||
|
||
async function applyUpdatedInstanceSettings (reply, project, user) { | ||
if (project.state !== 'suspended') { | ||
// This code is copy/paste with slight changes from projects.js | ||
// We also have projectActions.js that does suspend logic. | ||
// TODO: refactor into a Model function to suspend a project | ||
app.db.controllers.Project.setInflightState(project, 'starting') // TODO: better inflight state needed | ||
reply.send(await project.getHASettings() || {}) | ||
|
||
const targetState = project.state | ||
app.log.info(`Stopping project ${project.id}`) | ||
await app.containers.stop(project, { | ||
skipBilling: true | ||
}) | ||
await app.auditLog.Project.project.suspended(user, null, project) | ||
app.log.info(`Restarting project ${project.id}`) | ||
project.state = targetState | ||
await project.save() | ||
await project.reload() | ||
const startResult = await app.containers.start(project) | ||
startResult.started.then(async () => { | ||
await app.auditLog.Project.project.started(user, null, project) | ||
app.db.controllers.Project.clearInflightState(project) | ||
return true | ||
}).catch(_ => { | ||
app.db.controllers.Project.clearInflightState(project) | ||
}) | ||
} else { | ||
// A suspended project doesn't need to do anything more | ||
// The settings will get applied when it is next resumed | ||
reply.send(await project.getHASettings() || {}) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.