From 670f318c3988bf462126b1041eb45538c4585a04 Mon Sep 17 00:00:00 2001 From: Pablo Carranza Velez Date: Mon, 15 Feb 2016 20:34:11 +0000 Subject: [PATCH 1/4] Add endpoint to get device state --- CHANGELOG.md | 1 + docs/API.md | 35 +++++++++++++++++++++++++++++++++++ src/api.coffee | 3 +++ src/device.coffee | 6 +++++- 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 901eedfd..c2a8fb02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +* Add endpoint to get device state [Pablo] * Check for valid strings or ints in all config values [Pablo] * Remove quotes in OS version [Pablo] diff --git a/docs/API.md b/docs/API.md index 8505f24d..2aa34c70 100644 --- a/docs/API.md +++ b/docs/API.md @@ -405,3 +405,38 @@ $ curl -X POST --header "Content-Type:application/json" \ --data '{"deviceId": , "appId": }' \ "https://api.resin.io/supervisor/v1/regenerate-api-key" ``` + +
+ +### GET /v1/device + +Introduced in supervisor v1.6. +Returns the current device state, as reported to the Resin API. +The state is a JSON object that contains some or all of the following: +* `api_port`: Port on which the supervisor is listening. +* `ip_address`: Space-separated list of IP addresses of the device. +* `status`: Status of the device regarding the app, as a string, i.e. "Stopping", "Starting", "Downloading", "Installing", "Idle". +* `download_progress`: Amount of the application image that has been downloaded, expressed as a percentage. +* `os_version`: Version of the host OS running on the device. +* `supervisor_version`: Version of the supervisor running on the device. + +Other attributes may be added in the future, and some may be missing or null if they haven't been set yet. + +#### Examples: +From the app on the device: +```bash +$ curl -X GET --header "Content-Type:application/json" \ + "$RESIN_SUPERVISOR_ADDRESS/v1/device?apikey=$RESIN_SUPERVISOR_API_KEY" +``` +Response: +```json +{"api_port":48484,"ip_address":"192.168.0.114 10.42.0.3","status":"Downloading","download_progress":84,"os_version":"Resin OS 1.0.4 (fido)","supervisor_version":"1.6.0"} +``` + +Remotely via the API proxy: +```bash +$ curl -X POST --header "Content-Type:application/json" \ + --header "Authorization: Bearer " \ + --data '{"deviceId": , "appId": , "method": "GET"}' \ + "https://api.resin.io/supervisor/v1/device" +``` diff --git a/src/api.coffee b/src/api.coffee index 6da3fc54..b1d679b0 100644 --- a/src/api.coffee +++ b/src/api.coffee @@ -136,4 +136,7 @@ module.exports = (application) -> .catch (err) -> res.status(503).send(err?.message or err or 'Unknown error') + api.get '/v1/device', (req, res) -> + res.json(device.getState()) + return api diff --git a/src/device.coffee b/src/device.coffee index a06e766d..b7c0089a 100644 --- a/src/device.coffee +++ b/src/device.coffee @@ -128,12 +128,16 @@ exports.getDeviceType = do -> throw new Error('Device type not specified in config file') return configFromFile.deviceType +targetState = {} +exports.getState = -> + fieldsToOmit = ['api_secret', 'logs_channel', 'provisioning_progress', 'provisioning_state'] + return _.omit(targetState, fieldsToOmit) + # Calling this function updates the local device state, which is then used to synchronise # the remote device state, repeating any failed updates until successfully synchronised. # This function will also optimise updates by merging multiple updates and only sending the latest state. exports.updateState = do -> applyPromise = Promise.resolve() - targetState = {} actualState = {} getStateDiff = -> From d0c76aec0fcf0bb9bc7aaa62748553abadea434a Mon Sep 17 00:00:00 2001 From: Pablo Carranza Velez Date: Mon, 15 Feb 2016 21:09:55 +0000 Subject: [PATCH 2/4] Use a closure for device state and also report in GET /v1/device whether there's a pending update --- docs/API.md | 3 ++- src/application.coffee | 2 ++ src/device.coffee | 26 ++++++++++++++++---------- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/docs/API.md b/docs/API.md index 2aa34c70..9b759dbe 100644 --- a/docs/API.md +++ b/docs/API.md @@ -419,6 +419,7 @@ The state is a JSON object that contains some or all of the following: * `download_progress`: Amount of the application image that has been downloaded, expressed as a percentage. * `os_version`: Version of the host OS running on the device. * `supervisor_version`: Version of the supervisor running on the device. +* `update_pending`: This one is not reported to the Resin API. It's a boolean that will be true if the supervisor has tried to update the app, but failed (for example, if the update lock was set). Other attributes may be added in the future, and some may be missing or null if they haven't been set yet. @@ -430,7 +431,7 @@ $ curl -X GET --header "Content-Type:application/json" \ ``` Response: ```json -{"api_port":48484,"ip_address":"192.168.0.114 10.42.0.3","status":"Downloading","download_progress":84,"os_version":"Resin OS 1.0.4 (fido)","supervisor_version":"1.6.0"} +{"api_port":48484,"ip_address":"192.168.0.114 10.42.0.3","status":"Downloading","download_progress":84,"os_version":"Resin OS 1.0.4 (fido)","supervisor_version":"1.6.0","update_pending":false} ``` Remotely via the API proxy: diff --git a/src/application.coffee b/src/application.coffee index 705bb537..a171cec2 100644 --- a/src/application.coffee +++ b/src/application.coffee @@ -578,11 +578,13 @@ application.update = update = (force) -> throw new Error(joinErrorMessages(failures)) if failures.length > 0 .then -> updateStatus.failed = 0 + device.setUpdatePending(false) # We cleanup here as we want a point when we have a consistent apps/images state, rather than potentially at a # point where we might clean up an image we still want. dockerUtils.cleanupContainersAndImages() .catch (err) -> updateStatus.failed++ + device.setUpdatePending(true) if updateStatus.state is UPDATE_REQUIRED console.log('Updating failed, but there is already another update scheduled immediately: ', err) return diff --git a/src/device.coffee b/src/device.coffee index b7c0089a..66800f48 100644 --- a/src/device.coffee +++ b/src/device.coffee @@ -128,17 +128,11 @@ exports.getDeviceType = do -> throw new Error('Device type not specified in config file') return configFromFile.deviceType -targetState = {} -exports.getState = -> - fieldsToOmit = ['api_secret', 'logs_channel', 'provisioning_progress', 'provisioning_state'] - return _.omit(targetState, fieldsToOmit) - -# Calling this function updates the local device state, which is then used to synchronise -# the remote device state, repeating any failed updates until successfully synchronised. -# This function will also optimise updates by merging multiple updates and only sending the latest state. -exports.updateState = do -> +do -> applyPromise = Promise.resolve() + targetState = {} actualState = {} + updatePending = false getStateDiff = -> _.omit targetState, (value, key) -> @@ -173,7 +167,19 @@ exports.updateState = do -> # Check if any more state diffs have appeared whilst we've been processing this update. applyState() - return (updatedState = {}, retry = false) -> + exports.setUpdatePending = (value) -> + updatePending = value + + exports.getState = -> + fieldsToOmit = ['api_secret', 'logs_channel', 'provisioning_progress', 'provisioning_state'] + state = _.omit(targetState, fieldsToOmit) + state.update_pending = updatePending + return state + + # Calling this function updates the local device state, which is then used to synchronise + # the remote device state, repeating any failed updates until successfully synchronised. + # This function will also optimise updates by merging multiple updates and only sending the latest state. + exports.updateState = (updatedState = {}, retry = false) -> # Remove any updates that match the last we successfully sent. _.merge(targetState, updatedState) From a4b08e389e468926679dbe3d5a0d50ed8305b706 Mon Sep 17 00:00:00 2001 From: Pablo Carranza Velez Date: Wed, 17 Feb 2016 16:37:09 +0000 Subject: [PATCH 3/4] Add more update-related fields to the GET /v1/device endpoint --- docs/API.md | 10 ++++++---- src/application.coffee | 7 +++++-- src/device.coffee | 8 ++++---- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/docs/API.md b/docs/API.md index 9b759dbe..009e04fc 100644 --- a/docs/API.md +++ b/docs/API.md @@ -411,15 +411,17 @@ $ curl -X POST --header "Content-Type:application/json" \ ### GET /v1/device Introduced in supervisor v1.6. -Returns the current device state, as reported to the Resin API. +Returns the current device state, as reported to the Resin API and with some extra fields added to allow control over pending/locked updates. The state is a JSON object that contains some or all of the following: * `api_port`: Port on which the supervisor is listening. * `ip_address`: Space-separated list of IP addresses of the device. * `status`: Status of the device regarding the app, as a string, i.e. "Stopping", "Starting", "Downloading", "Installing", "Idle". -* `download_progress`: Amount of the application image that has been downloaded, expressed as a percentage. +* `download_progress`: Amount of the application image that has been downloaded, expressed as a percentage. If the update has already been downloaded, this will be `null`. * `os_version`: Version of the host OS running on the device. * `supervisor_version`: Version of the supervisor running on the device. -* `update_pending`: This one is not reported to the Resin API. It's a boolean that will be true if the supervisor has tried to update the app, but failed (for example, if the update lock was set). +* `update_pending`: This one is not reported to the Resin API. It's a boolean that will be true if the supervisor has detected there is a pending update. +* `update_downloaded`: Not reported to the Resin API either. Boolean that will be true if a pending update has already been downloaded. +* `update_failed`: Not reported to the Resin API. Boolean that will be true if the supervisor has tried to apply a pending update but failed (i.e. if the app was locked, there was a network failure or anything else went wrong). Other attributes may be added in the future, and some may be missing or null if they haven't been set yet. @@ -431,7 +433,7 @@ $ curl -X GET --header "Content-Type:application/json" \ ``` Response: ```json -{"api_port":48484,"ip_address":"192.168.0.114 10.42.0.3","status":"Downloading","download_progress":84,"os_version":"Resin OS 1.0.4 (fido)","supervisor_version":"1.6.0","update_pending":false} +{"api_port":48484,"ip_address":"192.168.0.114 10.42.0.3","status":"Downloading","download_progress":84,"os_version":"Resin OS 1.0.4 (fido)","supervisor_version":"1.6.0","update_pending":true,"update_downloaded":false,"update_failed":false} ``` Remotely via the API proxy: diff --git a/src/application.coffee b/src/application.coffee index a171cec2..e3b56477 100644 --- a/src/application.coffee +++ b/src/application.coffee @@ -144,6 +144,7 @@ fetch = (app) -> .then -> logSystemEvent(logTypes.downloadAppSuccess, app) device.updateState(status: 'Idle', download_progress: null) + device.setUpdateState(update_downloaded: true) docker.getImage(app.imageId).inspectAsync() .catch (err) -> logSystemEvent(logTypes.downloadAppError, app, err) @@ -516,6 +517,8 @@ application.update = update = (force) -> resourcesForUpdate = compareForUpdate(localApps, remoteApps, localAppEnvs, remoteAppEnvs) { toBeRemoved, toBeDownloaded, toBeInstalled, toBeUpdated, appsWithChangedEnvs, allAppIds } = resourcesForUpdate + if !_.isEmpty(toBeRemoved) or !_.isEmpty(toBeInstalled) or !_.isEmpty(toBeUpdated) + device.setUpdateState(update_pending: true) # Run special functions against variables if remoteAppEnvs has the corresponding variable function mapping. Promise.map appsWithChangedEnvs, (appId) -> Promise.using lockUpdates(remoteApps[appId], force), -> @@ -578,13 +581,13 @@ application.update = update = (force) -> throw new Error(joinErrorMessages(failures)) if failures.length > 0 .then -> updateStatus.failed = 0 - device.setUpdatePending(false) + 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 # point where we might clean up an image we still want. dockerUtils.cleanupContainersAndImages() .catch (err) -> updateStatus.failed++ - device.setUpdatePending(true) + device.setUpdateState(update_failed: true) if updateStatus.state is UPDATE_REQUIRED console.log('Updating failed, but there is already another update scheduled immediately: ', err) return diff --git a/src/device.coffee b/src/device.coffee index 66800f48..dd59964f 100644 --- a/src/device.coffee +++ b/src/device.coffee @@ -132,7 +132,7 @@ do -> applyPromise = Promise.resolve() targetState = {} actualState = {} - updatePending = false + updateState = { update_pending: false, update_failed: false, update_downloaded: false } getStateDiff = -> _.omit targetState, (value, key) -> @@ -167,13 +167,13 @@ do -> # Check if any more state diffs have appeared whilst we've been processing this update. applyState() - exports.setUpdatePending = (value) -> - updatePending = value + exports.setUpdateState = (value) -> + _.merge(updateState, value) exports.getState = -> fieldsToOmit = ['api_secret', 'logs_channel', 'provisioning_progress', 'provisioning_state'] state = _.omit(targetState, fieldsToOmit) - state.update_pending = updatePending + _.merge(state, updateState) return state # Calling this function updates the local device state, which is then used to synchronise From d6dcaa3ac5bdf0edce7d045faf6547e1e8687437 Mon Sep 17 00:00:00 2001 From: Pablo Carranza Velez Date: Wed, 17 Feb 2016 22:16:49 +0000 Subject: [PATCH 4/4] Document that commit is also present in the device state endpoint --- docs/API.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/API.md b/docs/API.md index 009e04fc..d4302702 100644 --- a/docs/API.md +++ b/docs/API.md @@ -414,6 +414,7 @@ Introduced in supervisor v1.6. Returns the current device state, as reported to the Resin API and with some extra fields added to allow control over pending/locked updates. The state is a JSON object that contains some or all of the following: * `api_port`: Port on which the supervisor is listening. +* `commit`: Hash of the current commit of the application that is running. * `ip_address`: Space-separated list of IP addresses of the device. * `status`: Status of the device regarding the app, as a string, i.e. "Stopping", "Starting", "Downloading", "Installing", "Idle". * `download_progress`: Amount of the application image that has been downloaded, expressed as a percentage. If the update has already been downloaded, this will be `null`. @@ -433,7 +434,7 @@ $ curl -X GET --header "Content-Type:application/json" \ ``` Response: ```json -{"api_port":48484,"ip_address":"192.168.0.114 10.42.0.3","status":"Downloading","download_progress":84,"os_version":"Resin OS 1.0.4 (fido)","supervisor_version":"1.6.0","update_pending":true,"update_downloaded":false,"update_failed":false} +{"api_port":48484,"ip_address":"192.168.0.114 10.42.0.3","commit":"414e65cd378a69a96f403b75f14b40b55856f860","status":"Downloading","download_progress":84,"os_version":"Resin OS 1.0.4 (fido)","supervisor_version":"1.6.0","update_pending":true,"update_downloaded":false,"update_failed":false} ``` Remotely via the API proxy: