Client

Differences between revisions 10 and 11
Revision 10 as of 2013-08-14 16:17:36
Size: 6300
Editor: mail
Comment:
Revision 11 as of 2013-08-14 19:40:04
Size: 9260
Editor: mail
Comment:
Deletions are marked like this. Additions are marked like this.
Line 40: Line 40:
=== Signals ===

'''!UpdateAvailableStatus(is_available, downloading, version, size, descriptions)'''<<BR>>
Sent in response to '''!CheckUpdateStatus()''' async calls, this signal provides information about the state of the update.
 * ''is_available'' - boolean which indicates whether an update is available or not. This will be false if the device's build number is equal to or greater than any candidate build on the server (IOW, there is no candidate available). This flag will be true when there is an update available.
 * ''downloading'' - boolean indicating whether a download is in progress. This doesn't include any preliminary downloads needed to determine whether a candidate is available or not (e.g. keyrings, blacklists, {{{channels.json}}}, and {{{index.json}}} files). This flag will be false if a download is paused.
 * ''version'' - integer specifying the candidate build number we plan to update to.
 * ''size'' - integer providing total size in bytes for the upgrade. This does not include any preliminary files needed to determine whether an update is available or not.
 * ''descriptions'' - array of mappings containing the descriptions for all updates that will be downloaded. Each element of the array is a mapping of language-specific description keys to the UTF-8 description for that language. See [[../Server|the server documentation]] for details.
Depending on the state of the system, as described below, some of the arguments of this signal may be ignored.

'''!UpdateProgress(percentage, eta)'''<<BR>>
Sent periodically, while a download is in progress. This signal is not sent when an upgrade is paused.
 * ''percentage'' - integer between 0 and 100 indicating how much of the download (not including preliminary files) have been currently downloaded.
 * ''eta'' - estimated time remaining to complete the download, in integer seconds

'''!UpdateDownloaded()'''<<BR>>
Sent when the currently in progress update has been completely and successfully downloaded. When this signal is received, it means that the device is ready to have the update applied (typically by issuing a system reboot).

'''!UpdateFailed(consecutive_failure_count, last_reason)'''<<BR>>
Sent when the update failed for any reason (including cancellation). The client will remain in the failure state until a '''!CheckUpdateStatus(0)''' or '''!CheckUpdateStatus(2)''' call is made.
 * ''consecutive_failure_count'' - an integer specifying the number of times in a row that a '''!CheckUpdateStatus()''' has resulted in an update failure. This increments until an update completes successfully (i.e. until the next '''!UpdateDownloaded()''' signal is issued).
 * ''last_reason'' - a string containing the English reason for why this updated failed.

=== Methods ===

