Example #1
0
    def api_execute(self, path, method, params=None, timeout=None):
        if not path.startswith('/'):
            raise ValueError('Path does not start with /')

        retry = params.pop('retry', None) if isinstance(params, dict) else None
        kwargs = {'fields': params, 'preload_content': False}

        if method in [self._MGET, self._MDELETE]:
            request_executor = self.http.request
        elif method in [self._MPUT, self._MPOST]:
            request_executor = self.http.request_encode_body
            kwargs['encode_multipart'] = False
        else:
            raise etcd.EtcdException('HTTP method {0} not supported'.format(method))

        # Update machines_cache if previous attempt of update has failed
        if self._update_machines_cache:
            self._load_machines_cache()
        elif not self._use_proxies and time.time() - self._machines_cache_updated > self._machines_cache_ttl:
            self._refresh_machines_cache()

        kwargs.update(self._build_request_parameters(timeout))

        if retry:
            machines_cache = [self._base_uri] + self._machines_cache

        response = False

        while True:
            try:
                some_request_failed = False
                while not response:
                    response = self._do_http_request(request_executor, method, self._base_uri + path, **kwargs)

                    if response is False:
                        if not retry:
                            raise etcd.EtcdException('{0} {1} request failed'.format(method, path))
                        some_request_failed = True
                if some_request_failed:
                    self._refresh_machines_cache()
                if response:
                    break
            except etcd.EtcdConnectionFailed:
                if not retry:
                    raise
                sleeptime = retry.sleeptime
                remaining_time = retry.stoptime - sleeptime - time.time()
                nodes, timeout, retries = self._calculate_timeouts(len(machines_cache), remaining_time)
                if nodes == 0:
                    self._update_machines_cache = True
                    raise
                retry.sleep_func(sleeptime)
                retry.update_delay()
                # We still have some time left. Partially restore `_machines_cache` and retry request
                kwargs.update(timeout=timeout, retries=retries)
                self._base_uri = machines_cache[0]
                self._machines_cache = machines_cache[1:nodes]

        return self._handle_server_response(response)
Example #2
0
    def api_execute(self, path, method, params=None, timeout=None):
        if not path.startswith('/'):
            raise ValueError('Path does not start with /')

        kwargs = {
            'fields': params,
            'redirect': self.allow_redirect,
            'headers': self._get_headers(),
            'preload_content': False
        }

        if method in [self._MGET, self._MDELETE]:
            request_executor = self.http.request
        elif method in [self._MPUT, self._MPOST]:
            request_executor = self.http.request_encode_body
            kwargs['encode_multipart'] = False
        else:
            raise etcd.EtcdException(
                'HTTP method {0} not supported'.format(method))

        # Update machines_cache if previous attempt of update has failed
        if self._update_machines_cache:
            self._load_machines_cache()

        if timeout is None:
            # calculate the number of retries and timeout *per node*
            # actual number of retries depends on the number of nodes
            etcd_nodes = len(self._machines_cache) + 1
            kwargs['retries'] = 0 if etcd_nodes > 3 else (
                1 if etcd_nodes > 1 else 2)

            # if etcd_nodes > 3:
            #     kwargs.update({'retries': 0, 'timeout': float(self.read_timeout)/etcd_nodes})
            # elif etcd_nodes > 1:
            #     kwargs.update({'retries': 1, 'timeout': self.read_timeout/2.0/etcd_nodes})
            # else:
            #     kwargs.update({'retries': 2, 'timeout': self.read_timeout/3.0})
            kwargs['timeout'] = self.read_timeout / float(kwargs['retries'] +
                                                          1) / etcd_nodes
        else:
            kwargs.update({'retries': 0, 'timeout': timeout})

        response = False

        try:
            while not response:
                response = self._do_http_request(request_executor, method,
                                                 self._base_uri + path,
                                                 **kwargs)

                if response is False and not self._use_proxies:
                    self._machines_cache = self.machines
                    self._machines_cache.remove(self._base_uri)
            return self._handle_server_response(response)
        except etcd.EtcdConnectionFailed:
            self._update_machines_cache = True
            raise
