예제 #1
0
 def test_update(self):
     # Bags can be updated, similar to dicts.
     bag = Bag(a=1, b=2, c=3)
     bag.update(b=7, d=9)
     self.assertEqual(bag.a, 1)
     self.assertEqual(bag.b, 7)
     self.assertEqual(bag.c, 3)
     self.assertEqual(bag.d, 9)
예제 #2
0
 def test_update_converter_overrides(self):
     # Converters in the update method permanently override ctor converters.
     converters = dict(a=int, b=int)
     bag = Bag(converters=converters, a='1', b='2')
     self.assertEqual(bag.a, 1)
     self.assertEqual(bag.b, 2)
     new_converters = dict(a=str)
     bag.update(converters=new_converters, a='3', b='4')
     self.assertEqual(bag.a, '3')
     self.assertEqual(bag.b, 4)
     bag.update(a='5', b='6')
     self.assertEqual(bag.a, '5')
     self.assertEqual(bag.b, 6)
예제 #3
0
 def test_update_converters(self):
     # The update method also accepts converters.
     bag = Bag(a=1, b=2, c=3)
     bag.update(converters=dict(d=int), d='4', e='5')
     self.assertEqual(bag.d, 4)
     self.assertEqual(bag.e, '5')
예제 #4
0
class Configuration:
    def __init__(self, directory=None):
        self._set_defaults()
        # Because the configuration object is a global singleton, it makes for
        # a convenient place to stash information used by widely separate
        # components.  For example, this is a placeholder for rendezvous
        # between the downloader and the D-Bus service.  When running under
        # D-Bus and we get a `paused` signal from the download manager, we need
        # this to plumb through an UpdatePaused signal to our clients.  It
        # rather sucks that we need a global for this, but I can't get the
        # plumbing to work otherwise.  This seems like the least horrible place
        # to stash this global.
        self.dbus_service = None
        # These are used to plumb command line arguments from the main() to
        # other parts of the system.
        self.skip_gpg_verification = False
        self.override_gsm = False
        # Cache.
        self._device = None
        self._build_number = None
        self.build_number_override = False
        self._channel = None
        # This is used only to override the phased percentage via command line
        # and the property setter.
        self._phase_override = None
        self._tempdir = None
        self.config_d = None
        self.ini_files = []
        self.http_base = None
        self.https_base = None
        if directory is not None:
            self.load(directory)
        self._calculate_http_bases()
        self._resources = ExitStack()
        atexit.register(self._resources.close)

    def _set_defaults(self):
        self.service = Bag(
            base='system-image.ubports.com',
            http_port=80,
            https_port=443,
            channel='daily',
            build_number=0,
        )
        self.system = Bag(
            timeout=as_timedelta('1h'),
            tempdir='/tmp',
            logfile='/var/log/system-image/client.log',
            loglevel=as_loglevel('info'),
            settings_db='/var/lib/system-image/settings.db',
        )
        self.gpg = Bag(
            archive_master='/usr/share/system-image/archive-master.tar.xz',
            image_master='/var/lib/system-image/keyrings/image-master.tar.xz',
            image_signing='/var/lib/system-image/keyrings/image-signing.tar.xz',
            device_signing=
            '/var/lib/system-image/keyrings/device-signing.tar.xz',
        )
        self.updater = Bag(
            cache_partition='/android/cache/recovery',
            data_partition='/var/lib/system-image',
        )
        self.hooks = Bag(
            device=as_object('systemimage.device.SystemProperty'),
            scorer=as_object('systemimage.scores.WeightedScorer'),
            apply=as_object('systemimage.apply.Reboot'),
        )
        self.dbus = Bag(lifetime=as_timedelta('10m'), )

    def _load_file(self, path):
        parser = SafeConfigParser()
        str_path = str(path)
        parser.read(str_path)
        self.ini_files.append(path)
        self.service.update(converters=dict(
            http_port=as_port,
            https_port=as_port,
            build_number=int,
            device=as_stripped,
        ),
                            **parser['service'])
        self.system.update(converters=dict(timeout=as_timedelta,
                                           loglevel=as_loglevel,
                                           settings_db=expand_path,
                                           tempdir=expand_path),
                           **parser['system'])
        self.gpg.update(**parser['gpg'])
        self.updater.update(**parser['updater'])
        self.hooks.update(converters=dict(device=as_object,
                                          scorer=as_object,
                                          apply=as_object),
                          **parser['hooks'])
        self.dbus.update(converters=dict(lifetime=as_timedelta),
                         **parser['dbus'])

    def load(self, directory):
        """Load up the configuration from a config.d directory."""
        # Look for all the files in the given directory with .ini or .cfg
        # suffixes.  The files must start with a number, and the files are
        # loaded in numeric order.
        if self.config_d is not None:
            raise RuntimeError('Configuration already loaded; use .reload()')
        self.config_d = directory
        if not Path(directory).is_dir():
            raise TypeError(
                '.load() requires a directory: {}'.format(directory))
        candidates = []
        for child in Path(directory).glob('*.ini'):
            order, _, base = child.stem.partition('_')
            # XXX 2014-10-03: The logging system isn't initialized when we get
            # here, so we can't log that these files are being ignored.
            if len(_) == 0:
                continue
            try:
                serial = int(order)
            except ValueError:
                continue
            candidates.append((serial, child))
        for serial, path in sorted(candidates):
            self._load_file(path)
        self._calculate_http_bases()

    def reload(self):
        """Reload the configuration directory."""
        # Reset some cached attributes.
        directory = self.config_d
        self.ini_files = []
        self.config_d = None
        self._build_number = None
        # Now load the defaults, then reload the previous config.d directory.
        self._set_defaults()
        self.load(directory)

    def _calculate_http_bases(self):
        if (self.service.http_port is NO_PORT
                and self.service.https_port is NO_PORT):
            raise ValueError('Cannot disable both http and https ports')
        # Construct the HTTP and HTTPS base urls, which most applications will
        # actually use.  We do this in two steps, in order to support disabling
        # one or the other (but not both) protocols.
        if self.service.http_port == 80:
            http_base = 'http://{}'.format(self.service.base)
        elif self.service.http_port is NO_PORT:
            http_base = None
        else:
            http_base = 'http://{}:{}'.format(self.service.base,
                                              self.service.http_port)
        # HTTPS.
        if self.service.https_port == 443:
            https_base = 'https://{}'.format(self.service.base)
        elif self.service.https_port is NO_PORT:
            https_base = None
        else:
            https_base = 'https://{}:{}'.format(self.service.base,
                                                self.service.https_port)
        # Sanity check and final settings.
        if http_base is None:
            assert https_base is not None
            http_base = https_base
        if https_base is None:
            assert http_base is not None
            https_base = http_base
        self.http_base = http_base
        self.https_base = https_base

    @property
    def build_number(self):
        if self._build_number is None:
            self._build_number = self.service.build_number
        return self._build_number

    @build_number.setter
    def build_number(self, value):
        if not isinstance(value, int):
            raise ValueError('integer is required, got: {}'.format(
                type(value).__name__))
        self._build_number = value
        self.build_number_override = True

    @build_number.deleter
    def build_number(self):
        self._build_number = None

    @property
    def device(self):
        if self._device is None:
            # Start by looking for a [service]device setting.  Use this if it
            # exists, otherwise fall back to calling the hook.
            self._device = getattr(self.service, 'device', None)
            if not self._device:
                self._device = self.hooks.device().get_device()
        return self._device

    @device.setter
    def device(self, value):
        self._device = value

    @property
    def channel(self):
        if self._channel is None:
            self._channel = self.service.channel
        return self._channel

    @channel.setter
    def channel(self, value):
        self._channel = value

    @property
    def phase_override(self):
        return self._phase_override

    @phase_override.setter
    def phase_override(self, value):
        self._phase_override = max(0, min(100, int(value)))

    @phase_override.deleter
    def phase_override(self):
        self._phase_override = None

    @property
    def tempdir(self):
        if self._tempdir is None:
            makedirs(self.system.tempdir)
            self._tempdir = self._resources.enter_context(
                temporary_directory(prefix='system-image-',
                                    dir=self.system.tempdir))
        return self._tempdir

    @property
    def user_agent(self):
        return USER_AGENT.format(self)