def write(self, key, value, ttl=None, dir=False, append=False, **kwdargs): """ Writes the value for a key, possibly doing atomit Compare-and-Swap Args: key (str): Key. value (object): value to set ttl (int): Time in seconds of expiration (optional). dir (bool): Set to true if we are writing a directory; default is false. append (bool): If true, it will post to append the new value to the dir, creating a sequential key. Defaults to false. Other parameters modifying the write method are accepted: prevValue (str): compare key to this value, and swap only if corresponding (optional). prevIndex (int): modify key only if actual modifiedIndex matches the provided one (optional). prevExist (bool): If false, only create key; if true, only update key. Returns: client.EtcdResult >>> print client.write('/key', 'newValue', ttl=60, prevExist=False).value 'newValue' """ key = self._sanitize_key(key) params = {} if value is not None: params['value'] = value if ttl: params['ttl'] = ttl if dir: if value: raise etcd.EtcdException( 'Cannot create a directory with a value') params['dir'] = "true" for (k, v) in kwdargs.items(): if k in self._comparison_conditions: if type(v) == bool: params[k] = v and "true" or "false" else: params[k] = v method = append and self._MPOST or self._MPUT if '_endpoint' in kwdargs: path = kwdargs['_endpoint'] + key else: path = self.key_endpoint + key response = self.api_execute(path, method, params=params) return self._result_from_response(response)
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) # 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) if method in (self._MGET, self._MPUT, self._MPOST, self._MDELETE): from urllib import urlencode response = self.http.request(method, url, body=urlencode(params)) if method in (self._MGET, self._MPUT): response = self.http.request( method, "?".join([url, urlencode(params)])) elif method in (self._MPOST, self._MDELETE): response = self.http.request(method, url, body=urlencode(params)) else: raise etcd.EtcdException( 'HTTP method {} not supported'.format(method)) except urllib3.exceptions.MaxRetryError: 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 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 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 _result_from_response(self, response): """ Creates an EtcdResult from json dictionary """ try: res = json.loads(response.read().decode('utf-8')) r = etcd.EtcdResult(**res) if response.status_code == 201: r.newKey = True r.parse_headers(response) return r except Exception as e: raise etcd.EtcdException('Unable to decode server response: %s' % e)
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 _handle_server_response(self, response): """ Handles the server response """ if response.status_code in [200, 201]: return response else: resp = response.read().decode('utf-8') # throw the appropriate exception try: r = json.loads(resp) except ValueError: r = None if r: etcd.EtcdError.handle(**r) else: raise etcd.EtcdException(resp)
def _get_index(self): res = self.client.api_execute(self._path, self.client._MGET, {u'field': u'index'}) if not res.data: raise etcd.EtcdException('Lock is non-existent (or expired)') self._index = res.data.decode('utf-8')
def _next_server(self): """ Selects the next server in the list, refreshes the server list. """ try: return self._machines_cache.pop() except IndexError: raise etcd.EtcdException('No more machines in the cluster')