All methods are asynchronous, thus never return any meaningful value. The response to any method is containing in subsequent signals that are issued, as described above.
Line 41: Line 69:
Asynchronous call to instruct the client to check for an available update. '''reset_level''' indicates how the client should check for updates:
 * 0 means ''Hard reset''
   * If an update is in progress or paused, it will be canceled. In all cases, any previous state will be reset, and a new update will begin. An '''!UpdateAvailableStatus(false, true, "", 0, 0, {})''' signal will be issued (arguments mean: no update available yet, an update is downloading, with the last 4 arguments to be ignored).
 * 1 means ''Keep current state''
   * If the previous update succeeded, a new update will be started and an '''!UpdateAvailableStatus(false, true, "", 0, 0, {})''' signal will be issued (arguments mean: no update available yet, an update is downloading, with the last 4 arguments to be ignored).
   * If an update/download is already in progress, it will continue and an '''!UpdateAvailableStatus(false, true, "", 0, 0, {})''' signal will be issued (arguments mean: no update available yet, an update is downloading, with the last 4 arguments to be ignored).
   * If a download is currently paused, it will resume if possible. An '''!UpdateAvailableStatus(false, downloading, "", 0, 0, {})''' signal will be issued (arguments mean: no update available yet, a flag indicating whether the paused download has been resumed, and the last 4 arguments to be ignored).
   * If a previous update failed, state will ''not'' be reset and no new update will be initiated. An '''!UpdateAvailableStatus(false, false, "Download failed", 0, 0, {})''' signal will be issued (arguments mean: no update available, not downloading, a human readable error message, with the last 3 arguments to be ignored). After that, an '''!UpdateFailed(buildno, failure_count, "Reason")''' signal will be issued (arguments mean: the build number that the last update attempted, a consecutive build failure count, and a human readable failure reason).
 * 2 means ''Restart if in failure state''
   * If the previous update succeeded, a new update will be started, and an '''!UpdateAvailableStatus(false, true, "", 0, 0, {})''' signal will be issued (arguments mean: no update available yet, an update is downloading, with the last 4 arguments to be ignored).
   * If an update/download is already in progress, it will continue, and an '''!UpdateAvailableStatus(false, true, "", 0, 0, {})''' signal will be issued (arguments mean: no update available yet, an update is downloading, with the last 4 arguments to be ignored).
   * If a download is currently paused, it will resume if possible. An '''!UpdateAvailableStatus(false, downloading, "", 0, 0, {})''' signal will be issued (arguments mean: no update available yet, a flag indicating whether the paused download has been resumed, and the last 4 arguments to be ignored).
   * If a previous update failed, reset state and begin a new update. An '''!UpdateAvailableStatus(false, true, "", 0, 0, {})''' signal will be issued (arguments mean: no update available yet, an update is downloading, with the last 4 arguments to be ignored).
Note that '''!UpdateAvailableStatus''' signal is only sent once an update candidate is determined to be available. This may involve downloading some preliminary files from the server (e.g. new keyrings, blacklists, {{{channels.json}}} and {{{index.json}}} files, etc.). If there is no update candidate available no '''!UpdateAvailableStatus''' signal is sent. '''''XXX Should a signal be sent in this case? If so, what are the values of the arguments? It could be false/false meaning no update is available and we're not downloading, but then what about the following arguments? They should probably be ignored.'''''
Instruct the client to check for an available update. If the client is in ''auto-download'' mode (see below), then this call will both check the server for an available update, and initiate the download of that update. If the client is in ''manual-download'' mode, then any available update will ''not'' occur automatically. It is also possible for the download to only occur automatically if other criteria are met, e.g. we are not on a wifi, or the device has a low battery.
 * ''reset_level'' indicates how the client should check for updates:
   * 0 means ''Hard reset''
     * If an update is in progress or paused, it will be canceled. In all cases, any previous state will be reset, and a new update check will be performed.
   * 1 means ''Keep current state''
     * If the previous update succeeded, a new update check will be started.
     * If an update check or download is already in progress, it will continue.
     * If a download is currently paused, it will resume if possible.
     * If a previous update check or download failed, state will ''not'' be reset and no new update check will be initiated.
  * 2 means ''Restart if in failure state''
    * If the previous update succeeded, a new update check will be started.
    * If an update check or download is already in progress, it will continue.
    * If a download is currently paused, it will resume if possible.
    * If a previous update check or download failed, reset state and begin a new update.
In all cases, an '''!UpdateAvailableStatus''' signal will be issued once an update candidate is determined to be available. If the client is in ''auto-download'' mode, then the download will also be started, leading to an '''!UpdateDownloaded''' or '''!UpdateFailed''' signal, as described above. When the '''!UpdateAvailableStatus''' signal is issued, the arguments can have the following values:
 * '''!UpdateAvailableStatus(true, true, build_number, size, descriptions)''' - This means that an update is available and is currently downloading. The build number of the candidate update is given, as is its total size in bytes, and the descriptions of the updates in all available languages.
 * '''!UpdateAvailableStatus(true, false, build_number, size, descriptions)''' - This means that an update is available, but it is not yet downloading, possibly because the client is in manual-update mode, or because the download is currently paused. The build number, size, and descriptions are given as above.
 * '''!UpdateAvailableStatus(false, ?, ?, ?, ?)''' - There is no update available, and the last four arguments should be ignored.

