mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2024-12-30 10:38:55 +00:00
First attempt at locking updates with files
This commit is contained in:
parent
9bbb0be536
commit
c52c2c0bd9
@ -15,6 +15,7 @@
|
|||||||
"event-stream": "^3.0.20",
|
"event-stream": "^3.0.20",
|
||||||
"express": "^4.0.0",
|
"express": "^4.0.0",
|
||||||
"knex": "~0.8.3",
|
"knex": "~0.8.3",
|
||||||
|
"lockfile": "^1.0.1",
|
||||||
"lodash": "^3.0.0",
|
"lodash": "^3.0.0",
|
||||||
"mixpanel": "0.0.20",
|
"mixpanel": "0.0.20",
|
||||||
"network-checker": "~0.0.5",
|
"network-checker": "~0.0.5",
|
||||||
|
@ -29,7 +29,7 @@ module.exports = (secret) ->
|
|||||||
|
|
||||||
api.post '/v1/update', (req, res) ->
|
api.post '/v1/update', (req, res) ->
|
||||||
utils.mixpanelTrack('Update notification')
|
utils.mixpanelTrack('Update notification')
|
||||||
application.update()
|
application.update(req.body.force)
|
||||||
res.sendStatus(204)
|
res.sendStatus(204)
|
||||||
|
|
||||||
api.post '/v1/spawn-tty', (req, res) ->
|
api.post '/v1/spawn-tty', (req, res) ->
|
||||||
@ -72,7 +72,7 @@ module.exports = (secret) ->
|
|||||||
if !app?
|
if !app?
|
||||||
throw new Error('App not found')
|
throw new Error('App not found')
|
||||||
Promise.using application.lockUpdates(), ->
|
Promise.using application.lockUpdates(), ->
|
||||||
application.kill(app)
|
application.lockAndKill(app)
|
||||||
.then ->
|
.then ->
|
||||||
new Promise (resolve, reject) ->
|
new Promise (resolve, reject) ->
|
||||||
request.post(config.gosuperAddress + '/v1/purge', { json: true, body: applicationId: appId })
|
request.post(config.gosuperAddress + '/v1/purge', { json: true, body: applicationId: appId })
|
||||||
@ -80,7 +80,7 @@ module.exports = (secret) ->
|
|||||||
.on 'response', -> resolve()
|
.on 'response', -> resolve()
|
||||||
.pipe(res)
|
.pipe(res)
|
||||||
.finally ->
|
.finally ->
|
||||||
application.start(app)
|
application.startAndUnlock(app)
|
||||||
.catch (err) ->
|
.catch (err) ->
|
||||||
res.status(503).send(err?.message or err or 'Unknown error')
|
res.status(503).send(err?.message or err or 'Unknown error')
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ knex.init.then ->
|
|||||||
console.log('Starting Apps..')
|
console.log('Starting Apps..')
|
||||||
knex('app').select()
|
knex('app').select()
|
||||||
.then (apps) ->
|
.then (apps) ->
|
||||||
Promise.all(apps.map(application.start))
|
Promise.all(apps.map(application.startAndUnlock))
|
||||||
.catch (error) ->
|
.catch (error) ->
|
||||||
console.error('Error starting apps:', error)
|
console.error('Error starting apps:', error)
|
||||||
.then ->
|
.then ->
|
||||||
|
@ -11,6 +11,8 @@ tty = require './lib/tty'
|
|||||||
logger = require './lib/logger'
|
logger = require './lib/logger'
|
||||||
{ cachedResinApi } = require './request'
|
{ cachedResinApi } = require './request'
|
||||||
device = require './device'
|
device = require './device'
|
||||||
|
lockFile = Promise.promisifyAll(require('lockfile'))
|
||||||
|
fs = Promise.promisifyAll(require('fs'))
|
||||||
|
|
||||||
{ docker } = dockerUtils
|
{ docker } = dockerUtils
|
||||||
|
|
||||||
@ -30,7 +32,7 @@ logTypes =
|
|||||||
humanName: 'Killed application'
|
humanName: 'Killed application'
|
||||||
stopAppError:
|
stopAppError:
|
||||||
eventName: 'Application stop error'
|
eventName: 'Application stop error'
|
||||||
humanName: 'Killed application'
|
humanName: 'Failed to kill application'
|
||||||
|
|
||||||
downloadApp:
|
downloadApp:
|
||||||
eventName: 'Application download'
|
eventName: 'Application download'
|
||||||
@ -244,6 +246,32 @@ getEnvironment = do ->
|
|||||||
console.error("Failed to get environment for device #{deviceId}, app #{appId}. #{err}")
|
console.error("Failed to get environment for device #{deviceId}, app #{appId}. #{err}")
|
||||||
throw err
|
throw err
|
||||||
|
|
||||||
|
lockPath = (app) -> "/mnt/root/resin-data/#{app.appId}/resin_updates.lock"
|
||||||
|
locked = {}
|
||||||
|
exports.lockAndKill = lockAndKill = (app, force) ->
|
||||||
|
Promise.try ->
|
||||||
|
fs.unlinkAsync(lockPath(app)) if force == true
|
||||||
|
.then ->
|
||||||
|
locked[app.appId] = true
|
||||||
|
lockFile.lockAsync(lockPath(app))
|
||||||
|
.catch (err) ->
|
||||||
|
if err.code != 'ENOENT'
|
||||||
|
locked[app.appId] = false
|
||||||
|
message = 'Updates are locked by application'
|
||||||
|
logSystemEvent(logTypes.stopAppError, app, { message })
|
||||||
|
throw message
|
||||||
|
.then ->
|
||||||
|
kill(app)
|
||||||
|
|
||||||
|
exports.startAndUnlock = startAndUnlock = (app) ->
|
||||||
|
Promise.try ->
|
||||||
|
throw "Cannot start app because we couldn't acquire lock" if locked[app.appId] == false
|
||||||
|
.then ->
|
||||||
|
locked[app.appId] = null
|
||||||
|
start(app)
|
||||||
|
.then ->
|
||||||
|
lockFile.unlockAsync(lockPath(app))
|
||||||
|
|
||||||
exports.lockUpdates = lockUpdates = do ->
|
exports.lockUpdates = lockUpdates = do ->
|
||||||
_lock = new Lock()
|
_lock = new Lock()
|
||||||
_lockUpdates = Promise.promisify(_lock.async.writeLock)
|
_lockUpdates = Promise.promisify(_lock.async.writeLock)
|
||||||
@ -254,7 +282,7 @@ exports.lockUpdates = lockUpdates = do ->
|
|||||||
# 2 - Update required
|
# 2 - Update required
|
||||||
currentlyUpdating = 0
|
currentlyUpdating = 0
|
||||||
failedUpdates = 0
|
failedUpdates = 0
|
||||||
exports.update = update = ->
|
exports.update = update = (force) ->
|
||||||
if currentlyUpdating isnt 0
|
if currentlyUpdating isnt 0
|
||||||
# Mark an update required after the current.
|
# Mark an update required after the current.
|
||||||
currentlyUpdating = 2
|
currentlyUpdating = 2
|
||||||
@ -331,20 +359,20 @@ exports.update = update = ->
|
|||||||
Promise.using lockUpdates(), ->
|
Promise.using lockUpdates(), ->
|
||||||
# Then delete all the ones to remove in one go
|
# Then delete all the ones to remove in one go
|
||||||
Promise.map toBeRemoved, (imageId) ->
|
Promise.map toBeRemoved, (imageId) ->
|
||||||
kill(apps[imageId])
|
lockAndKill(apps[imageId], force)
|
||||||
.then ->
|
.then ->
|
||||||
# Then install the apps and add each to the db as they succeed
|
# Then install the apps and add each to the db as they succeed
|
||||||
installingPromises = toBeInstalled.map (imageId) ->
|
installingPromises = toBeInstalled.map (imageId) ->
|
||||||
app = remoteApps[imageId]
|
app = remoteApps[imageId]
|
||||||
start(app)
|
startAndUnlock(app)
|
||||||
# And remove/recreate updated apps and update db as they succeed
|
# And remove/recreate updated apps and update db as they succeed
|
||||||
updatingPromises = toBeUpdated.map (imageId) ->
|
updatingPromises = toBeUpdated.map (imageId) ->
|
||||||
localApp = apps[imageId]
|
localApp = apps[imageId]
|
||||||
app = remoteApps[imageId]
|
app = remoteApps[imageId]
|
||||||
logSystemEvent(logTypes.updateApp, app)
|
logSystemEvent(logTypes.updateApp, app)
|
||||||
kill(localApp)
|
lockAndKill(localApp, force)
|
||||||
.then ->
|
.then ->
|
||||||
start(app)
|
startAndUnlock(app)
|
||||||
Promise.all(installingPromises.concat(updatingPromises))
|
Promise.all(installingPromises.concat(updatingPromises))
|
||||||
.then ->
|
.then ->
|
||||||
failedUpdates = 0
|
failedUpdates = 0
|
||||||
|
Loading…
Reference in New Issue
Block a user