Esempio n. 1
0
 def read(self,
          key,
          recursive=False,
          wait=False,
          timeout=None,
          waitIndex=None):
     try:
         if waitIndex:
             result = self.client.read(
                 key,
                 recursive=recursive,
                 wait=wait,
                 timeout=timeout,
                 waitIndex=waitIndex,
             )
         else:
             result = self.client.read(key,
                                       recursive=recursive,
                                       wait=wait,
                                       timeout=timeout)
     except (etcd.EtcdConnectionFailed, etcd.EtcdKeyNotFound) as err:
         log.error("etcd: %s", err)
         raise
     except ReadTimeoutError:
         # For some reason, we have to catch this directly.  It falls through
         # from python-etcd because it's trying to catch
         # urllib3.exceptions.ReadTimeoutError and strangely, doesn't catch.
         # This can occur from a watch timeout that expires, so it may be 'expected'
         # behavior. See issue #28553
         if wait:
             # Wait timeouts will throw ReadTimeoutError, which isn't bad
             log.debug("etcd: Timed out while executing a wait")
             raise EtcdUtilWatchTimeout(
                 "Watch on {0} timed out".format(key))
         log.error("etcd: Timed out")
         raise etcd.EtcdConnectionFailed("Connection failed")
     except MaxRetryError as err:
         # Same issue as ReadTimeoutError.  When it 'works', python-etcd
         # throws EtcdConnectionFailed, so we'll do that for it.
         log.error("etcd: Could not connect")
         raise etcd.EtcdConnectionFailed("Could not connect to etcd server")
     except etcd.EtcdException as err:
         # EtcdValueError inherits from ValueError, so we don't want to accidentally
         # catch this below on ValueError and give a bogus error message
         log.error("etcd: %s", err)
         raise
     except ValueError:
         # python-etcd doesn't fully support python 2.6 and ends up throwing this for *any* exception because
         # it uses the newer {} format syntax
         log.error(
             "etcd: error. python-etcd does not fully support python 2.6, no error information available"
         )
         raise
     except Exception as err:  # pylint: disable=broad-except
         log.error("etcd: uncaught exception %s", err)
         raise
     return result
Esempio n. 2
0
    def test_get(self):
        '''
        Test if it get a value from etcd, by direct path
        '''
        with patch('etcd.Client') as mock:
            client = etcd_util.EtcdClient({})

            with patch.object(client, 'read', autospec=True) as mock:
                mock.return_value = MagicMock(value='stack')
                self.assertEqual(client.get('salt'), 'stack')
                mock.assert_called_with('salt', recursive=False)

                self.assertEqual(client.get('salt', recurse=True), 'stack')
                mock.assert_called_with('salt', recursive=True)

                # iter(list(Exception)) works correctly with both mock<1.1 and mock>=1.1
                mock.side_effect = iter([etcd.EtcdKeyNotFound()])
                self.assertEqual(client.get('not-found'), None)

                mock.side_effect = iter([etcd.EtcdConnectionFailed()])
                self.assertEqual(client.get('watching'), None)

                # python 2.6 test
                mock.side_effect = ValueError
                self.assertEqual(client.get('not-found'), None)

                mock.side_effect = Exception
                self.assertRaises(Exception, client.get, 'some-error')
Esempio n. 3
0
    def _do_http_request(self, retry, machines_cache, request_executor, method, path, fields=None, **kwargs):
        some_request_failed = False
        for i, base_uri in enumerate(machines_cache):
            if i > 0:
                logger.info("Retrying on %s", base_uri)
            try:
                response = request_executor(method, base_uri + path, fields=fields, **kwargs)
                response.data.decode('utf-8')
                self._check_cluster_id(response)
                if some_request_failed:
                    self.set_base_uri(base_uri)
                    self._refresh_machines_cache()
                return response
            except (HTTPError, HTTPException, socket.error, socket.timeout) as e:
                self.http.clear()
                # switch to the next etcd node because we don't know exactly what happened,
                # whether the key didn't received an update or there is a network problem.
                if not retry and i + 1 < len(machines_cache):
                    self.set_base_uri(machines_cache[i + 1])
                if (isinstance(fields, dict) and fields.get("wait") == "true" and
                        isinstance(e, (ReadTimeoutError, ProtocolError))):
                    logger.debug("Watch timed out.")
                    raise etcd.EtcdWatchTimedOut("Watch timed out: {0}".format(e), cause=e)
                logger.error("Request to server %s failed: %r", base_uri, e)
                logger.info("Reconnection allowed, looking for another server.")
                if not retry:
                    raise etcd.EtcdException('{0} {1} request failed'.format(method, path))
                some_request_failed = True

        raise etcd.EtcdConnectionFailed('No more machines in the cluster')