Example #3
0
    def api_execute(self, path, method, params=None, timeout=None):
        if not path.startswith('/'):
            raise ValueError('Path does not start with /')

        retry = params.pop('retry', None) if isinstance(params, dict) else None
        kwargs = {'fields': params, 'preload_content': False}

        if method in [self._MGET, self._MDELETE]:
            request_executor = self.http.request
        elif method in [self._MPUT, self._MPOST]:
            request_executor = self.http.request_encode_body
            kwargs['encode_multipart'] = False
        else:
            raise etcd.EtcdException(
                'HTTP method {0} not supported'.format(method))

        # Update machines_cache if previous attempt of update has failed
        if self._update_machines_cache:
            self._load_machines_cache()
        elif not self._use_proxies and time.time(
        ) - self._machines_cache_updated > self._machines_cache_ttl:
            self._refresh_machines_cache()

        machines_cache = self.machines_cache
        etcd_nodes = len(machines_cache)
        kwargs.update(self._build_request_parameters(etcd_nodes, timeout))

        while True:
            try:
                response = self._do_http_request(retry, machines_cache,
                                                 request_executor, method,
                                                 path, **kwargs)
                return self._handle_server_response(response)
            except etcd.EtcdWatchTimedOut:
                raise
            except etcd.EtcdConnectionFailed as ex:
                try:
                    if self._load_machines_cache():
                        machines_cache = self.machines_cache
                        etcd_nodes = len(machines_cache)
                except Exception as e:
                    logger.debug('Failed to update list of etcd nodes: %r', e)
                sleeptime = retry.sleeptime
                remaining_time = retry.stoptime - sleeptime - time.time()
                nodes, timeout, retries = self._calculate_timeouts(
                    etcd_nodes, remaining_time)
                if nodes == 0:
                    self._update_machines_cache = True
                    raise ex
                retry.sleep_func(sleeptime)
                retry.update_delay()
                # We still have some time left. Partially reduce `machines_cache` and retry request
                kwargs.update(timeout=Timeout(connect=max(1, timeout / 2),
                                              total=timeout),
                              retries=retries)
                machines_cache = machines_cache[:nodes]
Example #4
0
 def release(self):
     """Release this lock."""
     if not self._index:
         raise etcd.EtcdException(
             u'Cannot release lock that has not been locked')
     params = {u'index': self._index}
     res = self.client.api_execute(self._path,
                                   self.client._MDELETE,
                                   params=params)
     self._index = None
Example #5
0
 def _handle_server_response(response):
     data = response.data
     try:
         data = data.decode('utf-8')
         data = json.loads(data)
     except (TypeError, ValueError, UnicodeError) as e:
         if response.status < 400:
             raise etcd.EtcdException('Server response was not valid JSON: %r' % e)
     if response.status < 400:
         return data
     _raise_for_data(data, response.status)
    def api_execute(self, path, method, params=None, timeout=None):
        if not path.startswith('/'):
            raise ValueError('Path does not start with /')

        kwargs = {'fields': params, 'preload_content': False}

        if method in [self._MGET, self._MDELETE]:
            request_executor = self.http.request
        elif method in [self._MPUT, self._MPOST]:
            request_executor = self.http.request_encode_body
            kwargs['encode_multipart'] = False
        else:
            raise etcd.EtcdException(
                'HTTP method {0} not supported'.format(method))

        # Update machines_cache if previous attempt of update has failed
        if self._update_machines_cache:
            self._load_machines_cache()
        elif time.time(
        ) - self._machines_cache_updated > self._machines_cache_ttl:
            self._machines_cache = self.machines
            if self._base_uri in self._machines_cache:
                self._machines_cache.remove(self._base_uri)
            self._machines_cache_updated = time.time()

        kwargs.update(self._build_request_parameters())

        if timeout is not None:
            kwargs.update({'retries': 0, 'timeout': timeout})

        response = False

        try:
            some_request_failed = False
            while not response:
                response = self._do_http_request(request_executor, method,
                                                 self._base_uri + path,
                                                 **kwargs)

                if response is False:
                    some_request_failed = True
            if some_request_failed and not self._use_proxies:
                self._machines_cache = self.machines
                if self._base_uri in self._machines_cache:
                    self._machines_cache.remove(self._base_uri)
        except etcd.EtcdConnectionFailed as e:
            if isinstance(e, etcd.EtcdWatchTimedOut) and self._machines_cache:
                self._base_uri = self._next_server()
            else:
                self._update_machines_cache = True
            if not response:
                raise
        return self._handle_server_response(response)
