mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2024-12-22 06:57:49 +00:00
When the device is about to reboot or shutdown, close the API server and avoid applying updates
We mark when the device is rebooting and avoid some steps in the update cycle that change the device state, similarly to when the device is in local mode, to avoid problems with non-atomic operations. This doesn't solve *all* the potential scenarios of a reboot happening in the middle of an update, but at least should prevent the case where we start an app container and reboot the device before saving the containerId, potentially causing a duplicated container issue. We also correct the API docs to reflect the 202 response when reboot or shutdown are successful. Change-Type: patch Signed-off-by: Pablo Carranza Velez <pablo@resin.io>
This commit is contained in:
parent
bfc28a0ed4
commit
42ac7487e7
@ -77,7 +77,7 @@ $ curl -X POST --header "Content-Type:application/json" \
|
||||
|
||||
Triggers an update check on the supervisor. Optionally, forces an update when updates are locked.
|
||||
|
||||
Responds with an empty 204 (Accepted) response.
|
||||
Responds with an empty 204 (No Content) response.
|
||||
|
||||
#### Request body
|
||||
Can be a JSON object with a `force` property. If this property is true, the update lock will be overridden.
|
||||
@ -110,7 +110,7 @@ $ curl -X POST --header "Content-Type:application/json" \
|
||||
|
||||
Reboots the device
|
||||
|
||||
When successful, responds with 204 accepted and a JSON object:
|
||||
When successful, responds with 202 accepted and a JSON object:
|
||||
```json
|
||||
{
|
||||
"Data": "OK",
|
||||
@ -144,7 +144,7 @@ $ curl -X POST --header "Content-Type:application/json" \
|
||||
|
||||
**Dangerous**. Shuts down the device.
|
||||
|
||||
When successful, responds with 204 accepted and a JSON object:
|
||||
When successful, responds with 202 accepted and a JSON object:
|
||||
```json
|
||||
{
|
||||
"Data": "OK",
|
||||
|
@ -57,18 +57,16 @@ module.exports = (application) ->
|
||||
.then (app) ->
|
||||
application.kill(app, removeContainer: false) if app?
|
||||
.then ->
|
||||
new Promise (resolve, reject) ->
|
||||
application.logSystemMessage('Rebooting', {}, 'Reboot')
|
||||
utils.gosuper.post('/v1/reboot')
|
||||
.on('error', reject)
|
||||
.on('response', -> resolve())
|
||||
.pipe(res)
|
||||
device.reboot()
|
||||
.then (response) ->
|
||||
res.status(202).json(response)
|
||||
.catch (err) ->
|
||||
if err instanceof application.UpdatesLockedError
|
||||
status = 423
|
||||
else
|
||||
status = 500
|
||||
res.status(status).send(err?.message or err or 'Unknown error')
|
||||
res.status(status).json({ Data: '', Error: err?.message or err or 'Unknown error' })
|
||||
|
||||
parsedRouter.post '/v1/shutdown', (req, res) ->
|
||||
force = req.body.force
|
||||
@ -80,18 +78,16 @@ module.exports = (application) ->
|
||||
.then (app) ->
|
||||
application.kill(app, removeContainer: false) if app?
|
||||
.then ->
|
||||
new Promise (resolve, reject) ->
|
||||
application.logSystemMessage('Shutting down', {}, 'Shutdown')
|
||||
utils.gosuper.post('/v1/shutdown')
|
||||
.on('error', reject)
|
||||
.on('response', -> resolve())
|
||||
.pipe(res)
|
||||
device.shutdown()
|
||||
.then (response) ->
|
||||
res.status(202).json(response)
|
||||
.catch (err) ->
|
||||
if err instanceof application.UpdatesLockedError
|
||||
status = 423
|
||||
else
|
||||
status = 500
|
||||
res.status(status).send(err?.message or err or 'Unknown error')
|
||||
res.status(status).json({ Data: '', Error: err?.message or err or 'Unknown error' })
|
||||
|
||||
parsedRouter.post '/v1/purge', (req, res) ->
|
||||
appId = req.body.appId
|
||||
|
@ -29,7 +29,8 @@ knex.init.then ->
|
||||
.then ->
|
||||
apiServer = api(application).listen(config.listenPort)
|
||||
apiServer.timeout = config.apiTimeout
|
||||
|
||||
device.events.on 'shutdown', ->
|
||||
apiServer.close()
|
||||
bootstrap.done
|
||||
.then ->
|
||||
Promise.join(
|
||||
|
@ -709,7 +709,7 @@ application.update = update = (force, scheduled = false) ->
|
||||
.tap (remoteApps) ->
|
||||
# Before running the updates, try to clean up any images that aren't in use
|
||||
# and will not be used in the target state
|
||||
return if application.localMode
|
||||
return if application.localMode or device.shuttingDown
|
||||
dockerUtils.cleanupContainersAndImages(_.map(remoteApps, 'imageId'))
|
||||
.catch (err) ->
|
||||
console.log('Cleanup failed: ', err, err.stack)
|
||||
@ -737,7 +737,7 @@ application.update = update = (force, scheduled = false) ->
|
||||
logSystemMessage("Error fetching/applying device configuration: #{err}", { error: err }, 'Set device configuration error')
|
||||
.return(allAppIds)
|
||||
.map (appId) ->
|
||||
return if application.localMode
|
||||
return if application.localMode or device.shuttingDown
|
||||
Promise.try ->
|
||||
needsDownload = _.includes(toBeDownloaded, appId)
|
||||
if _.includes(toBeRemoved, appId)
|
||||
@ -792,9 +792,9 @@ application.update = update = (force, scheduled = false) ->
|
||||
_.each(failures, (err) -> console.error('Error:', err, err.stack))
|
||||
throw new Error(joinErrorMessages(failures)) if failures.length > 0
|
||||
.then ->
|
||||
proxyvisor.sendUpdates()
|
||||
proxyvisor.sendUpdates() if !device.shuttingDown
|
||||
.then ->
|
||||
return if application.localMode
|
||||
return if application.localMode or device.shuttingDown
|
||||
updateStatus.failed = 0
|
||||
device.setUpdateState(update_pending: false, update_downloaded: false, update_failed: false)
|
||||
# We cleanup here as we want a point when we have a consistent apps/images state, rather than potentially at a
|
||||
|
@ -1,7 +1,6 @@
|
||||
_ = require 'lodash'
|
||||
Promise = require 'bluebird'
|
||||
memoizee = require 'memoizee'
|
||||
knex = require './db'
|
||||
utils = require './utils'
|
||||
{ resinApi } = require './request'
|
||||
device = exports
|
||||
@ -12,6 +11,7 @@ fs = Promise.promisifyAll(require('fs'))
|
||||
bootstrap = require './bootstrap'
|
||||
{ checkTruthy } = require './lib/validation'
|
||||
osRelease = require './lib/os-release'
|
||||
EventEmitter = require 'events'
|
||||
|
||||
# If we don't use promise: 'then', exceptions will crash the program
|
||||
memoizePromise = _.partial(memoizee, _, promise: 'then')
|
||||
@ -40,8 +40,25 @@ exports.getID = memoizePromise ->
|
||||
throw new Error('Could not find this device?!')
|
||||
return devices[0].id
|
||||
|
||||
exports.shuttingDown = false
|
||||
exports.events = new EventEmitter()
|
||||
exports.reboot = ->
|
||||
utils.gosuper.postAsync('/v1/reboot')
|
||||
utils.gosuper.postAsync('/v1/reboot', { json: true })
|
||||
.spread (res, body) ->
|
||||
if res.statusCode != 202
|
||||
throw new Error(body.Error)
|
||||
exports.shuttingDown = true
|
||||
exports.events.emit('shutdown')
|
||||
return body
|
||||
|
||||
exports.shutdown = ->
|
||||
utils.gosuper.postAsync('/v1/shutdown', { json: true })
|
||||
.spread (res, body) ->
|
||||
if res.statusCode != 202
|
||||
throw new Error(body.Error)
|
||||
exports.shuttingDown = true
|
||||
exports.events.emit('shutdown')
|
||||
return body
|
||||
|
||||
exports.hostConfigConfigVarPrefix = 'RESIN_HOST_'
|
||||
bootConfigEnvVarPrefix = 'RESIN_HOST_CONFIG_'
|
||||
|
Loading…
Reference in New Issue
Block a user