Esempio n. 4
0
    def machines(self):
        """Original `machines` method(property) of `etcd.Client` class raise exception
        when it failed to get list of etcd cluster members. This method is being called
        only when request failed on one of the etcd members during `api_execute` call.
        For us it's more important to execute original request rather then get new topology
        of etcd cluster. So we will catch this exception and return empty list of machines.
        Later, during next `api_execute` call we will forcefully update machines_cache.

        Also this method implements the same timeout-retry logic as `api_execute`, because
        the original method was retrying 2 times with the `read_timeout` on each node."""

        machines_cache = self.machines_cache
        kwargs = self._build_request_parameters(len(machines_cache))

        for base_uri in machines_cache:
            try:
                response = self.http.request(self._MGET, base_uri + self.version_prefix + '/machines', **kwargs)
                data = self._handle_server_response(response).data.decode('utf-8')
                machines = [m.strip() for m in data.split(',') if m.strip()]
                logger.debug("Retrieved list of machines: %s", machines)
                if machines:
                    random.shuffle(machines)
                    self._update_dns_cache(self._dns_resolver.resolve_async, machines)
                    return machines
            except Exception as e:
                self.http.clear()
                logger.error("Failed to get list of machines from %s%s: %r", base_uri, self.version_prefix, e)

        raise etcd.EtcdConnectionFailed('No more machines in the cluster')
Esempio n. 5
0
    def test_get(self):
        """
        Test if it get a value from etcd, by direct path
        """
        with patch("etcd.Client") as mock:
            client = etcd_util.EtcdClient({})

            with patch.object(client, "read", autospec=True) as mock:
                mock.return_value = MagicMock(value="stack")
                self.assertEqual(client.get("salt"), "stack")
                mock.assert_called_with("salt", recursive=False)

                self.assertEqual(client.get("salt", recurse=True), "stack")
                mock.assert_called_with("salt", recursive=True)

                # iter(list(Exception)) works correctly with both mock<1.1 and mock>=1.1
                mock.side_effect = iter([etcd.EtcdKeyNotFound()])
                self.assertEqual(client.get("not-found"), None)

                mock.side_effect = iter([etcd.EtcdConnectionFailed()])
                self.assertEqual(client.get("watching"), None)

                # python 2.6 test
                mock.side_effect = ValueError
                self.assertEqual(client.get("not-found"), None)

                mock.side_effect = Exception
                self.assertRaises(Exception, client.get, "some-error")
Esempio n. 6
0
def test_get():
    """
    Test if it get a value from etcd, by direct path
    """
    with patch("etcd.Client") as mock:
        client = etcd_util.EtcdClient({})

        with patch.object(client, "read", autospec=True) as mock:
            mock.return_value = MagicMock(value="stack")
            assert client.get("salt") == "stack"
            mock.assert_called_with("salt", recursive=False)

            # iter(list(Exception)) works correctly with both mock<1.1 and mock>=1.1
            mock.side_effect = iter([etcd.EtcdKeyNotFound()])
            assert client.get("not-found") is None

            mock.side_effect = iter([etcd.EtcdConnectionFailed()])
            assert client.get("watching") is None

            # python 2.6 test
            mock.side_effect = ValueError
            assert client.get("not-found") is None

            mock.side_effect = Exception
            with pytest.raises(Exception):
                client.get("some-error")

        # Get with recurse now delegates to client.tree
        with patch.object(client, "tree", autospec=True) as tree_mock:
            tree_mock.return_value = {"salt": "stack"}
            assert client.get("salt", recurse=True) == {"salt": "stack"}
            tree_mock.assert_called_with("salt")
