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)
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
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]
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
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)
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)
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
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)
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))
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)
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')
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)
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())