Example #7
0
    def deletemember(self, memberid):
        """
        add a member to the cluster

        Args:
            memberid (str): member id to delete

        """
        try:
            self.api_execute(self.version_prefix + '/members/%s'%memberid, self._MDELETE)
        except Exception as e:
            raise etcd.EtcdException("Cannot delete member: %s" % e)
Example #8
0
 def get(self):
     """
     Get Information on the lock.
     This allows to operate on locks that have not been acquired directly.
     """
     res = self.client.api_execute(self._path, self.client._MGET)
     if res.data:
         self.value = res.data.decode('utf-8')
     else:
         raise etcd.EtcdException('Lock is non-existent (or expired)')
     self._get_index()
     return self
Example #9
0
    def api_execute(self, path, method, params=None, timeout=None):
        """ Executes the query. """

        some_request_failed = False
        response = False
        request_kwargs = {}

        if timeout is None:
            timeout = self.read_timeout
        else:
            request_kwargs['retries'] = False

        if timeout:
            request_kwargs['timeout'] = timeout

        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,
                                                 fields=params,
                                                 redirect=self.allow_redirect,
                                                 **request_kwargs)

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

            except urllib3.exceptions.HTTPError as exc:
                if 'timeout' in request_kwargs and \
                   isinstance(exc, urllib3.exceptions.TimeoutError):
                    raise
                self._base_uri = self._next_server()
                some_request_failed = True

        if some_request_failed:
            self._machines_cache = self.machines
            self._machines_cache.remove(self._base_uri)
        return self._handle_server_response(response)
Example #10
0
 def delete(self):
     try:
         _ = self.client.api_execute(self.uri, self.client._MDELETE)
     except etcd.EtcdInsufficientPermissions as e:
         _log.error("Any action on the authorization requires the root role")
         raise
     except etcd.EtcdKeyNotFound:
         _log.info("%s '%s' not found", self.entity, self.name)
         raise
     except Exception as e:
         _log.error("Failed to delete %s in %s%s: %r",
                    self.entity, self._base_uri, self.version_prefix, e)
         raise etcd.EtcdException(
             "Could not delete {} '{}'".format(self.entity, self.name))
Example #11
0
    def renew(self, new_ttl, timeout=None):
        """
        Renew the TTL on this lock.

        Args:
            new_ttl (int): new TTL to set.
        """
        if not self._index:
            raise etcd.EtcdException(
                u'Cannot renew lock that has not been locked')
        params = {u'ttl': new_ttl, u'index': self._index}
        res = self.client.api_execute(self._path,
                                      self.client._MPUT,
                                      params=params)
Example #12
0
    def get(self, key):
        """
        Get the name of a leader object.

        Args:
            key (string): name of the leader key,

        Raises:
            etcd.EtcdException

        """
        res = self.client.api_execute(self.get_path(key), self.client._MGET)
        if not res.data:
            raise etcd.EtcdException('Leader path {} not found'.format(key))
        return res.data.decode('utf-8')
Example #13
0
    def addmember(self, memberuri):
        """
        add a member to the cluster

        Args:
            memberuri (str): new member's address as a uri

        Returns:
            dict with id and peerURLs as documented at https://coreos.com/etcd/docs/latest/v2/members_api.html#add-a-member

        """
        try:
            return self.api_execute_json(self.version_prefix + '/members', self._MPOST, {"peerURLs":[memberuri]}).data.decode('utf-8')
        except Exception as e:
            raise etcd.EtcdException("Cannot add member: %s" % e)
Example #14
0
 def setUp(self):
     with patch.object(Client, 'machines') as mock_machines:
         mock_machines.__get__ = Mock(return_value=[
             'http://localhost:2379', 'http://localhost:4001'
         ])
         self.etcd = Etcd(
             'foo', {
                 'namespace': '/patroni/',
                 'ttl': 30,
                 'host': 'localhost:2379',
                 'scope': 'test'
             })
         self.etcd.client.write = etcd_write
         self.etcd.client.read = etcd_read
         self.etcd.client.delete = Mock(side_effect=etcd.EtcdException())