Esempio n. 7
0
    def wait_for_cluster(cluster_name,
                         client,
                         client_port=DEFAULT_CLIENT_PORT):
        """
        Validates the health of the etcd cluster is healthy
        :param cluster_name: Name of the cluster
        :type cluster_name: str

        :param client: The client on which to validate the cluster
        :type client: SSHClient

        :param client_port: Port to be used by client
        :type client_port: int

        :return: None
        """
        EtcdInstaller._logger.debug(
            'Waiting for cluster "{0}"'.format(cluster_name))
        tries = 10
        healthy = EtcdInstaller._is_healty(cluster_name,
                                           client,
                                           client_port=client_port)
        while healthy is False and tries > 0:
            tries -= 1
            time.sleep(10 - tries)
            healthy = EtcdInstaller._is_healty(cluster_name,
                                               client,
                                               client_port=client_port)
        if healthy is False:
            raise etcd.EtcdConnectionFailed(
                'Etcd cluster "{0}" could not be started correctly'.format(
                    cluster_name))
        EtcdInstaller._logger.debug(
            'Cluster "{0}" running'.format(cluster_name))
Esempio n. 8
0
 def _next_server(self):
     """ Selects the next server in the list, refreshes the server list. """
     _log.debug("Selection next machine in cache. Available machines: %s",
                self._machines_cache)
     try:
         mach = self._machines_cache.pop()
     except IndexError:
         _log.error("Machines cache is empty, no machines to try.")
         raise etcd.EtcdConnectionFailed('No more machines in the cluster')
     else:
         _log.info("Selected new etcd server %s", mach)
         return mach
Esempio n. 9
0
    def test_watch(self):
        with patch('etcd.Client', autospec=True) as client_mock:
            client = etcd_util.EtcdClient({})

            with patch.object(client, 'read', autospec=True) as mock:
                mock.return_value = MagicMock(value='stack', key='/some-key', modifiedIndex=1, dir=False)
                self.assertDictEqual(client.watch('/some-key'),
                                     {'value': 'stack', 'key': '/some-key', 'mIndex': 1, 'changed': True, 'dir': False})
                mock.assert_called_with('/some-key', wait=True, recursive=False, timeout=0, waitIndex=None)

                mock.side_effect = iter([etcd_util.EtcdUtilWatchTimeout, mock.return_value])
                self.assertDictEqual(client.watch('/some-key'),
                                     {'value': 'stack', 'changed': False, 'mIndex': 1, 'key': '/some-key', 'dir': False})

                mock.side_effect = iter([etcd_util.EtcdUtilWatchTimeout, etcd.EtcdKeyNotFound])
                self.assertEqual(client.watch('/some-key'),
                                 {'value': None, 'changed': False, 'mIndex': 0, 'key': '/some-key', 'dir': False})

                mock.side_effect = iter([etcd_util.EtcdUtilWatchTimeout, ValueError])
                self.assertEqual(client.watch('/some-key'), {})

                mock.side_effect = None
                mock.return_value = MagicMock(value='stack', key='/some-key', modifiedIndex=1, dir=True)
                self.assertDictEqual(client.watch('/some-dir', recurse=True, timeout=5, index=10),
                                     {'value': 'stack', 'key': '/some-key', 'mIndex': 1, 'changed': True, 'dir': True})
                mock.assert_called_with('/some-dir', wait=True, recursive=True, timeout=5, waitIndex=10)

                # iter(list(Exception)) works correctly with both mock<1.1 and mock>=1.1
                mock.side_effect = iter([MaxRetryError(None, None)])
                self.assertEqual(client.watch('/some-key'), {})

                mock.side_effect = iter([etcd.EtcdConnectionFailed()])
                self.assertEqual(client.watch('/some-key'), {})

                mock.side_effect = None
                mock.return_value = None
                self.assertEqual(client.watch('/some-key'), {})
