def main(self):
        # Continue looping while the service is running.
        while not self._terminate_flag:
            # This blocks on changes to the watched key in etcd.
            _log.debug("Waiting for change from etcd for key {}".format(
                         self._plugin.key()))
            old_value = self._last_value
            value = self.update_from_etcd()
            if self._terminate_flag:
                break

            if value and value != old_value:
                _log.info("Got new config value from etcd - filename {}, file size {}, SHA512 hash {}".format(
                    self._plugin.file(),
                    len(value),
                    sha512(utils.safely_encode(value)).hexdigest()))
                _log.debug("Got new config value from etcd:\n{}".format(
                           utils.safely_encode(value)))
                self._plugin.on_config_changed(value, self._alarm)
                FILE_CHANGED.log(filename=self._plugin.file())
Exemple #2
0
 def test_safely_encode(self):
     self.assertEquals(safely_encode(None), None)
     self.assertEquals(safely_encode(u'ASCII'), 'ASCII')
     self.assertEquals(safely_encode(u'\x80nonASCII'), '\xc2\x80nonASCII')
Exemple #3
0
 def test_safely_encode(self):
     self.assertEquals(safely_encode(None), None)
     self.assertEquals(safely_encode(u'ASCII'), 'ASCII')
     self.assertEquals(safely_encode(u'\x80nonASCII'), '\xc2\x80nonASCII')
    def read_from_etcd(self, wait=True):
        result = None
        wait_index = None

        try:
            result = self._client.read(self.key(), quorum=True)
            wait_index = result.etcd_index + 1

            if wait:
                # If the cluster view hasn't changed since we last saw it, then
                # wait for it to change before doing anything else.
                _log.info("Read value {} from etcd, "
                          "comparing to last value {}".format(
                              utils.safely_encode(result.value),
                              utils.safely_encode(self._last_value)))

                if result.value == self._last_value:
                    _log.info("Watching for changes with {}".format(wait_index))

                    while not self._terminate_flag and not self._abort_read and self.is_running():
                        _log.debug("Started a new watch")
                        try:
                            result = self._client.read(self.key(),
                                                       timeout=self.TIMEOUT_ON_WATCH,
                                                       waitIndex=wait_index,
                                                       wait=True,
                                                       recursive=False)
                            break
                        except etcd.EtcdException as e:
                            if "Read timed out" in e.message:
                                # Timeouts after TIMEOUT_ON_WATCH seconds are expected, so
                                # ignore them - unless we're terminating, we'll
                                # stay in the while loop and try again
                                pass
                            else:
                                raise

                    _log.debug("Finished watching")

                    # Return if we're terminating.
                    if self._terminate_flag:
                        return self.tuple_from_result(result)

        except etcd.EtcdKeyError:
            _log.info("Key {} doesn't exist in etcd yet".format(self.key()))
            # Use any value on disk first, but the default value if not found
            try:
                f = open(self._plugin.file(), 'r')
                value = f.read()  # pragma: no cover
            except:
                value = self.default_value()

            # Attempt to create new key in etcd.
            try:
                # The prevExist set to False will fail the write if it finds a
                # key already in etcd. This stops us overwriting a manually
                # uploaded file with the default template.
                self._client.write(self.key(), value, prevExist=False)
                return (value, None)
            except:  # pragma: no cover
                _log.debug("Failed to create new key in the etcd store")
                # Sleep briefly to avoid hammering a non-existent key
                sleep(self.PAUSE_BEFORE_RETRY_ON_MISSING_KEY)
                # Return 'None' so that plugins do not write config to disk
                # that does not exist in the etcd store, leaving us out of sync
                return (None, None)

        except Exception as e:
            # Catch-all error handler (for invalid requests, timeouts, etc -
            # start over.
            _log.error("{} caught {!r} when trying to read with index {}"
                       " - pause before retry".
                       format(self._ip, e, wait_index))
            # Sleep briefly to avoid hammering a failed server
            self.pause()
            # The main loop (which reads from etcd in a loop) should call this
            # function again after we return, causing the read to be retried.

        return self.tuple_from_result(result)
    def read_from_etcd(self, wait=True):
        result = None
        wait_index = None

        try:
            result = self._client.read(self.key(), quorum=True)
            wait_index = result.etcd_index + 1

            if wait:
                # If the cluster view hasn't changed since we last saw it, then
                # wait for it to change before doing anything else.
                _log.info("Read value {} from etcd, "
                          "comparing to last value {}".format(
                              utils.safely_encode(result.value),
                              utils.safely_encode(self._last_value)))

                if result.value == self._last_value:
                    _log.info(
                        "Watching for changes with {}".format(wait_index))

                    while not self._terminate_flag and not self._abort_read and self.is_running(
                    ):
                        _log.debug("Started a new watch")
                        try:
                            result = self._client.read(
                                self.key(),
                                timeout=self.TIMEOUT_ON_WATCH,
                                waitIndex=wait_index,
                                wait=True,
                                recursive=False)
                            break
                        except etcd.EtcdException as e:
                            if "Read timed out" in e.message:
                                # Timeouts after TIMEOUT_ON_WATCH seconds are expected, so
                                # ignore them - unless we're terminating, we'll
                                # stay in the while loop and try again
                                pass
                            else:
                                raise

                    _log.debug("Finished watching")

                    # Return if we're terminating.
                    if self._terminate_flag:
                        return self.tuple_from_result(result)

        except etcd.EtcdKeyError:
            _log.info("Key {} doesn't exist in etcd yet".format(self.key()))
            # Use any value on disk first, but the default value if not found
            try:
                f = open(self._plugin.file(), 'r')
                value = f.read()  # pragma: no cover
            except:
                value = self.default_value()

            # Attempt to create new key in etcd.
            try:
                # The prevExist set to False will fail the write if it finds a
                # key already in etcd. This stops us overwriting a manually
                # uploaded file with the default template.
                self._client.write(self.key(), value, prevExist=False)
                return (value, None)
            except:  # pragma: no cover
                _log.debug("Failed to create new key in the etcd store")
                # Sleep briefly to avoid hammering a non-existent key
                sleep(self.PAUSE_BEFORE_RETRY_ON_MISSING_KEY)
                # Return 'None' so that plugins do not write config to disk
                # that does not exist in the etcd store, leaving us out of sync
                return (None, None)

        except Exception as e:
            # Catch-all error handler (for invalid requests, timeouts, etc -
            # start over.
            _log.error("{} caught {!r} when trying to read with index {}"
                       " - pause before retry".format(self._ip, e, wait_index))
            # Sleep briefly to avoid hammering a failed server
            self.pause()
            # The main loop (which reads from etcd in a loop) should call this
            # function again after we return, causing the read to be retried.

        return self.tuple_from_result(result)