Support for phased updates

Bug #1231628 reported by Stéphane Graber
6
This bug affects 1 person
Affects Status Importance Assigned to Milestone
Ubuntu system image
Fix Released
Critical
Barry Warsaw
system-image (Ubuntu)
Fix Released
Undecided
Unassigned

Bug Description

In order to progressively deploy an image to our users and to be able to block an update in case of an issue, we need the client to support the phased-percentage field.

This field is part of an image definition in index.json and is an integer between 0 and 100.
The client needs to hash the version number with something unique to the device (MAC, EMEI, ...) and use that to seed a random number generator and then get a value between 1 and 100. If that value is higher than phased-percentage the entry needs to be ignored in the upgrade path resolution.

Only the latest version number can have phased-percentage set but the client doesn't need to care about this, the server will enforce it on its side.
All the client needs to do is check phased-percentage against its own random value and drop them from the path resolution if the local value is higher than the one in the index (same algorithm as phased package updates in the archive).

In the case an image is known to be bad, it'll have its phased-percentage value set to "0" so no client will attempt to use it. When an image is ready for general consumption, it'll have phased-percentage set to "100" or simply not set at all (same result).

Tags: client
tags: added: client
Barry Warsaw (barry)
Changed in ubuntu-system-image:
importance: Undecided → Critical
Changed in ubuntu-system-image:
status: New → Triaged
Revision history for this message
Stéphane Graber (stgraber) wrote :

The relevant function in update-manager appears to be:
    def _is_ignored_phased_update(self, pkg):
        """ This will test if the pkg is a phased update and if
            it needs to get installed or ignored.

            :return: True if the updates should be ignored
        """
        # allow the admin to override this
        if apt.apt_pkg.config.find_b(
                self.ALWAYS_INCLUDE_PHASED_UPDATES, False):
            return False

        if self.PHASED_UPDATES_KEY in pkg.candidate.record:
            if apt.apt_pkg.config.find_b(
                    self.NEVER_INCLUDE_PHASED_UPDATES, False):
                logging.info("holding back phased update per configuration")
                return True

            # its important that we always get the same result on
            # multiple runs of the update-manager, so we need to
            # feed a seed that is a combination of the pkg/ver/machine
            self.random.seed("%s-%s-%s" % (
                pkg.candidate.source_name, pkg.candidate.version,
                self.machine_uniq_id))
            threshold = pkg.candidate.record[self.PHASED_UPDATES_KEY]
            percentage = self.random.randint(0, 100)
            if percentage > int(threshold):
                logging.info("holding back phased update (%s < %s)" % (
                    threshold, percentage))
                return True
        return False

The unique id comes from:
        UNIQ_MACHINE_ID_FILE = "/var/lib/dbus/machine-id"
        with open(self.UNIQ_MACHINE_ID_FILE) as f:
            self.machine_uniq_id = f.read()

Note that we have that file on Ubuntu Touch too and it's writable, persistent and appaears to be indeed unique, so I'd recommend we just use that.

Barry Warsaw (barry)
Changed in ubuntu-system-image:
milestone: none → 1.7
Barry Warsaw (barry)
Changed in ubuntu-system-image:
milestone: 1.7 → 1.8
Barry Warsaw (barry)
Changed in ubuntu-system-image:
assignee: nobody → Barry Warsaw (barry)
Barry Warsaw (barry)
Changed in ubuntu-system-image:
status: Triaged → In Progress
Revision history for this message
Barry Warsaw (barry) wrote :

Implementation notes:

* The calculated phased percentage will be the same for the entire life of the system-image-{cli,dbus} process. I.e. it won't be different for each image comparison we see in the index.json file.

* We'll read /var/lib/dbus/machine-id as bytes (including the trailing newline) as the random seed. This means it would be the same for every invocation of system-image. If we want it to be different each time (and we indeed probably do), then we need to hash the contents of that file, and add in something like the current system time in float seconds.

Revision history for this message
Barry Warsaw (barry) wrote :

E.g.:

import time
import random

with open('/var/lib/dbus/machine-id', 'rb') as fp:
    data = fp.read()

now = str(time.time()).encode('us-ascii')
seed = data + now

r = random.Random()
r.seed(seed)
for i in range(10):
    print(r.randint(0, 100))

Barry Warsaw (barry)
Changed in ubuntu-system-image:
status: In Progress → Fix Committed
Barry Warsaw (barry)
Changed in ubuntu-system-image:
status: Fix Committed → Fix Released
Barry Warsaw (barry)
Changed in system-image (Ubuntu):
status: New → Fix Released
To post a comment you must log in.
This report contains Public information  
Everyone can see this information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.