Esempio n. 10
0
    def api_execute(self, path, method, params=None, timeout=None):
        """ Executes the query. """

        some_request_failed = False
        response = False

        if timeout is None:
            timeout = self.read_timeout

        if timeout == 0:
            timeout = None

        if not path.startswith('/'):
            raise ValueError('Path does not start with /')

        while not response:
            try:
                url = self._base_uri + path

                if (method == self._MGET) or (method == self._MDELETE):
                    response = self.http.request(method,
                                                 url,
                                                 timeout=timeout,
                                                 fields=params,
                                                 redirect=self.allow_redirect,
                                                 preload_content=False)

                elif (method == self._MPUT) or (method == self._MPOST):
                    response = self.http.request_encode_body(
                        method,
                        url,
                        fields=params,
                        timeout=timeout,
                        encode_multipart=False,
                        redirect=self.allow_redirect,
                        preload_content=False)
                else:
                    raise etcd.EtcdException(
                        'HTTP method {} not supported'.format(method))

            # urllib3 doesn't wrap all httplib exceptions and earlier versions
            # don't wrap socket errors either.
            except (urllib3.exceptions.HTTPError, HTTPException,
                    socket.error) as e:
                _log.error("Request to server %s failed: %r", self._base_uri,
                           e)
                if self._allow_reconnect:
                    _log.info("Reconnection allowed, looking for another "
                              "server.")
                    # _next_server() raises EtcdException if there are no
                    # machines left to try, breaking out of the loop.
                    self._base_uri = self._next_server()
                    some_request_failed = True
                else:
                    _log.debug("Reconnection disabled, giving up.")
                    raise etcd.EtcdConnectionFailed(
                        "Connection to etcd failed due to %r" % e)
            except:
                _log.exception("Unexpected request failure, re-raising.")
                raise

            else:
                # Check the cluster ID hasn't changed under us.  We use
                # preload_content=False above so we can read the headers
                # before we wait for the content of a long poll.
                cluster_id = response.getheader("x-etcd-cluster-id")
                id_changed = (self.expected_cluster_id
                              and cluster_id is not None
                              and cluster_id != self.expected_cluster_id)
                # Update the ID so we only raise the exception once.
                old_expected_cluster_id = self.expected_cluster_id
                self.expected_cluster_id = cluster_id
                if id_changed:
                    # Defensive: clear the pool so that we connect afresh next
                    # time.
                    self.http.clear()
                    raise etcd.EtcdClusterIdChanged(
                        'The UUID of the cluster changed from {} to '
                        '{}.'.format(old_expected_cluster_id, cluster_id))

        if some_request_failed:
            if not self._use_proxies:
                # The cluster may have changed since last invocation
                self._machines_cache = self.machines
            self._machines_cache.remove(self._base_uri)
        return self._handle_server_response(response)
Esempio n. 11
0
        def wrapper(self, path, method, params=None, timeout=None):
            response = False

            if timeout is None:
                timeout = self.read_timeout

            if timeout == 0:
                timeout = None

            if not path.startswith('/'):
                raise ValueError('Path does not start with /')

            while not response:
                some_request_failed = False
                try:
                    response = payload(self, path, method,
                                       params=params, timeout=timeout)
                    # Check the cluster ID hasn't changed under us.  We use
                    # preload_content=False above so we can read the headers
                    # before we wait for the content of a watch.
                    self._check_cluster_id(response, path)
                    # Now force the data to be preloaded in order to trigger any
                    # IO-related errors in this method rather than when we try to
                    # access it later.
                    _ = response.data
                    # urllib3 doesn't wrap all httplib exceptions and earlier versions
                    # don't wrap socket errors either.
                except (HTTPError, HTTPException, socket.error) as e:
                    if (isinstance(params, dict) and
                        params.get("wait") == "true" and
                        isinstance(e, ReadTimeoutError)):
                        _log.debug("Watch timed out.")
                        raise etcd.EtcdWatchTimedOut(
                            "Watch timed out: %r" % e,
                            cause=e
                        )
                    _log.error("Request to server %s failed: %r",
                               self._base_uri, e)
                    if self._allow_reconnect:
                        _log.info("Reconnection allowed, looking for another "
                                  "server.")
                        # _next_server() raises EtcdException if there are no
                        # machines left to try, breaking out of the loop.
                        self._base_uri = self._next_server(cause=e)
                        some_request_failed = True

                        # if exception is raised on _ = response.data
                        # the condition for while loop will be False
                        # but we should retry
                        response = False
                    else:
                        _log.debug("Reconnection disabled, giving up.")
                        raise etcd.EtcdConnectionFailed(
                            "Connection to etcd failed due to %r" % e,
                            cause=e
                        )
                except etcd.EtcdClusterIdChanged as e:
                    _log.warning(e)
                    raise
                except:
                    _log.debug("Unexpected request failure, re-raising.")
                    raise

                if some_request_failed:
                    if not self._use_proxies:
                        # The cluster may have changed since last invocation
                        self._machines_cache = self.machines
                        self._machines_cache.remove(self._base_uri)
            return self._handle_server_response(response)