'''!ApplyUpdate()'''<<BR>>

'''!CancelUpdate()'''<<BR>>

'''!PauseDownload()'''<<BR>>

'''!ResumeDownload()'''<<BR>>

'''!SetSetting(key, value)'''<<BR>>

'''!GetSetting(key)'''<<BR>>

=== Notes ===

 * I decided not to include the ''version'' argument for '''!ApplyUpdate()''' and '''!CancelUpdate()'''. I understand why these were requested, but I'm calling YAGNI on it, at least for now. It complicates the API and until we work out the semantics for updating to anything other than the highest build number available, I want to keep things simple. It's easier to add things to an API than remove them. Similarly on the '''!UpdateProgress''', '''UpdateDownloaded''', and '''UpdateFailed''' signals, although not on the '''!UpdateAvailableStatus''' signal since the version there would indicate what update candidate we're going to update to.
 * I changed the values for ''reset_level''. It made more sense to me that level 0 was a hard reset.
 * I changed the order of some of the arguments to methods and signals.

Client side

Introduction

The client tool is used to calculate the update, download the files, validate them and then set the needed flags for the upgrader to pick them up and apply.

Requirements

  • Secure download of the indexes (HTTPS + GPG)
  • Support for everything described in the GPG spec (revocation list, multiple keyrings, ...)

  • Resolution of the best upgrade path based on different policies (total download size, least number of reboots, ...)
  • Download and validation of the files
  • Flexible implementation to allow different upgrader setups with minimal changes required
  • Support for suspend/resume of downloads

Nice to have

  • Bandwidth limiting is a nice to have and might be tricky to implement.

Implementation

The current implementation is a command line tool, however it's expected to be turned into a DBus service that the Touch UI can drive.

Step-by-step example for an update

Whenever called the client does the following:

  1. Grab https://server/channels.json and lookup the index for the current channel. If present, also grab the device GPG keyring.

  2. Grab https://server/<channel>/<model>/index.json

  3. Read the current version number of the device (ubuntu-build file)
  4. Look for the most recent version available
  5. Resolve an upgrade path to it, minimizing download size and number of reboots
  6. Download any file needed up until the next reboot
  7. Validate all the files
  8. Write them to the cache partition
  9. Write the list of updates for the upgrader to use
  10. Reboot into the upgrader

Those steps don't include all of the specific GPG validation bits required to ensure the authenticity of all files. Those are detailed in the separate GPG wiki page.

Security (e.g. what to download over https/http) is outlined in the server security section.

DBus API

The client will export a DBus API on the system bus which will allow for a u/i in the System Settings to query, begin, cancel, and apply a system update. This service starts via DBus activation and exits automatically after a configurable amount of time (i.e. it does not run forever). It maintains state such that the client can exit and get restarted to continue the update, even across reboots. The client uses the download service to manage all file downloads. Here is the DBus API specification:

Signals

UpdateAvailableStatus(is_available, downloading, version, size, descriptions)
Sent in response to CheckUpdateStatus() async calls, this signal provides information about the state of the update.

  • is_available - boolean which indicates whether an update is available or not. This will be false if the device's build number is equal to or greater than any candidate build on the server (IOW, there is no candidate available). This flag will be true when there is an update available.

  • downloading - boolean indicating whether a download is in progress. This doesn't include any preliminary downloads needed to determine whether a candidate is available or not (e.g. keyrings, blacklists, channels.json, and index.json files). This flag will be false if a download is paused.

  • version - integer specifying the candidate build number we plan to update to.

  • size - integer providing total size in bytes for the upgrade. This does not include any preliminary files needed to determine whether an update is available or not.

  • descriptions - array of mappings containing the descriptions for all updates that will be downloaded. Each element of the array is a mapping of language-specific description keys to the UTF-8 description for that language. See the server documentation for details.