Esempio n. 12
0
    def api_execute(self, path, method, params=None, timeout=None):
        """ Executes the query. """

        some_request_failed = False
        response = False

        if timeout is None:
            timeout = self.read_timeout

        if timeout == 0:
            timeout = None

        if not path.startswith('/'):
            raise ValueError('Path does not start with /')

        while not response:
            try:
                url = self._base_uri + path

                if (method == self._MGET) or (method == self._MDELETE):
                    response = self.http.request(method,
                                                 url,
                                                 timeout=timeout,
                                                 fields=params,
                                                 redirect=self.allow_redirect,
                                                 preload_content=False)

                elif (method == self._MPUT) or (method == self._MPOST):
                    response = self.http.request_encode_body(
                        method,
                        url,
                        fields=params,
                        timeout=timeout,
                        encode_multipart=False,
                        redirect=self.allow_redirect,
                        preload_content=False)
                else:
                    raise etcd.EtcdException(
                        'HTTP method {} not supported'.format(method))

                # Check the cluster ID hasn't changed under us.  We use
                # preload_content=False above so we can read the headers
                # before we wait for the content of a watch.
                self._check_cluster_id(response)
                # Now force the data to be preloaded in order to trigger any
                # IO-related errors in this method rather than when we try to
                # access it later.
                _ = response.data
            # urllib3 doesn't wrap all httplib exceptions and earlier versions
            # don't wrap socket errors either.
            except (urllib3.exceptions.HTTPError, HTTPException,
                    socket.error) as e:
                if (params.get("wait") == "true" and isinstance(
                        e, urllib3.exceptions.ReadTimeoutError)):
                    _log.debug("Watch timed out.")
                    raise etcd.EtcdWatchTimedOut("Watch timed out: %r" % e,
                                                 cause=e)

                _log.error("Request to server %s failed: %r", self._base_uri,
                           e)
                if self._allow_reconnect:
                    _log.info("Reconnection allowed, looking for another "
                              "server.")
                    # _next_server() raises EtcdException if there are no
                    # machines left to try, breaking out of the loop.
                    self._base_uri = self._next_server(cause=e)
                    some_request_failed = True
                else:
                    _log.debug("Reconnection disabled, giving up.")
                    raise etcd.EtcdConnectionFailed(
                        "Connection to etcd failed due to %r" % e, cause=e)
            except:
                _log.exception("Unexpected request failure, re-raising.")
                raise

        if some_request_failed:
            if not self._use_proxies:
                # The cluster may have changed since last invocation
                self._machines_cache = self.machines
            self._machines_cache.remove(self._base_uri)
        return self._handle_server_response(response)
Esempio n. 13
0
    def test_watch(self):
        with patch("etcd.Client", autospec=True) as client_mock:
            client = etcd_util.EtcdClient({})

            with patch.object(client, "read", autospec=True) as mock:
                mock.return_value = MagicMock(
                    value="stack", key="/some-key", modifiedIndex=1, dir=False
                )
                self.assertDictEqual(
                    client.watch("/some-key"),
                    {
                        "value": "stack",
                        "key": "/some-key",
                        "mIndex": 1,
                        "changed": True,
                        "dir": False,
                    },
                )
                mock.assert_called_with(
                    "/some-key", wait=True, recursive=False, timeout=0, waitIndex=None
                )

                mock.side_effect = iter(
                    [etcd_util.EtcdUtilWatchTimeout, mock.return_value]
                )
                self.assertDictEqual(
                    client.watch("/some-key"),
                    {
                        "value": "stack",
                        "changed": False,
                        "mIndex": 1,
                        "key": "/some-key",
                        "dir": False,
                    },
                )

                mock.side_effect = iter(
                    [etcd_util.EtcdUtilWatchTimeout, etcd.EtcdKeyNotFound]
                )
                self.assertEqual(
                    client.watch("/some-key"),
                    {
                        "value": None,
                        "changed": False,
                        "mIndex": 0,
                        "key": "/some-key",
                        "dir": False,
                    },
                )

                mock.side_effect = iter([etcd_util.EtcdUtilWatchTimeout, ValueError])
                self.assertEqual(client.watch("/some-key"), {})

                mock.side_effect = None
                mock.return_value = MagicMock(
                    value="stack", key="/some-key", modifiedIndex=1, dir=True
                )
                self.assertDictEqual(
                    client.watch("/some-dir", recurse=True, timeout=5, index=10),
                    {
                        "value": "stack",
                        "key": "/some-key",
                        "mIndex": 1,
                        "changed": True,
                        "dir": True,
                    },
                )
                mock.assert_called_with(
                    "/some-dir", wait=True, recursive=True, timeout=5, waitIndex=10
                )

                # iter(list(Exception)) works correctly with both mock<1.1 and mock>=1.1
                mock.side_effect = iter([MaxRetryError(None, None)])
                self.assertEqual(client.watch("/some-key"), {})

                mock.side_effect = iter([etcd.EtcdConnectionFailed()])
                self.assertEqual(client.watch("/some-key"), {})

                mock.side_effect = None
                mock.return_value = None
                self.assertEqual(client.watch("/some-key"), {})
Esempio n. 14
0
def test_watch(use_v2, client_name):
    with patch(client_name, autospec=True):
        client = etcd_util.get_conn(
            {"etcd.require_v2": use_v2, "etcd.encode_values": False}
        )

        if use_v2:
            with patch.object(client, "read", autospec=True) as mock:
                mock.return_value = MagicMock(
                    value="stack", key="/some-key", modifiedIndex=1, dir=False
                )
                assert client.watch("/some-key") == {
                    "value": "stack",
                    "key": "/some-key",
                    "mIndex": 1,
                    "changed": True,
                    "dir": False,
                }
                mock.assert_called_with(
                    "/some-key",
                    wait=True,
                    recurse=False,
                    timeout=0,
                    start_revision=None,
                )

                mock.side_effect = iter(
                    [etcd_util.EtcdUtilWatchTimeout, mock.return_value]
                )
                assert client.watch("/some-key") == {
                    "value": "stack",
                    "changed": False,
                    "mIndex": 1,
                    "key": "/some-key",
                    "dir": False,
                }

                mock.side_effect = iter(
                    [etcd_util.EtcdUtilWatchTimeout, etcd.EtcdKeyNotFound]
                )
                assert client.watch("/some-key") == {
                    "value": None,
                    "changed": False,
                    "mIndex": 0,
                    "key": "/some-key",
                    "dir": False,
                }

                mock.side_effect = iter([etcd_util.EtcdUtilWatchTimeout, ValueError])
                assert client.watch("/some-key") == {}

                mock.side_effect = None
                mock.return_value = MagicMock(
                    value="stack", key="/some-key", modifiedIndex=1, dir=True
                )
                assert client.watch(
                    "/some-dir", recurse=True, timeout=5, start_revision=10
                ) == {
                    "value": "stack",
                    "key": "/some-key",
                    "mIndex": 1,
                    "changed": True,
                    "dir": True,
                }
                mock.assert_called_with(
                    "/some-dir", wait=True, recurse=True, timeout=5, start_revision=10
                )

                # iter(list(Exception)) works correctly with both mock<1.1 and mock>=1.1
                mock.side_effect = iter([MaxRetryError(None, None)])
                assert client.watch("/some-key") == {}

                mock.side_effect = iter([etcd.EtcdConnectionFailed()])
                assert client.watch("/some-key") is None

                mock.side_effect = None
                mock.return_value = None
                assert client.watch("/some-key") == {}
        else:
            with patch.object(client, "read", autospec=True) as mock:
                mock.return_value = MagicMock(
                    value="stack", key="/some-key", mod_revision=1
                )
                assert client.watch("/some-key") == {
                    "value": "stack",
                    "key": "/some-key",
                    "mIndex": 1,
                    "changed": True,
                    "dir": False,
                }
                mock.assert_called_with(
                    "/some-key",
                    wait=True,
                    recurse=False,
                    timeout=0,
                    start_revision=None,
                )
                mock.return_value = MagicMock(
                    value="stack", key="/some-key", mod_revision=1
                )
                assert client.watch(
                    "/some-key", recurse=True, timeout=5, start_revision=10
                ) == {
                    "value": "stack",
                    "key": "/some-key",
                    "mIndex": 1,
                    "changed": True,
                    "dir": False,
                }
                mock.assert_called_with(
                    "/some-key", wait=True, recurse=True, timeout=5, start_revision=10
                )

                mock.side_effect = None
                mock.return_value = None
                assert client.watch("/some-key") is None