Depending on the state of the system, as described below, some of the arguments of this signal may be ignored.

UpdateProgress(percentage, eta)
Sent periodically, while a download is in progress. This signal is not sent when an upgrade is paused.

  • percentage - integer between 0 and 100 indicating how much of the download (not including preliminary files) have been currently downloaded.

  • eta - estimated time remaining to complete the download, in integer seconds

UpdateDownloaded()
Sent when the currently in progress update has been completely and successfully downloaded. When this signal is received, it means that the device is ready to have the update applied (typically by issuing a system reboot).

UpdateFailed(consecutive_failure_count, last_reason)
Sent when the update failed for any reason (including cancellation). The client will remain in the failure state until a CheckUpdateStatus(0) or CheckUpdateStatus(2) call is made.

  • consecutive_failure_count - an integer specifying the number of times in a row that a CheckUpdateStatus() has resulted in an update failure. This increments until an update completes successfully (i.e. until the next UpdateDownloaded() signal is issued).

  • last_reason - a string containing the English reason for why this updated failed.

Methods

All methods are asynchronous, thus never return any meaningful value. The response to any method is containing in subsequent signals that are issued, as described above.

CheckUpdateStatus(reset_level)
Instruct the client to check for an available update. If the client is in auto-download mode (see below), then this call will both check the server for an available update, and initiate the download of that update. If the client is in manual-download mode, then any available update will not occur automatically. It is also possible for the download to only occur automatically if other criteria are met, e.g. we are not on a wifi, or the device has a low battery.

  • reset_level indicates how the client should check for updates:

    • 0 means Hard reset

      • If an update is in progress or paused, it will be canceled. In all cases, any previous state will be reset, and a new update check will be performed.
    • 1 means Keep current state

      • If the previous update succeeded, a new update check will be started.
      • If an update check or download is already in progress, it will continue.
      • If a download is currently paused, it will resume if possible.
      • If a previous update check or download failed, state will not be reset and no new update check will be initiated.

    • 2 means Restart if in failure state

      • If the previous update succeeded, a new update check will be started.
      • If an update check or download is already in progress, it will continue.
      • If a download is currently paused, it will resume if possible.
      • If a previous update check or download failed, reset state and begin a new update.

In all cases, an UpdateAvailableStatus signal will be issued once an update candidate is determined to be available. If the client is in auto-download mode, then the download will also be started, leading to an UpdateDownloaded or UpdateFailed signal, as described above. When the UpdateAvailableStatus signal is issued, the arguments can have the following values:

  • UpdateAvailableStatus(true, true, build_number, size, descriptions) - This means that an update is available and is currently downloading. The build number of the candidate update is given, as is its total size in bytes, and the descriptions of the updates in all available languages.

  • UpdateAvailableStatus(true, false, build_number, size, descriptions) - This means that an update is available, but it is not yet downloading, possibly because the client is in manual-update mode, or because the download is currently paused. The build number, size, and descriptions are given as above.

  • UpdateAvailableStatus(false, ?, ?, ?, ?) - There is no update available, and the last four arguments should be ignored.

ApplyUpdate()

CancelUpdate()

PauseDownload()

ResumeDownload()

SetSetting(key, value)

GetSetting(key)

Notes

  • I decided not to include the version argument for ApplyUpdate() and CancelUpdate(). I understand why these were requested, but I'm calling YAGNI on it, at least for now. It complicates the API and until we work out the semantics for updating to anything other than the highest build number available, I want to keep things simple. It's easier to add things to an API than remove them. Similarly on the UpdateProgress, UpdateDownloaded, and UpdateFailed signals, although not on the UpdateAvailableStatus signal since the version there would indicate what update candidate we're going to update to.

  • I changed the values for reset_level. It made more sense to me that level 0 was a hard reset.

  • I changed the order of some of the arguments to methods and signals.

Code

ImageBasedUpgrades/Client (last edited 2016-10-31 14:09:03 by localhost)