Esempio n. 15
0
    def read(self,
             key,
             recurse=False,
             wait=False,
             timeout=None,
             start_revision=None,
             **kwargs):
        recursive = kwargs.pop("recursive", None)
        wait_index = kwargs.pop("waitIndex", None)
        if recursive is not None:
            salt.utils.versions.warn_until(
                "Argon",
                "The recursive kwarg has been deprecated, and will be removed "
                "in the Argon release. Please use recurse instead.",
            )
            recurse = recursive
        if wait_index is not None:
            salt.utils.versions.warn_until(
                "Argon",
                "The waitIndex kwarg has been deprecated, and will be removed "
                "in the Argon release. Please use start_revision instead.",
            )
            start_revision = wait_index
        if kwargs:
            log.warning("Invalid kwargs passed in will not be used: %s",
                        kwargs)

        try:
            if start_revision:
                result = self.client.read(
                    key,
                    recursive=recurse,
                    wait=wait,
                    timeout=timeout,
                    waitIndex=start_revision,
                )
            else:
                result = self.client.read(key,
                                          recursive=recurse,
                                          wait=wait,
                                          timeout=timeout)
        except (etcd.EtcdConnectionFailed, etcd.EtcdKeyNotFound) as err:
            log.error("etcd: %s", err)
            raise
        except ReadTimeoutError:
            # For some reason, we have to catch this directly.  It falls through
            # from python-etcd because it's trying to catch
            # urllib3.exceptions.ReadTimeoutError and strangely, doesn't catch.
            # This can occur from a watch timeout that expires, so it may be 'expected'
            # behavior. See issue #28553
            if wait:
                # Wait timeouts will throw ReadTimeoutError, which isn't bad
                log.debug("etcd: Timed out while executing a wait")
                raise EtcdUtilWatchTimeout("Watch on {} timed out".format(key))
            log.error("etcd: Timed out")
            raise etcd.EtcdConnectionFailed("Connection failed")
        except MaxRetryError as err:
            # Same issue as ReadTimeoutError.  When it 'works', python-etcd
            # throws EtcdConnectionFailed, so we'll do that for it.
            log.error("etcd: Could not connect")
            raise etcd.EtcdConnectionFailed("Could not connect to etcd server")
        except etcd.EtcdException as err:
            # EtcdValueError inherits from ValueError, so we don't want to accidentally
            # catch this below on ValueError and give a bogus error message
            log.error("etcd: %s", err)
            raise
        except ValueError:
            # python-etcd doesn't fully support python 2.6 and ends up throwing this for *any* exception because
            # it uses the newer {} format syntax
            log.error(
                "etcd: error. python-etcd does not fully support python 2.6, no error"
                " information available")
            raise
        except Exception as err:  # pylint: disable=broad-except
            log.error("etcd: uncaught exception %s", err)
            raise
        return result