<NME> base.py <BEF> import abc import collections import warnings import logging import base64 import json import os import six from six.moves import urllib log = logging.getLogger(__name__) class ConsulException(Exception): pass class ACLDisabled(ConsulException): pass class ACLPermissionDenied(ConsulException): pass class NotFound(ConsulException): pass class Timeout(ConsulException): pass class BadRequest(ConsulException): pass class ClientError(ConsulException): """Encapsulates 4xx Http error code""" pass # # Convenience to define checks class Check(object): """ There are three different kinds of checks: script, http and ttl """ @classmethod def script(klass, args, interval): """ Run the script *args* every *interval* (e.g. "10s") to peform health check """ if isinstance(args, six.string_types) \ or isinstance(args, six.binary_type): warnings.warn( "Check.script should take a list of args", DeprecationWarning) args = ["sh", "-c", args] return {'args': args, 'interval': interval} @classmethod def http(klass, url, interval, timeout=None, deregister=None, header=None): """ Peform a HTTP GET against *url* every *interval* (e.g. "10s") to peform health check with an optional *timeout* and optional *deregister* after which a failing service will be automatically deregistered. Optional parameter *header* specifies headers sent in HTTP request. *header* paramater is in form of map of lists of strings, e.g. {"x-foo": ["bar", "baz"]}. """ ret = {'http': url, 'interval': interval} if timeout: ret['timeout'] = timeout if deregister: ret['DeregisterCriticalServiceAfter'] = deregister if header: ret['header'] = header return ret @classmethod def tcp(klass, host, port, interval, timeout=None, deregister=None): """ Attempt to establish a tcp connection to the specified *host* and *port* at a specified *interval* with optional *timeout* and optional *deregister* after which a failing service will be automatically deregistered. """ ret = { 'tcp': '{host:s}:{port:d}'.format(host=host, port=port), 'interval': interval } if timeout: ret['timeout'] = timeout if deregister: ret['DeregisterCriticalServiceAfter'] = deregister return ret @classmethod def ttl(klass, ttl): """ Set check to be marked as critical after *ttl* (e.g. "10s") unless the check is periodically marked as passing. """ return {'ttl': ttl} @classmethod def docker(klass, container_id, shell, script, interval, deregister=None): """ Invoke *script* packaged within a running docker container with *container_id* at a specified *interval* on the configured *shell* using the Docker Exec API. Optional *register* after which a failing service will be automatically deregistered. """ ret = { 'docker_container_id': container_id, 'shell': shell, 'script': script, 'interval': interval } if deregister: ret['DeregisterCriticalServiceAfter'] = deregister return ret @classmethod def _compat( self, script=None, interval=None, ttl=None, http=None, timeout=None, deregister=None): if not script and not http and not ttl: return {} log.warn( 'DEPRECATED: use consul.Check.script/http/ttl to specify check') ret = {'check': {}} if script: assert interval and not (ttl or http) ret['check'] = {'script': script, 'interval': interval} if ttl: assert not (interval or script or http) ret['check'] = {'ttl': ttl} if http: assert interval and not (script or ttl) ret['check'] = {'http': http, 'interval': interval} if timeout: assert http ret['check']['timeout'] = timeout if deregister: ret['check']['DeregisterCriticalServiceAfter'] = deregister return ret Response = collections.namedtuple('Response', ['code', 'headers', 'body']) # params[consistency] = '1' def callback(response): if response.code == 404: data = None else: data = json.loads(response.body) elif response.code == 401: raise ACLDisabled(response.body) elif response.code == 403: raise ACLPermissionDenied(response.body) elif response.code == 404: if not allow_404: raise NotFound(response.body) else: raise ClientError("%d %s" % (response.code, response.body)) elif 500 <= response.code < 600: raise ConsulException("%d %s" % (response.code, response.body)) @classmethod def bool(klass): # returns True on successful response def cb(response): CB._status(response) return response.code == 200 return cb @classmethod def json( klass, map=None, allow_404=True, one=False, decode=False, is_id=False, index=False): """ *map* is a function to apply to the final result. *allow_404* if set, None will be returned on 404, instead of raising NotFound. *index* if set, a tuple of index, data will be returned. *one* returns only the first item of the list of items. empty lists are coerced to None. *decode* if specified this key will be base64 decoded. *is_id* only the 'ID' field of the json object will be returned. """ def cb(response): CB._status(response, allow_404=allow_404) if response.code == 404: return response.headers['X-Consul-Index'], None data = json.loads(response.body) if decode: for item in data: if item.get(decode) is not None: item[decode] = base64.b64decode(item[decode]) if is_id: data = data['ID'] if one: if data == []: data = None if data is not None: data = data[0] if map: data = map(data) if index: return response.headers['X-Consul-Index'], data return data return cb class HTTPClient(six.with_metaclass(abc.ABCMeta, object)): def __init__(self, host='127.0.0.1', port=8500, scheme='http', verify=True, cert=None): self.host = host self.port = port self.scheme = scheme self.verify = verify self.base_uri = '%s://%s:%s' % (self.scheme, self.host, self.port) self.cert = cert def uri(self, path, params=None): uri = self.base_uri + urllib.parse.quote(path, safe='/:') if params: uri = '%s?%s' % (uri, urllib.parse.urlencode(params)) return uri @abc.abstractmethod def get(self, callback, path, params=None): raise NotImplementedError @abc.abstractmethod def put(self, callback, path, params=None, data=''): raise NotImplementedError @abc.abstractmethod def delete(self, callback, path, params=None): raise NotImplementedError @abc.abstractmethod def post(self, callback, path, params=None, data=''): raise NotImplementedError class Consul(object): def __init__( self, host='127.0.0.1', port=8500, token=None, scheme='http', consistency='default', dc=None, verify=True, cert=None): """ *token* is an optional `ACL token`_. If supplied it will be used by default for all requests made with this client session. It's still possible to override this token by passing a token explicitly for a request. *consistency* sets the consistency mode to use by default for all reads that support the consistency option. It's still possible to override this by passing explicitly for a given request. *consistency* can be either 'default', 'consistent' or 'stale'. *dc* is the datacenter that this agent will communicate with. By default the datacenter of the host is used. *verify* is whether to verify the SSL certificate for HTTPS requests *cert* client side certificates for HTTPS requests """ # TODO: Status if os.getenv('CONSUL_HTTP_ADDR'): try: host, port = os.getenv('CONSUL_HTTP_ADDR').split(':') except ValueError: raise ConsulException('CONSUL_HTTP_ADDR (%s) invalid, ' 'does not match <host>:<port>' % os.getenv('CONSUL_HTTP_ADDR')) use_ssl = os.getenv('CONSUL_HTTP_SSL') if use_ssl is not None: scheme = 'https' if use_ssl == 'true' else 'http' if os.getenv('CONSUL_HTTP_SSL_VERIFY') is not None: verify = os.getenv('CONSUL_HTTP_SSL_VERIFY') == 'true' self.http = self.connect(host, port, scheme, verify, cert) self.token = os.getenv('CONSUL_HTTP_TOKEN', token) self.scheme = scheme self.dc = dc assert consistency in ('default', 'consistent', 'stale'), \ 'consistency must be either default, consistent or state' self.consistency = consistency self.event = Consul.Event(self) self.kv = Consul.KV(self) self.txn = Consul.Txn(self) self.agent = Consul.Agent(self) self.catalog = Consul.Catalog(self) self.health = Consul.Health(self) self.session = Consul.Session(self) self.acl = Consul.ACL(self) self.status = Consul.Status(self) self.query = Consul.Query(self) self.coordinate = Consul.Coordinate(self) self.operator = Consul.Operator(self) class Event(object): """ The event command provides a mechanism to fire a custom user event to an entire datacenter. These events are opaque to Consul, but they can be used to build scripting infrastructure to do automated deploys, restart services, or perform any other orchestration action. Unlike most Consul data, which is replicated using consensus, event data is purely peer-to-peer over gossip. This means it is not persisted and does not have a total ordering. In practice, this means you cannot rely on the order of message delivery. An advantage however is that events can still be used even in the absence of server nodes or during an outage.""" def __init__(self, agent): self.agent = agent def fire( self, name, body="", node=None, service=None, tag=None, token=None): """ Sends an event to Consul's gossip protocol. *name* is the Consul-opaque name of the event. This can be filtered on in calls to list, below *body* is the Consul-opaque body to be delivered with the event. From the Consul documentation: The underlying gossip also sets limits on the size of a user event message. It is hard to give an exact number, as it depends on various parameters of the event, but the payload should be kept very small (< 100 bytes). Specifying too large of an event will return an error. *node*, *service*, and *tag* are regular expressions which remote agents will filter against to determine if they should store the event *token* is an optional `ACL token`_ to apply to this request. If the token's policy is not allowed to fire an event of this *name* an *ACLPermissionDenied* exception will be raised. """ assert not name.startswith('/'), \ 'keys should not start with a forward slash' params = [] if node is not None: params.append(('node', node)) if service is not None: params.append(('service', service)) if tag is not None: params.append(('tag', tag)) token = token or self.agent.token if token: params.append(('token', token)) return self.agent.http.put( CB.json(), '/v1/event/fire/%s' % name, params=params, data=body) def list( self, name=None, index=None, wait=None): """ Returns a tuple of (*index*, *events*) Note: Since Consul's event protocol uses gossip, there is no ordering, and instead index maps to the newest event that matches the query. *name* is the type of events to list, if None, lists all available. *index* is the current event Consul index, suitable for making subsequent calls to wait for changes since this query was last run. Check https://consul.io/docs/agent/http/event.html#event_list for more infos about indexes on events. *wait* the maximum duration to wait (e.g. '10s') to retrieve a given index. This parameter is only applied if *index* is also specified. the wait time by default is 5 minutes. Consul agents only buffer the most recent entries. The current buffer size is 256, but this value could change in the future. Each *event* looks like this:: { { "ID": "b54fe110-7af5-cafc-d1fb-afc8ba432b1c", "Name": "deploy", "Payload": "1609030", "NodeFilter": "", "ServiceFilter": "", "TagFilter": "", "Version": 1, "LTime": 19 }, } """ params = [] if name is not None: params.append(('name', name)) if index: params.append(('index', index)) if wait: params.append(('wait', wait)) return self.agent.http.get( CB.json(index=True, decode='Payload'), '/v1/event/list', params=params) class KV(object): """ The KV endpoint is used to expose a simple key/value store. This can be used to store service configurations or other meta data in a simple way. """ def __init__(self, agent): self.agent = agent def get( self, key, index=None, recurse=False, wait=None, token=None, consistency=None, keys=False, separator=None, dc=None): """ Returns a tuple of (*index*, *value[s]*) *index* is the current Consul index, suitable for making subsequent calls to wait for changes since this query was last run. *wait* the maximum duration to wait (e.g. '10s') to retrieve a given index. this parameter is only applied if *index* is also specified. the wait time by default is 5 minutes. *token* is an optional `ACL token`_ to apply to this request. *keys* is a boolean which, if True, says to return a flat list of keys without values or other metadata. *separator* can be used with *keys* to list keys only up to a given separator character. *dc* is the optional datacenter that you wish to communicate with. If None is provided, defaults to the agent's datacenter. The *value* returned is for the specified key, or if *recurse* is True a list of *values* for all keys with the given prefix is returned. Each *value* looks like this:: { "CreateIndex": 100, "ModifyIndex": 200, "LockIndex": 200, "Key": "foo", "Flags": 0, "Value": "bar", "Session": "adf4238a-882b-9ddc-4a9d-5b6758e4159e" } Note, if the requested key does not exists *(index, None)* is returned. It's then possible to long poll on the index for when the key is created. """ assert not key.startswith('/'), \ 'keys should not start with a forward slash' params = [] if index: params.append(('index', index)) if wait: params.append(('wait', wait)) if recurse: params.append(('recurse', '1')) token = token or self.agent.token if token: params.append(('token', token)) dc = dc or self.agent.dc if dc: params.append(('dc', dc)) if keys: params.append(('keys', True)) if separator: params.append(('separator', separator)) consistency = consistency or self.agent.consistency if consistency in ('consistent', 'stale'): params.append((consistency, '1')) one = False decode = False if not keys: decode = 'Value' if not recurse and not keys: one = True return self.agent.http.get( CB.json(index=True, decode=decode, one=one), '/v1/kv/%s' % key, params=params) def put( self, key, value, cas=None, flags=None, acquire=None, release=None, token=None, dc=None): """ Sets *key* to the given *value*. *value* can either be None (useful for marking a key as a directory) or any string type, including binary data (e.g. a msgpack'd data structure) The optional *cas* parameter is used to turn the PUT into a Check-And-Set operation. This is very useful as it allows clients to build more complex syncronization primitives on top. If the index is 0, then Consul will only put the key if it does not already exist. If the index is non-zero, then the key is only set if the index matches the ModifyIndex of that key. An optional *flags* can be set. This can be used to specify an unsigned value between 0 and 2^64-1. *acquire* is an optional session_id. if supplied a lock acquisition will be attempted. *release* is an optional session_id. if supplied a lock release will be attempted. *token* is an optional `ACL token`_ to apply to this request. If the token's policy is not allowed to write to this key an *ACLPermissionDenied* exception will be raised. *dc* is the optional datacenter that you wish to communicate with. If None is provided, defaults to the agent's datacenter. The return value is simply either True or False. If False is returned, then the update has not taken place. """ assert not key.startswith('/'), \ 'keys should not start with a forward slash' assert value is None or \ isinstance(value, (six.string_types, six.binary_type)), \ "value should be None or a string / binary data" params = [] if cas is not None: params.append(('cas', cas)) if flags is not None: params.append(('flags', flags)) if acquire: params.append(('acquire', acquire)) if release: params.append(('release', release)) token = token or self.agent.token if token: params.append(('token', token)) dc = dc or self.agent.dc if dc: params.append(('dc', dc)) return self.agent.http.put( CB.json(), '/v1/kv/%s' % key, params=params, data=value) def delete(self, key, recurse=None, cas=None, token=None, dc=None): """ Deletes a single key or if *recurse* is True, all keys sharing a prefix. *cas* is an optional flag is used to turn the DELETE into a Check-And-Set operation. This is very useful as a building block for more complex synchronization primitives. Unlike PUT, the index must be greater than 0 for Consul to take any action: a 0 index will not delete the key. If the index is non-zero, the key is only deleted if the index matches the ModifyIndex of that key. *token* is an optional `ACL token`_ to apply to this request. If the token's policy is not allowed to delete to this key an *ACLPermissionDenied* exception will be raised. *dc* is the optional datacenter that you wish to communicate with. If None is provided, defaults to the agent's datacenter. """ assert not key.startswith('/'), \ 'keys should not start with a forward slash' params = [] if recurse: params.append(('recurse', '1')) if cas is not None: params.append(('cas', cas)) token = token or self.agent.token if token: params.append(('token', token)) dc = dc or self.agent.dc if dc: params.append(('dc', dc)) return self.agent.http.delete( CB.json(), '/v1/kv/%s' % key, params=params) class Txn(object): """ The Transactions endpoints manage updates or fetches of multiple keys inside a single, atomic transaction. """ def __init__(self, agent): self.agent = agent def put(self, payload): """ Create a transaction by submitting a list of operations to apply to the KV store inside of a transaction. If any operation fails, the transaction is rolled back and none of the changes are applied. *payload* is a list of operations where each operation is a `dict` with a single key value pair, with the key specifying operation the type. An example payload of operation type "KV" is dict:: { "KV": { "Verb": "<verb>", "Key": "<key>", "Value": "<Base64-encoded blob of data>", "Flags": 0, "Index": 0, "Session": "<session id>" } } """ return self.agent.http.put(CB.json(), "/v1/txn", data=json.dumps(payload)) class Agent(object): """ The Agent endpoints are used to interact with a local Consul agent. Usually, services and checks are registered with an agent, which then takes on the burden of registering with the Catalog and performing anti-entropy to recover from outages. """ def __init__(self, agent): self.agent = agent self.service = Consul.Agent.Service(agent) self.check = Consul.Agent.Check(agent) def self(self): """ Returns configuration of the local agent and member information. """ return self.agent.http.get(CB.json(), '/v1/agent/self') def services(self): """ Returns all the services that are registered with the local agent. These services were either provided through configuration files, or added dynamically using the HTTP API. It is important to note that the services known by the agent may be different than those reported by the Catalog. This is usually due to changes being made while there is no leader elected. The agent performs active anti-entropy, so in most situations everything will be in sync within a few seconds. """ return self.agent.http.get(CB.json(), '/v1/agent/services') def checks(self): """ Returns all the checks that are registered with the local agent. These checks were either provided through configuration files, or added dynamically using the HTTP API. Similar to services, the checks known by the agent may be different than those reported by the Catalog. This is usually due to changes being made while there is no leader elected. The agent performs active anti-entropy, so in most situations everything will be in sync within a few seconds. """ return self.agent.http.get(CB.json(), '/v1/agent/checks') def members(self, wan=False): """ Returns all the members that this agent currently sees. This may vary by agent, use the nodes api of Catalog to retrieve a cluster wide consistent view of members. For agents running in server mode, setting *wan* to *True* returns the list of WAN members instead of the LAN members which is default. """ params = [] if wan: params.append(('wan', 1)) return self.agent.http.get( CB.json(), '/v1/agent/members', params=params) def maintenance(self, enable, reason=None): """ The node maintenance endpoint can place the agent into "maintenance mode". *enable* is either 'true' or 'false'. 'true' enables maintenance mode, 'false' disables maintenance mode. *reason* is an optional string. This is simply to aid human operators. """ params = [] params.append(('enable', enable)) if reason: params.append(('reason', reason)) return self.agent.http.put( CB.bool(), '/v1/agent/maintenance', params=params) def join(self, address, wan=False): """ This endpoint instructs the agent to attempt to connect to a given address. *address* is the ip to connect to. *wan* is either 'true' or 'false'. For agents running in server mode, 'true' causes the agent to attempt to join using the WAN pool. Default is 'false'. """ params = [] if wan: params.append(('wan', 1)) return self.agent.http.put( CB.bool(), '/v1/agent/join/%s' % address, params=params) def force_leave(self, node): """ This endpoint instructs the agent to force a node into the left state. If a node fails unexpectedly, then it will be in a failed state. Once in the failed state, Consul will attempt to reconnect, and the services and checks belonging to that node will not be cleaned up. Forcing a node into the left state allows its old entries to be removed. *node* is the node to change state for. """ return self.agent.http.put( CB.bool(), '/v1/agent/force-leave/%s' % node) class Service(object): def __init__(self, agent): self.agent = agent def register( self, name, service_id=None, address=None, port=None, tags=None, check=None, token=None, meta=None, # *deprecated* use check parameter script=None, interval=None, ttl=None, http=None, timeout=None, enable_tag_override=False): """ Add a new service to the local agent. There is more documentation on services `here <http://www.consul.io/docs/agent/services.html>`_. *name* is the name of the service. If the optional *service_id* is not provided it is set to *name*. You cannot have duplicate *service_id* entries per agent, so it may be necessary to provide one. *address* will default to the address of the agent if not provided. An optional health *check* can be created for this service is one of `Check.script`_, `Check.http`_, `Check.tcp`_, `Check.ttl`_ or `Check.docker`_. *token* is an optional `ACL token`_ to apply to this request. Note this call will return successful even if the token doesn't have permissions to register this service. *meta* specifies arbitrary KV metadata linked to the service formatted as {k1:v1, k2:v2}. *script*, *interval*, *ttl*, *http*, and *timeout* arguments are deprecated. use *check* instead. *enable_tag_override* is an optional bool that enable you to modify a service tags from servers(consul agent role server) Default is set to False. This option is only for >=v0.6.0 version on both agent and servers. for more information https://www.consul.io/docs/agent/services.html """ payload = {} payload['name'] = name if enable_tag_override: payload['enabletagoverride'] = enable_tag_override if service_id: payload['id'] = service_id if address: payload['address'] = address if port: payload['port'] = port if tags: payload['tags'] = tags if meta: payload['meta'] = meta if check: payload['check'] = check else: payload.update(Check._compat( script=script, interval=interval, ttl=ttl, http=http, timeout=timeout)) params = [] token = token or self.agent.token if token: params.append(('token', token)) return self.agent.http.put( CB.bool(), '/v1/agent/service/register', params=params, data=json.dumps(payload)) def deregister(self, service_id): """ Used to remove a service from the local agent. The agent will take care of deregistering the service with the Catalog. If there is an associated check, that is also deregistered. """ return self.agent.http.put( CB.bool(), '/v1/agent/service/deregister/%s' % service_id) def maintenance(self, service_id, enable, reason=None): """ The service maintenance endpoint allows placing a given service into "maintenance mode". *service_id* is the id of the service that is to be targeted for maintenance. *enable* is either 'true' or 'false'. 'true' enables maintenance mode, 'false' disables maintenance mode. *reason* is an optional string. This is simply to aid human operators. """ params = [] params.append(('enable', enable)) if reason: params.append(('reason', reason)) return self.agent.http.put( CB.bool(), '/v1/agent/service/maintenance/{0}'.format(service_id), params=params) class Check(object): def __init__(self, agent): self.agent = agent def register( self, name, check=None, check_id=None, notes=None, service_id=None, token=None, # *deprecated* use check parameter script=None, interval=None, ttl=None, http=None, timeout=None): """ Register a new check with the local agent. More documentation on checks can be found `here <http://www.consul.io/docs/agent/checks.html>`_. *name* is the name of the check. *check* is one of `Check.script`_, `Check.http`_, `Check.tcp`_ `Check.ttl`_ or `Check.docker`_ and is required. If the optional *check_id* is not provided it is set to *name*. *check_id* must be unique for this agent. *notes* is not used by Consul, and is meant to be human readable. Optionally, a *service_id* can be specified to associate a registered check with an existing service. *token* is an optional `ACL token`_ to apply to this request. Note this call will return successful even if the token doesn't have permissions to register this check. *script*, *interval*, *ttl*, *http*, and *timeout* arguments are deprecated. use *check* instead. Returns *True* on success. """ payload = {'name': name} assert check or script or ttl or http, \ 'check is required' if check: payload.update(check) else: payload.update(Check._compat( script=script, interval=interval, ttl=ttl, http=http, timeout=timeout)['check']) if check_id: payload['id'] = check_id if notes: payload['notes'] = notes if service_id: payload['serviceid'] = service_id params = [] token = token or self.agent.token if token: params.append(('token', token)) return self.agent.http.put( CB.bool(), '/v1/agent/check/register', params=params, data=json.dumps(payload)) def deregister(self, check_id): """ Remove a check from the local agent. """ return self.agent.http.put( CB.bool(), '/v1/agent/check/deregister/%s' % check_id) def ttl_pass(self, check_id, notes=None): """ Mark a ttl based check as passing. Optional notes can be attached to describe the status of the check. """ params = [] if notes: params.append(('note', notes)) return self.agent.http.put( CB.bool(), '/v1/agent/check/pass/%s' % check_id, params=params) def ttl_fail(self, check_id, notes=None): """ Mark a ttl based check as failing. Optional notes can be attached to describe why check is failing. The status of the check will be set to critical and the ttl clock will be reset. """ params = [] if notes: params.append(('note', notes)) return self.agent.http.put( CB.bool(), '/v1/agent/check/fail/%s' % check_id, params=params) def ttl_warn(self, check_id, notes=None): """ Mark a ttl based check with warning. Optional notes can be attached to describe the warning. The status of the check will be set to warn and the ttl clock will be reset. """ params = [] if notes: params.append(('note', notes)) return self.agent.http.put( CB.bool(), '/v1/agent/check/warn/%s' % check_id, params=params) class Catalog(object): def __init__(self, agent): self.agent = agent def register(self, node, address, service=None, check=None, dc=None, token=None, node_meta=None): """ A low level mechanism for directly registering or updating entries in the catalog. It is usually recommended to use agent.service.register and agent.check.register, as they are simpler and perform anti-entropy. *node* is the name of the node to register. *address* is the ip of the node. *service* is an optional service to register. if supplied this is a dict:: { "Service": "redis", "ID": "redis1", "Tags": [ "master", "v1" ], "Port": 8000 } where *Service* is required and is the name of the service *ID* is optional, and will be set to *Service* if not provided. Note *ID* must be unique for the given *node*. *Tags* and *Port* are optional. *check* is an optional check to register. if supplied this is a dict:: { "Node": "foobar", "CheckID": "service:redis1", "Name": "Redis health check", "Notes": "Script based health check", "Status": "passing", "ServiceID": "redis1" } *dc* is the datacenter of the node and defaults to this agents datacenter. *token* is an optional `ACL token`_ to apply to this request. *node_meta* is an optional meta data used for filtering, a dictionary formatted as {k1:v1, k2:v2}. This manipulates the health check entry, but does not setup a script or TTL to actually update the status. The full documentation is `here <https://consul.io/docs/agent/http.html#catalog>`_. Returns *True* on success. """ data = {'node': node, 'address': address} params = [] dc = dc or self.agent.dc if dc: data['datacenter'] = dc if service: data['service'] = service if check: data['check'] = check token = token or self.agent.token if token: data['WriteRequest'] = {'Token': token} params.append(('token', token)) if node_meta: for nodemeta_name, nodemeta_value in node_meta.items(): params.append(('node-meta', '{0}:{1}'. format(nodemeta_name, nodemeta_value))) return self.agent.http.put( CB.bool(), '/v1/catalog/register', data=json.dumps(data), params=params) def deregister(self, node, service_id=None, check_id=None, dc=None, token=None): """ A low level mechanism for directly removing entries in the catalog. It is usually recommended to use the agent APIs, as they are simpler and perform anti-entropy. *node* and *dc* specify which node on which datacenter to remove. If *service_id* and *check_id* are not provided, all associated services and checks are deleted. Otherwise only one of *service_id* and *check_id* should be provided and only that service or check will be removed. *token* is an optional `ACL token`_ to apply to this request. Returns *True* on success. """ assert not (service_id and check_id) data = {'node': node} dc = dc or self.agent.dc if dc: data['datacenter'] = dc if service_id: data['serviceid'] = service_id if check_id: data['checkid'] = check_id token = token or self.agent.token if token: data['WriteRequest'] = {'Token': token} return self.agent.http.put( CB.bool(), '/v1/catalog/deregister', data=json.dumps(data)) def datacenters(self): """ Returns all the datacenters that are known by the Consul server. """ return self.agent.http.get( CB.json(), '/v1/catalog/datacenters') def nodes( self, index=None, wait=None, consistency=None, dc=None, near=None, token=None, node_meta=None): """ Returns a tuple of (*index*, *nodes*) of all nodes known about in the *dc* datacenter. *dc* defaults to the current datacenter of this agent. *index* is the current Consul index, suitable for making subsequent calls to wait for changes since this query was last run. *wait* the maximum duration to wait (e.g. '10s') to retrieve a given index. this parameter is only applied if *index* is also specified. the wait time by default is 5 minutes. *near* is a node name to sort the resulting list in ascending order based on the estimated round trip time from that node *consistency* can be either 'default', 'consistent' or 'stale'. if not specified *consistency* will the consistency level this client was configured with. *token* is an optional `ACL token`_ to apply to this request. *node_meta* is an optional meta data used for filtering, a dictionary formatted as {k1:v1, k2:v2}. The response looks like this:: (index, [ { "Node": "baz", "Address": "10.1.10.11" }, { "Node": "foobar", "Address": "10.1.10.12" } ]) """ params = [] dc = dc or self.agent.dc if dc: params.append(('dc', dc)) if index: params.append(('index', index)) if wait: params.append(('wait', wait)) if near: params.append(('near', near)) token = token or self.agent.token if token: params.append(('token', token)) consistency = consistency or self.agent.consistency if consistency in ('consistent', 'stale'): params.append((consistency, '1')) if node_meta: for nodemeta_name, nodemeta_value in node_meta.items(): params.append(('node-meta', '{0}:{1}'. format(nodemeta_name, nodemeta_value))) return self.agent.http.get( CB.json(index=True), '/v1/catalog/nodes', params=params) def services(self, index=None, wait=None, consistency=None, dc=None, token=None, node_meta=None): """ Returns a tuple of (*index*, *services*) of all services known about in the *dc* datacenter. *dc* defaults to the current datacenter of this agent. *index* is the current Consul index, suitable for making subsequent calls to wait for changes since this query was last run. *wait* the maximum duration to wait (e.g. '10s') to retrieve a given index. this parameter is only applied if *index* is also specified. the wait time by default is 5 minutes. *consistency* can be either 'default', 'consistent' or 'stale'. if not specified *consistency* will the consistency level this client was configured with. *token* is an optional `ACL token`_ to apply to this request. *node_meta* is an optional meta data used for filtering, a dictionary formatted as {k1:v1, k2:v2}. The response looks like this:: (index, { "consul": [], "redis": [], "postgresql": [ "master", "slave" ] }) The main keys are the service names and the list provides all the known tags for a given service. """ params = [] dc = dc or self.agent.dc if dc: params.append(('dc', dc)) if index: params.append(('index', index)) if wait: params.append(('wait', wait)) token = token or self.agent.token if token: params.append(('token', token)) consistency = consistency or self.agent.consistency if consistency in ('consistent', 'stale'): params.append((consistency, '1')) if node_meta: for nodemeta_name, nodemeta_value in node_meta.items(): params.append(('node-meta', '{0}:{1}'. format(nodemeta_name, nodemeta_value))) return self.agent.http.get( CB.json(index=True), '/v1/catalog/services', params=params) def node(self, node, index=None, wait=None, consistency=None, dc=None, token=None): """ Returns a tuple of (*index*, *services*) of all services provided by *node*. *index* is the current Consul index, suitable for making subsequent calls to wait for changes since this query was last run. *wait* the maximum duration to wait (e.g. '10s') to retrieve a given index. this parameter is only applied if *index* is also specified. the wait time by default is 5 minutes. *consistency* can be either 'default', 'consistent' or 'stale'. if not specified *consistency* will the consistency level this client was configured with. *dc* is the datacenter of the node and defaults to this agents datacenter. *token* is an optional `ACL token`_ to apply to this request. The response looks like this:: (index, { "Node": { "Node": "foobar", "Address": "10.1.10.12" }, "Services": { "consul": { "ID": "consul", "Service": "consul", "Tags": null, "Port": 8300 }, "redis": { "ID": "redis", "Service": "redis", "Tags": [ "v1" ], "Port": 8000 } } }) """ params = [] dc = dc or self.agent.dc if dc: params.append(('dc', dc)) if index: params.append(('index', index)) if wait: params.append(('wait', wait)) token = token or self.agent.token if token: params.append(('token', token)) consistency = consistency or self.agent.consistency if consistency in ('consistent', 'stale'): params.append((consistency, '1')) return self.agent.http.get( CB.json(index=True), '/v1/catalog/node/%s' % node, params=params) def service( self, service, index=None, wait=None, tag=None, consistency=None, dc=None, near=None, token=None, node_meta=None): """ Returns a tuple of (*index*, *nodes*) of the nodes providing *service* in the *dc* datacenter. *dc* defaults to the current datacenter of this agent. *index* is the current Consul index, suitable for making subsequent calls to wait for changes since this query was last run. *wait* the maximum duration to wait (e.g. '10s') to retrieve a given index. this parameter is only applied if *index* is also specified. the wait time by default is 5 minutes. If *tag* is provided, the list of nodes returned will be filtered by that tag. *near* is a node name to sort the resulting list in ascending order based on the estimated round trip time from that node *consistency* can be either 'default', 'consistent' or 'stale'. if not specified *consistency* will the consistency level this client was configured with. *token* is an optional `ACL token`_ to apply to this request. *node_meta* is an optional meta data used for filtering, a dictionary formatted as {k1:v1, k2:v2}. The response looks like this:: (index, [ { "Node": "foobar", "Address": "10.1.10.12", "ServiceID": "redis", "ServiceName": "redis", "ServiceTags": null, "ServicePort": 8000 } ]) """ params = [] dc = dc or self.agent.dc if dc: params.append(('dc', dc)) if tag: params.append(('tag', tag)) if index: params.append(('index', index)) if wait: params.append(('wait', wait)) if near: params.append(('near', near)) token = token or self.agent.token if token: params.append(('token', token)) consistency = consistency or self.agent.consistency if consistency in ('consistent', 'stale'): params.append((consistency, '1')) if node_meta: for nodemeta_name, nodemeta_value in node_meta.items(): params.append(('node-meta', '{0}:{1}'. format(nodemeta_name, nodemeta_value))) return self.agent.http.get( CB.json(index=True), '/v1/catalog/service/%s' % service, params=params) class Health(object): # TODO: All of the health endpoints support all consistency modes def __init__(self, agent): self.agent = agent def service(self, service, index=None, wait=None, passing=None, tag=None, dc=None, near=None, token=None, node_meta=None): """ Returns a tuple of (*index*, *nodes*) *index* is the current Consul index, suitable for making subsequent calls to wait for changes since this query was last run. *wait* the maximum duration to wait (e.g. '10s') to retrieve a given index. this parameter is only applied if *index* is also specified. the wait time by default is 5 minutes. *nodes* are the nodes providing the given service. Calling with *passing* set to True will filter results to only those nodes whose checks are currently passing. Calling with *tag* will filter the results by tag. *dc* is the datacenter of the node and defaults to this agents datacenter. *near* is a node name to sort the resulting list in ascending order based on the estimated round trip time from that node *token* is an optional `ACL token`_ to apply to this request. *node_meta* is an optional meta data used for filtering, a dictionary formatted as {k1:v1, k2:v2}. """ params = [] if index: params.append(('index', index)) if wait: params.append(('wait', wait)) if passing: params.append(('passing', '1')) if tag is not None: params.append(('tag', tag)) dc = dc or self.agent.dc if dc: params.append(('dc', dc)) if near: params.append(('near', near)) token = token or self.agent.token if token: params.append(('token', token)) if node_meta: for nodemeta_name, nodemeta_value in node_meta.items(): params.append(('node-meta', '{0}:{1}'. format(nodemeta_name, nodemeta_value))) return self.agent.http.get( CB.json(index=True), '/v1/health/service/%s' % service, params=params) def checks( self, service, index=None, wait=None, dc=None, near=None, token=None, node_meta=None): """ Returns a tuple of (*index*, *checks*) with *checks* being the checks associated with the service. *service* is the name of the service being checked. *index* is the current Consul index, suitable for making subsequent calls to wait for changes since this query was last run. *wait* the maximum duration to wait (e.g. '10s') to retrieve a given index. this parameter is only applied if *index* is also specified. the wait time by default is 5 minutes. *dc* is the datacenter of the node and defaults to this agents datacenter. *near* is a node name to sort the resulting list in ascending order based on the estimated round trip time from that node *token* is an optional `ACL token`_ to apply to this request. *node_meta* is an optional meta data used for filtering, a dictionary formatted as {k1:v1, k2:v2}. """ params = [] if index: params.append(('index', index)) if wait: params.append(('wait', wait)) dc = dc or self.agent.dc if dc: params.append(('dc', dc)) if near: params.append(('near', near)) token = token or self.agent.token if token: params.append(('token', token)) if node_meta: for nodemeta_name, nodemeta_value in node_meta.items(): params.append(('node-meta', '{0}:{1}'. format(nodemeta_name, nodemeta_value))) return self.agent.http.get( CB.json(index=True), '/v1/health/checks/%s' % service, params=params) def state(self, name, index=None, wait=None, dc=None, near=None, token=None, node_meta=None): """ Returns a tuple of (*index*, *nodes*) *name* is a supported state. From the Consul docs: The supported states are any, unknown, passing, warning, or critical. The any state is a wildcard that can be used to return all checks. *index* is the current Consul index, suitable for making subsequent calls to wait for changes since this query was last run. *wait* the maximum duration to wait (e.g. '10s') to retrieve a given index. this parameter is only applied if *index* is also specified. the wait time by default is 5 minutes. *dc* is the datacenter of the node and defaults to this agents datacenter. *near* is a node name to sort the resulting list in ascending order based on the estimated round trip time from that node *token* is an optional `ACL token`_ to apply to this request. *node_meta* is an optional meta data used for filtering, a dictionary formatted as {k1:v1, k2:v2}. *nodes* are the nodes providing the given service. """ assert name in ['any', 'unknown', 'passing', 'warning', 'critical'] params = [] if index: params.append(('index', index)) if wait: params.append(('wait', wait)) dc = dc or self.agent.dc if dc: params.append(('dc', dc)) if near: params.append(('near', near)) token = token or self.agent.token if token: params.append(('token', token)) if node_meta: for nodemeta_name, nodemeta_value in node_meta.items(): params.append(('node-meta', '{0}:{1}'. format(nodemeta_name, nodemeta_value))) return self.agent.http.get( CB.json(index=True), '/v1/health/state/%s' % name, params=params) def node(self, node, index=None, wait=None, dc=None, token=None): """ Returns a tuple of (*index*, *checks*) *index* is the current Consul index, suitable for making subsequent calls to wait for changes since this query was last run. *wait* the maximum duration to wait (e.g. '10s') to retrieve a given index. this parameter is only applied if *index* is also specified. the wait time by default is 5 minutes. *dc* is the datacenter of the node and defaults to this agents datacenter. *token* is an optional `ACL token`_ to apply to this request. *nodes* are the nodes providing the given service. """ params = [] if index: params.append(('index', index)) if wait: params.append(('wait', wait)) dc = dc or self.agent.dc if dc: params.append(('dc', dc)) token = token or self.agent.token if token: params.append(('token', token)) return self.agent.http.get( CB.json(index=True), '/v1/health/node/%s' % node, params=params) class Session(object): def __init__(self, agent): self.agent = agent def create( self, name=None, node=None, checks=None, lock_delay=15, behavior='release', ttl=None, dc=None): """ Creates a new session. There is more documentation for sessions `here <https://consul.io/docs/internals/sessions.html>`_. *name* is an optional human readable name for the session. *node* is the node to create the session on. if not provided the current agent's node will be used. *checks* is a list of checks to associate with the session. if not provided it defaults to the *serfHealth* check. It is highly recommended that, if you override this list, you include the default *serfHealth*. *lock_delay* is an integer of seconds. *behavior* can be set to either 'release' or 'delete'. This controls the behavior when a session is invalidated. By default, this is 'release', causing any locks that are held to be released. Changing this to 'delete' causes any locks that are held to be deleted. 'delete' is useful for creating ephemeral key/value entries. when *ttl* is provided, the session is invalidated if it is not renewed before the TTL expires. If specified, it is an integer of seconds. Currently it must be between 10 and 86400 seconds. By default the session will be created in the current datacenter but an optional *dc* can be provided. Returns the string *session_id* for the session. """ params = [] dc = dc or self.agent.dc if dc: params.append(('dc', dc)) data = {} if name: data['name'] = name if node: data['node'] = node if checks is not None: data['checks'] = checks if lock_delay != 15: data['lockdelay'] = '%ss' % lock_delay assert behavior in ('release', 'delete'), \ 'behavior must be release or delete' if behavior != 'release': data['behavior'] = behavior if ttl: assert 10 <= ttl <= 86400 data['ttl'] = '%ss' % ttl if data: data = json.dumps(data) else: data = '' return self.agent.http.put( CB.json(is_id=True), '/v1/session/create', params=params, data=data) def destroy(self, session_id, dc=None): """ Destroys the session *session_id* Returns *True* on success. """ params = [] dc = dc or self.agent.dc if dc: params.append(('dc', dc)) return self.agent.http.put( CB.bool(), '/v1/session/destroy/%s' % session_id, params=params) def list(self, index=None, wait=None, consistency=None, dc=None): """ Returns a tuple of (*index*, *sessions*) of all active sessions in the *dc* datacenter. *dc* defaults to the current datacenter of this agent. *index* is the current Consul index, suitable for making subsequent calls to wait for changes since this query was last run. *wait* the maximum duration to wait (e.g. '10s') to retrieve a given index. this parameter is only applied if *index* is also specified. the wait time by default is 5 minutes. *consistency* can be either 'default', 'consistent' or 'stale'. if not specified *consistency* will the consistency level this client was configured with. The response looks like this:: (index, [ { "LockDelay": 1.5e+10, "Checks": [ "serfHealth" ], "Node": "foobar", "ID": "adf4238a-882b-9ddc-4a9d-5b6758e4159e", "CreateIndex": 1086449 }, ... ]) """ params = [] dc = dc or self.agent.dc if dc: params.append(('dc', dc)) if index: params.append(('index', index)) if wait: params.append(('wait', wait)) consistency = consistency or self.agent.consistency if consistency in ('consistent', 'stale'): params.append((consistency, '1')) return self.agent.http.get( CB.json(index=True), '/v1/session/list', params=params) def node(self, node, index=None, wait=None, consistency=None, dc=None): """ Returns a tuple of (*index*, *sessions*) as per *session.list*, but filters the sessions returned to only those active for *node*. *index* is the current Consul index, suitable for making subsequent calls to wait for changes since this query was last run. *wait* the maximum duration to wait (e.g. '10s') to retrieve a given index. this parameter is only applied if *index* is also specified. the wait time by default is 5 minutes. *consistency* can be either 'default', 'consistent' or 'stale'. if not specified *consistency* will the consistency level this client was configured with. """ params = [] dc = dc or self.agent.dc if dc: params.append(('dc', dc)) if index: params.append(('index', index)) if wait: params.append(('wait', wait)) consistency = consistency or self.agent.consistency if consistency in ('consistent', 'stale'): params.append((consistency, '1')) return self.agent.http.get( CB.json(index=True), '/v1/session/node/%s' % node, params=params) def info(self, session_id, index=None, wait=None, consistency=None, dc=None): """ Returns a tuple of (*index*, *session*) for the session *session_id* in the *dc* datacenter. *dc* defaults to the current datacenter of this agent. *index* is the current Consul index, suitable for making subsequent calls to wait for changes since this query was last run. *wait* the maximum duration to wait (e.g. '10s') to retrieve a given index. this parameter is only applied if *index* is also specified. the wait time by default is 5 minutes. *consistency* can be either 'default', 'consistent' or 'stale'. if not specified *consistency* will the consistency level this client was configured with. """ params = [] dc = dc or self.agent.dc if dc: params.append(('dc', dc)) if index: params.append(('index', index)) if wait: params.append(('wait', wait)) consistency = consistency or self.agent.consistency if consistency in ('consistent', 'stale'): params.append((consistency, '1')) return self.agent.http.get( CB.json(index=True, one=True), '/v1/session/info/%s' % session_id, params=params) def renew(self, session_id, dc=None): """ This is used with sessions that have a TTL, and it extends the expiration by the TTL. *dc* is the optional datacenter that you wish to communicate with. If None is provided, defaults to the agent's datacenter. Returns the session. """ params = [] dc = dc or self.agent.dc if dc: params.append(('dc', dc)) return self.agent.http.put( CB.json(one=True, allow_404=False), '/v1/session/renew/%s' % session_id, params=params) class ACL(object): def __init__(self, agent): self.agent = agent def list(self, token=None): """ Lists all the active ACL tokens. This is a privileged endpoint, and requires a management token. *token* will override this client's default token. An *ACLPermissionDenied* exception will be raised if a management token is not used. """ params = [] token = token or self.agent.token if token: params.append(('token', token)) return self.agent.http.get( CB.json(), '/v1/acl/list', params=params) def info(self, acl_id, token=None): """ Returns the token information for *acl_id*. """ params = [] token = token or self.agent.token if token: params.append(('token', token)) return self.agent.http.get( CB.json(one=True), '/v1/acl/info/%s' % acl_id, params=params) def create(self, name=None, type='client', rules=None, acl_id=None, token=None): """ Creates a new ACL token. This is a privileged endpoint, and requires a management token. *token* will override this client's default token. An *ACLPermissionDenied* exception will be raised if a management token is not used. *name* is an optional name for this token. *type* is either 'management' or 'client'. A management token is effectively like a root user, and has the ability to perform any action including creating, modifying, and deleting ACLs. A client token can only perform actions as permitted by *rules*. *rules* is an optional `HCL`_ string for this `ACL Token`_ Rule Specification. Rules look like this:: # Default all keys to read-only key "" { policy = "read" } key "foo/" { policy = "write" } key "foo/private/" { # Deny access to the private dir policy = "deny" } Returns the string *acl_id* for the new token. """ params = [] token = token or self.agent.token if token: params.append(('token', token)) payload = {} if name: payload['Name'] = name if type: assert type in ('client', 'management'), \ 'type must be client or management' payload['Type'] = type if rules: assert isinstance(rules, str), \ 'Only HCL or JSON encoded strings supported for the moment' payload['Rules'] = rules if acl_id: payload['ID'] = acl_id if payload: data = json.dumps(payload) else: data = '' return self.agent.http.put( CB.json(is_id=True), '/v1/acl/create', params=params, data=data) def update(self, acl_id, name=None, type=None, rules=None, token=None): """ Updates the ACL token *acl_id*. This is a privileged endpoint, and requires a management token. *token* will override this client's default token. An *ACLPermissionDenied* exception will be raised if a management token is not used. *name* is an optional name for this token. *type* is either 'management' or 'client'. A management token is effectively like a root user, and has the ability to perform any action including creating, modifying, and deleting ACLs. A client token can only perform actions as permitted by *rules*. *rules* is an optional `HCL`_ string for this `ACL Token`_ Rule Specification. Returns the string *acl_id* of this token on success. """ params = [] token = token or self.agent.token if token: params.append(('token', token)) payload = {'ID': acl_id} if name: payload['Name'] = name if type: assert type in ('client', 'management'), \ 'type must be client or management' payload['Type'] = type if rules: assert isinstance(rules, str), \ 'Only HCL or JSON encoded strings supported for the moment' payload['Rules'] = rules data = json.dumps(payload) return self.agent.http.put( CB.json(is_id=True), '/v1/acl/update', params=params, data=data) def clone(self, acl_id, token=None): """ Clones the ACL token *acl_id*. This is a privileged endpoint, and requires a management token. *token* will override this client's default token. An *ACLPermissionDenied* exception will be raised if a management token is not used. Returns the string of the newly created *acl_id*. """ params = [] token = token or self.agent.token if token: params.append(('token', token)) return self.agent.http.put( CB.json(is_id=True), '/v1/acl/clone/%s' % acl_id, params=params) def destroy(self, acl_id, token=None): """ Destroys the ACL token *acl_id*. This is a privileged endpoint, and requires a management token. *token* will override this client's default token. An *ACLPermissionDenied* exception will be raised if a management token is not used. Returns *True* on success. """ params = [] token = token or self.agent.token if token: params.append(('token', token)) return self.agent.http.put( CB.json(), '/v1/acl/destroy/%s' % acl_id, params=params) class Status(object): """ The Status endpoints are used to get information about the status of the Consul cluster. """ def __init__(self, agent): self.agent = agent def leader(self): """ This endpoint is used to get the Raft leader for the datacenter in which the agent is running. """ return self.agent.http.get(CB.json(), '/v1/status/leader') def peers(self): """ This endpoint retrieves the Raft peers for the datacenter in which the the agent is running. """ return self.agent.http.get(CB.json(), '/v1/status/peers') class Query(object): def __init__(self, agent): self.agent = agent def list(self, dc=None, token=None): """ Lists all the active queries. This is a privileged endpoint, therefore you will only be able to get the prepared queries which the token supplied has read privileges to. *dc* is the datacenter that this agent will communicate with. By default the datacenter of the host is used. *token* is an optional `ACL token`_ to apply to this request. """ params = [] token = token or self.agent.token if token: params.append(('token', token)) if dc: params.append(('dc', dc)) return self.agent.http.get(CB.json(), '/v1/query', params=params) def _query_data(self, service=None, name=None, session=None, token=None, nearestn=None, datacenters=None, onlypassing=None, tags=None, ttl=None, regexp=None): service_body = dict([ (k, v) for k, v in { 'service': service, 'onlypassing': onlypassing, 'tags': tags, 'failover': dict([ (k, v) for k, v in { 'nearestn': nearestn, 'datacenters': datacenters }.items() if v is not None ]) }.items() if v is not None ]) data = dict([ (k, v) for k, v in { 'name': name, 'session': session, 'token': token or self.agent.token, 'dns': { 'ttl': ttl } if ttl is not None else None, 'template': dict([ (k, v) for k, v in { 'type': 'name_prefix_match', 'regexp': regexp }.items() if v is not None ]), 'service': service_body }.items() if v is not None ]) return json.dumps(data) def create(self, service, name=None, dc=None, session=None, token=None, nearestn=None, datacenters=None, onlypassing=None, tags=None, ttl=None, regexp=None): """ Creates a new query. This is a privileged endpoint, and requires a management token for a certain query name.*token* will override this client's default token. *service* is mandatory for new query. represent service name to query. *name* is an optional name for this query. *dc* is the datacenter that this agent will communicate with. By default the datacenter of the host is used. *token* is an optional `ACL token`_ to apply to this request. *nearestn* if set to a value greater than zero, then the query will be forwarded to up to NearestN other datacenters based on their estimated network round trip time using Network Coordinates from the WAN gossip pool. *datacenters* is a fixed list of remote datacenters to forward the query to if there are no healthy nodes in the local datacenter. *onlypassing* controls the behavior of the query's health check filtering. *tags* is a list of service tags to filter the query results. *ttl* is a duration string that can use "s" as a suffix for seconds. It controls how the TTL is set when query results are served over DNS. *regexp* is optional for template this option is only supported in Consul 0.6.4 or later. The only option for type is name_prefix_match so if you want a query template with no regexp enter an empty string. For more information about query https://www.consul.io/docs/agent/http/query.html """ path = '/v1/query' params = None if dc is None else [('dc', dc)] data = self._query_data( service, name, session, token, nearestn, datacenters, onlypassing, tags, ttl, regexp ) return self.agent.http.post( CB.json(), path, params=params, data=data) def update(self, query_id, service=None, name=None, dc=None, session=None, token=None, nearestn=None, datacenters=None, onlypassing=None, tags=None, ttl=None, regexp=None): """ This endpoint will update a certain query *query_id* is the query id for update all the other setting remains the same as the query create method """ path = '/v1/query/%s' % query_id params = None if dc is None else [('dc', dc)] data = self._query_data( service, name, session, token, nearestn, datacenters, onlypassing, tags, ttl, regexp ) return self.agent.http.put( CB.bool(), path, params=params, data=data) def get(self, query_id, token=None, dc=None): """ This endpoint will return information about a certain query *query_id* the query id to retrieve information about *token* is an optional `ACL token`_ to apply to this request. *dc* is the datacenter that this agent will communicate with. By default the datacenter of the host is used. """ params = [] token = token or self.agent.token if token: params.append(('token', token)) if dc: params.append(('dc', dc)) return self.agent.http.get( CB.json(), '/v1/query/%s' % query_id, params=params) def delete(self, query_id, token=None, dc=None): """ This endpoint will delete certain query *query_id* the query id delete *token* is an optional `ACL token`_ to apply
{'commit': "diff --git a/consul/base.py b/consul/base.py\nindex 4dca5c4..358beba 100644\n--- a/consul/base.py\n+++ b/consul/base.py\n@@ -169,7 +169,9 @@ class Consul(object):\n                 params[consistency] = '1'\n \n             def callback(response):\n-                if response.code == 404:\n+                if response.code == 500:\n+                    raise ConsulException(response.body)\n+                elif response.code == 404:\n                     data = None\n                 else:\n                     data = json.loads(response.body)\n", 'commit_subject': 'Merge pull request #28 from monetate/kv-get-500', 'repo_name': 'cablehead/python-consul', 'committer': {'name': 'Andy Gayton', 'email': 'e3579b1e47f273529f0f929453e939a68ede9fd1@thecablelounge.com', 'time_sec': '1427156417', 'tz_offset': '-420', 'date': {'seconds': '1427156417'}}, 'commit_tree': 'c1095b0f24c297b599199a61b0db8fb2d95ac20a', 'commit_message': 'Merge pull request #28 from monetate/kv-get-500\n\nRaise ConsulException on kv GET 500 response code.', 'language_name': 'Python', 'license': 'mit', 'hunks': "@@ -169,7 +169,9 @@ class Consul(object):\n                 params[consistency] = '1'\n \n             def callback(response):\n-                if response.code == 404:\n+                if response.code == 500:\n+                    raise ConsulException(response.body)\n+                elif response.code == 404:\n                     data = None\n                 else:\n                     data = json.loads(response.body)\n", 'addition_count': 3, 'deletion_count': 1, 'src_file': 'a/consul/base.py', 'tgt_file': 'b/consul/base.py', 'file_extension': '.py', 'before_file': 'import abc\nimport collections\nimport warnings\nimport logging\nimport base64\nimport json\nimport os\n\nimport six\nfrom six.moves import urllib\n\n\nlog = logging.getLogger(__name__)\n\n\nclass ConsulException(Exception):\n    pass\n\n\nclass ACLDisabled(ConsulException):\n    pass\n\n\nclass ACLPermissionDenied(ConsulException):\n    pass\n\n\nclass NotFound(ConsulException):\n    pass\n\n\nclass Timeout(ConsulException):\n    pass\n\n\nclass BadRequest(ConsulException):\n    pass\n\n\nclass ClientError(ConsulException):\n    """Encapsulates 4xx Http error code"""\n    pass\n\n\n#\n# Convenience to define checks\n\nclass Check(object):\n    """\n    There are three different kinds of checks: script, http and ttl\n    """\n    @classmethod\n    def script(klass, args, interval):\n        """\n        Run the script *args* every *interval* (e.g. "10s") to peform health\n        check\n        """\n        if isinstance(args, six.string_types) \\\n                or isinstance(args, six.binary_type):\n            warnings.warn(\n                "Check.script should take a list of args", DeprecationWarning)\n            args = ["sh", "-c", args]\n        return {\'args\': args, \'interval\': interval}\n\n    @classmethod\n    def http(klass, url, interval, timeout=None, deregister=None, header=None):\n        """\n        Peform a HTTP GET against *url* every *interval* (e.g. "10s") to peform\n        health check with an optional *timeout* and optional *deregister* after\n        which a failing service will be automatically deregistered. Optional\n        parameter *header* specifies headers sent in HTTP request. *header*\n        paramater is in form of map of lists of strings,\n        e.g. {"x-foo": ["bar", "baz"]}.\n        """\n        ret = {\'http\': url, \'interval\': interval}\n        if timeout:\n            ret[\'timeout\'] = timeout\n        if deregister:\n            ret[\'DeregisterCriticalServiceAfter\'] = deregister\n        if header:\n            ret[\'header\'] = header\n        return ret\n\n    @classmethod\n    def tcp(klass, host, port, interval, timeout=None, deregister=None):\n        """\n        Attempt to establish a tcp connection to the specified *host* and\n        *port* at a specified *interval* with optional *timeout* and optional\n        *deregister* after which a failing service will be automatically\n        deregistered.\n        """\n        ret = {\n            \'tcp\': \'{host:s}:{port:d}\'.format(host=host, port=port),\n            \'interval\': interval\n        }\n        if timeout:\n            ret[\'timeout\'] = timeout\n        if deregister:\n            ret[\'DeregisterCriticalServiceAfter\'] = deregister\n        return ret\n\n    @classmethod\n    def ttl(klass, ttl):\n        """\n        Set check to be marked as critical after *ttl* (e.g. "10s") unless the\n        check is periodically marked as passing.\n        """\n        return {\'ttl\': ttl}\n\n    @classmethod\n    def docker(klass, container_id, shell, script, interval, deregister=None):\n        """\n        Invoke *script* packaged within a running docker container with\n        *container_id* at a specified *interval* on the configured\n        *shell* using the Docker Exec API.  Optional *register* after which a\n        failing service will be automatically deregistered.\n        """\n        ret = {\n            \'docker_container_id\': container_id,\n            \'shell\': shell,\n            \'script\': script,\n            \'interval\': interval\n        }\n        if deregister:\n            ret[\'DeregisterCriticalServiceAfter\'] = deregister\n        return ret\n\n    @classmethod\n    def _compat(\n            self,\n            script=None,\n            interval=None,\n            ttl=None,\n            http=None,\n            timeout=None,\n            deregister=None):\n\n        if not script and not http and not ttl:\n            return {}\n\n        log.warn(\n            \'DEPRECATED: use consul.Check.script/http/ttl to specify check\')\n\n        ret = {\'check\': {}}\n\n        if script:\n            assert interval and not (ttl or http)\n            ret[\'check\'] = {\'script\': script, \'interval\': interval}\n        if ttl:\n            assert not (interval or script or http)\n            ret[\'check\'] = {\'ttl\': ttl}\n        if http:\n            assert interval and not (script or ttl)\n            ret[\'check\'] = {\'http\': http, \'interval\': interval}\n        if timeout:\n            assert http\n            ret[\'check\'][\'timeout\'] = timeout\n\n        if deregister:\n            ret[\'check\'][\'DeregisterCriticalServiceAfter\'] = deregister\n\n        return ret\n\n\nResponse = collections.namedtuple(\'Response\', [\'code\', \'headers\', \'body\'])\n\n\n#\n                params[consistency] = \'1\'\n\n            def callback(response):\n                if response.code == 404:\n                    data = None\n                else:\n                    data = json.loads(response.body)\n            elif response.code == 401:\n                raise ACLDisabled(response.body)\n            elif response.code == 403:\n                raise ACLPermissionDenied(response.body)\n            elif response.code == 404:\n                if not allow_404:\n                    raise NotFound(response.body)\n            else:\n                raise ClientError("%d %s" % (response.code, response.body))\n        elif 500 <= response.code < 600:\n            raise ConsulException("%d %s" % (response.code, response.body))\n\n    @classmethod\n    def bool(klass):\n        # returns True on successful response\n        def cb(response):\n            CB._status(response)\n            return response.code == 200\n        return cb\n\n    @classmethod\n    def json(\n            klass,\n            map=None,\n            allow_404=True,\n            one=False,\n            decode=False,\n            is_id=False,\n            index=False):\n        """\n        *map* is a function to apply to the final result.\n\n        *allow_404* if set, None will be returned on 404, instead of raising\n        NotFound.\n\n        *index* if set, a tuple of index, data will be returned.\n\n        *one* returns only the first item of the list of items. empty lists are\n        coerced to None.\n\n        *decode* if specified this key will be base64 decoded.\n\n        *is_id* only the \'ID\' field of the json object will be returned.\n        """\n        def cb(response):\n            CB._status(response, allow_404=allow_404)\n            if response.code == 404:\n                return response.headers[\'X-Consul-Index\'], None\n\n            data = json.loads(response.body)\n\n            if decode:\n                for item in data:\n                    if item.get(decode) is not None:\n                        item[decode] = base64.b64decode(item[decode])\n            if is_id:\n                data = data[\'ID\']\n            if one:\n                if data == []:\n                    data = None\n                if data is not None:\n                    data = data[0]\n            if map:\n                data = map(data)\n            if index:\n                return response.headers[\'X-Consul-Index\'], data\n            return data\n        return cb\n\n\nclass HTTPClient(six.with_metaclass(abc.ABCMeta, object)):\n    def __init__(self, host=\'127.0.0.1\', port=8500, scheme=\'http\',\n                 verify=True, cert=None):\n        self.host = host\n        self.port = port\n        self.scheme = scheme\n        self.verify = verify\n        self.base_uri = \'%s://%s:%s\' % (self.scheme, self.host, self.port)\n        self.cert = cert\n\n    def uri(self, path, params=None):\n        uri = self.base_uri + urllib.parse.quote(path, safe=\'/:\')\n        if params:\n            uri = \'%s?%s\' % (uri, urllib.parse.urlencode(params))\n        return uri\n\n    @abc.abstractmethod\n    def get(self, callback, path, params=None):\n        raise NotImplementedError\n\n    @abc.abstractmethod\n    def put(self, callback, path, params=None, data=\'\'):\n        raise NotImplementedError\n\n    @abc.abstractmethod\n    def delete(self, callback, path, params=None):\n        raise NotImplementedError\n\n    @abc.abstractmethod\n    def post(self, callback, path, params=None, data=\'\'):\n        raise NotImplementedError\n\n\nclass Consul(object):\n    def __init__(\n            self,\n            host=\'127.0.0.1\',\n            port=8500,\n            token=None,\n            scheme=\'http\',\n            consistency=\'default\',\n            dc=None,\n            verify=True,\n            cert=None):\n        """\n        *token* is an optional `ACL token`_. If supplied it will be used by\n        default for all requests made with this client session. It\'s still\n        possible to override this token by passing a token explicitly for a\n        request.\n\n        *consistency* sets the consistency mode to use by default for all reads\n        that support the consistency option. It\'s still possible to override\n        this by passing explicitly for a given request. *consistency* can be\n        either \'default\', \'consistent\' or \'stale\'.\n\n        *dc* is the datacenter that this agent will communicate with.\n        By default the datacenter of the host is used.\n\n        *verify* is whether to verify the SSL certificate for HTTPS requests\n\n        *cert* client side certificates for HTTPS requests\n        """\n\n        # TODO: Status\n\n        if os.getenv(\'CONSUL_HTTP_ADDR\'):\n            try:\n                host, port = os.getenv(\'CONSUL_HTTP_ADDR\').split(\':\')\n            except ValueError:\n                raise ConsulException(\'CONSUL_HTTP_ADDR (%s) invalid, \'\n                                      \'does not match <host>:<port>\'\n                                      % os.getenv(\'CONSUL_HTTP_ADDR\'))\n        use_ssl = os.getenv(\'CONSUL_HTTP_SSL\')\n        if use_ssl is not None:\n            scheme = \'https\' if use_ssl == \'true\' else \'http\'\n        if os.getenv(\'CONSUL_HTTP_SSL_VERIFY\') is not None:\n            verify = os.getenv(\'CONSUL_HTTP_SSL_VERIFY\') == \'true\'\n\n        self.http = self.connect(host, port, scheme, verify, cert)\n        self.token = os.getenv(\'CONSUL_HTTP_TOKEN\', token)\n        self.scheme = scheme\n        self.dc = dc\n        assert consistency in (\'default\', \'consistent\', \'stale\'), \\\n            \'consistency must be either default, consistent or state\'\n        self.consistency = consistency\n\n        self.event = Consul.Event(self)\n        self.kv = Consul.KV(self)\n        self.txn = Consul.Txn(self)\n        self.agent = Consul.Agent(self)\n        self.catalog = Consul.Catalog(self)\n        self.health = Consul.Health(self)\n        self.session = Consul.Session(self)\n        self.acl = Consul.ACL(self)\n        self.status = Consul.Status(self)\n        self.query = Consul.Query(self)\n        self.coordinate = Consul.Coordinate(self)\n        self.operator = Consul.Operator(self)\n\n    class Event(object):\n        """\n        The event command provides a mechanism to fire a custom user event to\n        an entire datacenter. These events are opaque to Consul, but they can\n        be used to build scripting infrastructure to do automated deploys,\n        restart services, or perform any other orchestration action.\n\n        Unlike most Consul data, which is replicated using consensus, event\n        data is purely peer-to-peer over gossip.\n\n        This means it is not persisted and does not have a total ordering. In\n        practice, this means you cannot rely on the order of message delivery.\n        An advantage however is that events can still be used even in the\n        absence of server nodes or during an outage."""\n        def __init__(self, agent):\n            self.agent = agent\n\n        def fire(\n                self,\n                name,\n                body="",\n                node=None,\n                service=None,\n                tag=None,\n                token=None):\n            """\n            Sends an event to Consul\'s gossip protocol.\n\n            *name* is the Consul-opaque name of the event. This can be filtered\n            on in calls to list, below\n\n            *body* is the Consul-opaque body to be delivered with the event.\n             From the Consul documentation:\n                The underlying gossip also sets limits on the size of a user\n                event message. It is hard to give an exact number, as it\n                depends on various parameters of the event, but the payload\n                should be kept very small (< 100 bytes). Specifying too large\n                of an event will return an error.\n\n            *node*, *service*, and *tag* are regular expressions which remote\n            agents will filter against to determine if they should store the\n            event\n\n            *token* is an optional `ACL token`_ to apply to this request. If\n            the token\'s policy is not allowed to fire an event of this *name*\n            an *ACLPermissionDenied* exception will be raised.\n            """\n            assert not name.startswith(\'/\'), \\\n                \'keys should not start with a forward slash\'\n            params = []\n            if node is not None:\n                params.append((\'node\', node))\n            if service is not None:\n                params.append((\'service\', service))\n            if tag is not None:\n                params.append((\'tag\', tag))\n            token = token or self.agent.token\n            if token:\n                params.append((\'token\', token))\n\n            return self.agent.http.put(\n                CB.json(),\n                \'/v1/event/fire/%s\' % name, params=params, data=body)\n\n        def list(\n                self,\n                name=None,\n                index=None,\n                wait=None):\n            """\n            Returns a tuple of (*index*, *events*)\n                Note: Since Consul\'s event protocol uses gossip, there is no\n                ordering, and instead index maps to the newest event that\n                matches the query.\n\n            *name* is the type of events to list, if None, lists all available.\n\n            *index* is the current event Consul index, suitable for making\n            subsequent calls to wait for changes since this query was last run.\n            Check https://consul.io/docs/agent/http/event.html#event_list for\n            more infos about indexes on events.\n\n            *wait* the maximum duration to wait (e.g. \'10s\') to retrieve\n            a given index. This parameter is only applied if *index* is also\n            specified. the wait time by default is 5 minutes.\n\n            Consul agents only buffer the most recent entries. The current\n            buffer size is 256, but this value could change in the future.\n\n            Each *event* looks like this::\n\n                {\n                      {\n                        "ID": "b54fe110-7af5-cafc-d1fb-afc8ba432b1c",\n                        "Name": "deploy",\n                        "Payload": "1609030",\n                        "NodeFilter": "",\n                        "ServiceFilter": "",\n                        "TagFilter": "",\n                        "Version": 1,\n                        "LTime": 19\n                      },\n                }\n            """\n            params = []\n            if name is not None:\n                params.append((\'name\', name))\n            if index:\n                params.append((\'index\', index))\n                if wait:\n                    params.append((\'wait\', wait))\n            return self.agent.http.get(\n                CB.json(index=True, decode=\'Payload\'),\n                \'/v1/event/list\', params=params)\n\n    class KV(object):\n        """\n        The KV endpoint is used to expose a simple key/value store. This can be\n        used to store service configurations or other meta data in a simple\n        way.\n        """\n        def __init__(self, agent):\n            self.agent = agent\n\n        def get(\n                self,\n                key,\n                index=None,\n                recurse=False,\n                wait=None,\n                token=None,\n                consistency=None,\n                keys=False,\n                separator=None,\n                dc=None):\n            """\n            Returns a tuple of (*index*, *value[s]*)\n\n            *index* is the current Consul index, suitable for making subsequent\n            calls to wait for changes since this query was last run.\n\n            *wait* the maximum duration to wait (e.g. \'10s\') to retrieve\n            a given index. this parameter is only applied if *index* is also\n            specified. the wait time by default is 5 minutes.\n\n            *token* is an optional `ACL token`_ to apply to this request.\n\n            *keys* is a boolean which, if True, says to return a flat list of\n            keys without values or other metadata. *separator* can be used\n            with *keys* to list keys only up to a given separator character.\n\n            *dc* is the optional datacenter that you wish to communicate with.\n            If None is provided, defaults to the agent\'s datacenter.\n\n            The *value* returned is for the specified key, or if *recurse* is\n            True a list of *values* for all keys with the given prefix is\n            returned.\n\n            Each *value* looks like this::\n\n                {\n                    "CreateIndex": 100,\n                    "ModifyIndex": 200,\n                    "LockIndex": 200,\n                    "Key": "foo",\n                    "Flags": 0,\n                    "Value": "bar",\n                    "Session": "adf4238a-882b-9ddc-4a9d-5b6758e4159e"\n                }\n\n            Note, if the requested key does not exists *(index, None)* is\n            returned. It\'s then possible to long poll on the index for when the\n            key is created.\n            """\n            assert not key.startswith(\'/\'), \\\n                \'keys should not start with a forward slash\'\n            params = []\n            if index:\n                params.append((\'index\', index))\n                if wait:\n                    params.append((\'wait\', wait))\n            if recurse:\n                params.append((\'recurse\', \'1\'))\n            token = token or self.agent.token\n            if token:\n                params.append((\'token\', token))\n            dc = dc or self.agent.dc\n            if dc:\n                params.append((\'dc\', dc))\n            if keys:\n                params.append((\'keys\', True))\n            if separator:\n                params.append((\'separator\', separator))\n            consistency = consistency or self.agent.consistency\n            if consistency in (\'consistent\', \'stale\'):\n                params.append((consistency, \'1\'))\n\n            one = False\n            decode = False\n\n            if not keys:\n                decode = \'Value\'\n            if not recurse and not keys:\n                one = True\n            return self.agent.http.get(\n                CB.json(index=True, decode=decode, one=one),\n                \'/v1/kv/%s\' % key,\n                params=params)\n\n        def put(\n                self,\n                key,\n                value,\n                cas=None,\n                flags=None,\n                acquire=None,\n                release=None,\n                token=None,\n                dc=None):\n            """\n            Sets *key* to the given *value*.\n\n            *value* can either be None (useful for marking a key as a\n            directory) or any string type, including binary data (e.g. a\n            msgpack\'d data structure)\n\n            The optional *cas* parameter is used to turn the PUT into a\n            Check-And-Set operation. This is very useful as it allows clients\n            to build more complex syncronization primitives on top. If the\n            index is 0, then Consul will only put the key if it does not\n            already exist. If the index is non-zero, then the key is only set\n            if the index matches the ModifyIndex of that key.\n\n            An optional *flags* can be set. This can be used to specify an\n            unsigned value between 0 and 2^64-1.\n\n            *acquire* is an optional session_id. if supplied a lock acquisition\n            will be attempted.\n\n            *release* is an optional session_id. if supplied a lock release\n            will be attempted.\n\n            *token* is an optional `ACL token`_ to apply to this request. If\n            the token\'s policy is not allowed to write to this key an\n            *ACLPermissionDenied* exception will be raised.\n\n            *dc* is the optional datacenter that you wish to communicate with.\n            If None is provided, defaults to the agent\'s datacenter.\n\n            The return value is simply either True or False. If False is\n            returned, then the update has not taken place.\n            """\n            assert not key.startswith(\'/\'), \\\n                \'keys should not start with a forward slash\'\n            assert value is None or \\\n                isinstance(value, (six.string_types, six.binary_type)), \\\n                "value should be None or a string / binary data"\n\n            params = []\n            if cas is not None:\n                params.append((\'cas\', cas))\n            if flags is not None:\n                params.append((\'flags\', flags))\n            if acquire:\n                params.append((\'acquire\', acquire))\n            if release:\n                params.append((\'release\', release))\n            token = token or self.agent.token\n            if token:\n                params.append((\'token\', token))\n            dc = dc or self.agent.dc\n            if dc:\n                params.append((\'dc\', dc))\n            return self.agent.http.put(\n                CB.json(), \'/v1/kv/%s\' % key, params=params, data=value)\n\n        def delete(self, key, recurse=None, cas=None, token=None, dc=None):\n            """\n            Deletes a single key or if *recurse* is True, all keys sharing a\n            prefix.\n\n            *cas* is an optional flag is used to turn the DELETE into a\n            Check-And-Set operation. This is very useful as a building block\n            for more complex synchronization primitives. Unlike PUT, the index\n            must be greater than 0 for Consul to take any action: a 0 index\n            will not delete the key. If the index is non-zero, the key is only\n            deleted if the index matches the ModifyIndex of that key.\n\n            *token* is an optional `ACL token`_ to apply to this request. If\n            the token\'s policy is not allowed to delete to this key an\n            *ACLPermissionDenied* exception will be raised.\n\n            *dc* is the optional datacenter that you wish to communicate with.\n            If None is provided, defaults to the agent\'s datacenter.\n            """\n            assert not key.startswith(\'/\'), \\\n                \'keys should not start with a forward slash\'\n\n            params = []\n            if recurse:\n                params.append((\'recurse\', \'1\'))\n            if cas is not None:\n                params.append((\'cas\', cas))\n            token = token or self.agent.token\n            if token:\n                params.append((\'token\', token))\n            dc = dc or self.agent.dc\n            if dc:\n                params.append((\'dc\', dc))\n\n            return self.agent.http.delete(\n                CB.json(), \'/v1/kv/%s\' % key, params=params)\n\n    class Txn(object):\n        """\n        The Transactions endpoints manage updates or fetches of multiple keys\n        inside a single, atomic transaction.\n        """\n        def __init__(self, agent):\n            self.agent = agent\n\n        def put(self, payload):\n            """\n            Create a transaction by submitting a list of operations to apply to\n            the KV store inside of a transaction. If any operation fails, the\n            transaction is rolled back and none of the changes are applied.\n\n            *payload* is a list of operations where each operation is a `dict`\n            with a single key value pair, with the key specifying operation the\n            type. An example payload of operation type "KV" is\n            dict::\n\n                {\n                    "KV": {\n                      "Verb": "<verb>",\n                      "Key": "<key>",\n                      "Value": "<Base64-encoded blob of data>",\n                      "Flags": 0,\n                      "Index": 0,\n                      "Session": "<session id>"\n                    }\n                }\n            """\n            return self.agent.http.put(CB.json(), "/v1/txn",\n                                       data=json.dumps(payload))\n\n    class Agent(object):\n        """\n        The Agent endpoints are used to interact with a local Consul agent.\n        Usually, services and checks are registered with an agent, which then\n        takes on the burden of registering with the Catalog and performing\n        anti-entropy to recover from outages.\n        """\n        def __init__(self, agent):\n            self.agent = agent\n            self.service = Consul.Agent.Service(agent)\n            self.check = Consul.Agent.Check(agent)\n\n        def self(self):\n            """\n            Returns configuration of the local agent and member information.\n            """\n            return self.agent.http.get(CB.json(), \'/v1/agent/self\')\n\n        def services(self):\n            """\n            Returns all the services that are registered with the local agent.\n            These services were either provided through configuration files, or\n            added dynamically using the HTTP API. It is important to note that\n            the services known by the agent may be different than those\n            reported by the Catalog. This is usually due to changes being made\n            while there is no leader elected. The agent performs active\n            anti-entropy, so in most situations everything will be in sync\n            within a few seconds.\n            """\n            return self.agent.http.get(CB.json(), \'/v1/agent/services\')\n\n        def checks(self):\n            """\n            Returns all the checks that are registered with the local agent.\n            These checks were either provided through configuration files, or\n            added dynamically using the HTTP API. Similar to services,\n            the checks known by the agent may be different than those\n            reported by the Catalog. This is usually due to changes being made\n            while there is no leader elected. The agent performs active\n            anti-entropy, so in most situations everything will be in sync\n            within a few seconds.\n            """\n            return self.agent.http.get(CB.json(), \'/v1/agent/checks\')\n\n        def members(self, wan=False):\n            """\n            Returns all the members that this agent currently sees. This may\n            vary by agent, use the nodes api of Catalog to retrieve a cluster\n            wide consistent view of members.\n\n            For agents running in server mode, setting *wan* to *True* returns\n            the list of WAN members instead of the LAN members which is\n            default.\n            """\n            params = []\n            if wan:\n                params.append((\'wan\', 1))\n            return self.agent.http.get(\n                CB.json(), \'/v1/agent/members\', params=params)\n\n        def maintenance(self, enable, reason=None):\n            """\n            The node maintenance endpoint can place the agent into\n            "maintenance mode".\n\n            *enable* is either \'true\' or \'false\'. \'true\' enables maintenance\n            mode, \'false\' disables maintenance mode.\n\n            *reason* is an optional string. This is simply to aid human\n            operators.\n            """\n\n            params = []\n\n            params.append((\'enable\', enable))\n            if reason:\n                params.append((\'reason\', reason))\n\n            return self.agent.http.put(\n                CB.bool(), \'/v1/agent/maintenance\', params=params)\n\n        def join(self, address, wan=False):\n            """\n            This endpoint instructs the agent to attempt to connect to a\n            given address.\n\n            *address* is the ip to connect to.\n\n            *wan* is either \'true\' or \'false\'. For agents running in server\n            mode, \'true\' causes the agent to attempt to join using the WAN\n            pool. Default is \'false\'.\n            """\n\n            params = []\n\n            if wan:\n                params.append((\'wan\', 1))\n\n            return self.agent.http.put(\n                CB.bool(), \'/v1/agent/join/%s\' % address, params=params)\n\n        def force_leave(self, node):\n            """\n            This endpoint instructs the agent to force a node into the left\n            state. If a node fails unexpectedly, then it will be in a failed\n            state. Once in the failed state, Consul will attempt to reconnect,\n            and the services and checks belonging to that node will not be\n            cleaned up. Forcing a node into the left state allows its old\n            entries to be removed.\n\n            *node* is the node to change state for.\n            """\n\n            return self.agent.http.put(\n                CB.bool(), \'/v1/agent/force-leave/%s\' % node)\n\n        class Service(object):\n            def __init__(self, agent):\n                self.agent = agent\n\n            def register(\n                    self,\n                    name,\n                    service_id=None,\n                    address=None,\n                    port=None,\n                    tags=None,\n                    check=None,\n                    token=None,\n                    meta=None,\n                    # *deprecated* use check parameter\n                    script=None,\n                    interval=None,\n                    ttl=None,\n                    http=None,\n                    timeout=None,\n                    enable_tag_override=False):\n                """\n                Add a new service to the local agent. There is more\n                documentation on services\n                `here <http://www.consul.io/docs/agent/services.html>`_.\n\n                *name* is the name of the service.\n\n                If the optional *service_id* is not provided it is set to\n                *name*. You cannot have duplicate *service_id* entries per\n                agent, so it may be necessary to provide one.\n\n                *address* will default to the address of the agent if not\n                provided.\n\n                An optional health *check* can be created for this service is\n                one of `Check.script`_, `Check.http`_, `Check.tcp`_,\n                `Check.ttl`_ or `Check.docker`_.\n\n                *token* is an optional `ACL token`_ to apply to this request.\n                Note this call will return successful even if the token doesn\'t\n                have permissions to register this service.\n\n                *meta* specifies arbitrary KV metadata linked to the service\n                formatted as {k1:v1, k2:v2}.\n\n                *script*, *interval*, *ttl*, *http*, and *timeout* arguments\n                are deprecated. use *check* instead.\n\n                *enable_tag_override* is an optional bool that enable you\n                to modify a service tags from servers(consul agent role server)\n                Default is set to False.\n                This option is only for >=v0.6.0 version on both agent and\n                servers.\n                for more information\n                https://www.consul.io/docs/agent/services.html\n                """\n\n                payload = {}\n\n                payload[\'name\'] = name\n                if enable_tag_override:\n                    payload[\'enabletagoverride\'] = enable_tag_override\n                if service_id:\n                    payload[\'id\'] = service_id\n                if address:\n                    payload[\'address\'] = address\n                if port:\n                    payload[\'port\'] = port\n                if tags:\n                    payload[\'tags\'] = tags\n                if meta:\n                    payload[\'meta\'] = meta\n                if check:\n                    payload[\'check\'] = check\n\n                else:\n                    payload.update(Check._compat(\n                        script=script,\n                        interval=interval,\n                        ttl=ttl,\n                        http=http,\n                        timeout=timeout))\n\n                params = []\n                token = token or self.agent.token\n                if token:\n                    params.append((\'token\', token))\n\n                return self.agent.http.put(\n                    CB.bool(),\n                    \'/v1/agent/service/register\',\n                    params=params,\n                    data=json.dumps(payload))\n\n            def deregister(self, service_id):\n                """\n                Used to remove a service from the local agent. The agent will\n                take care of deregistering the service with the Catalog. If\n                there is an associated check, that is also deregistered.\n                """\n                return self.agent.http.put(\n                    CB.bool(), \'/v1/agent/service/deregister/%s\' % service_id)\n\n            def maintenance(self, service_id, enable, reason=None):\n                """\n                The service maintenance endpoint allows placing a given service\n                into "maintenance mode".\n\n                *service_id* is the id of the service that is to be targeted\n                for maintenance.\n\n                *enable* is either \'true\' or \'false\'. \'true\' enables\n                maintenance mode, \'false\' disables maintenance mode.\n\n                *reason* is an optional string. This is simply to aid human\n                operators.\n                """\n\n                params = []\n\n                params.append((\'enable\', enable))\n                if reason:\n                    params.append((\'reason\', reason))\n\n                return self.agent.http.put(\n                    CB.bool(),\n                    \'/v1/agent/service/maintenance/{0}\'.format(service_id),\n                    params=params)\n\n        class Check(object):\n            def __init__(self, agent):\n                self.agent = agent\n\n            def register(\n                    self,\n                    name,\n                    check=None,\n                    check_id=None,\n                    notes=None,\n                    service_id=None,\n                    token=None,\n                    # *deprecated* use check parameter\n                    script=None,\n                    interval=None,\n                    ttl=None,\n                    http=None,\n                    timeout=None):\n                """\n                Register a new check with the local agent. More documentation\n                on checks can be found `here\n                <http://www.consul.io/docs/agent/checks.html>`_.\n\n                *name* is the name of the check.\n\n                *check* is one of `Check.script`_, `Check.http`_, `Check.tcp`_\n                `Check.ttl`_ or `Check.docker`_ and is required.\n\n                If the optional *check_id* is not provided it is set to *name*.\n                *check_id* must be unique for this agent.\n\n                *notes* is not used by Consul, and is meant to be human\n                readable.\n\n                Optionally, a *service_id* can be specified to associate a\n                registered check with an existing service.\n\n                *token* is an optional `ACL token`_ to apply to this request.\n                Note this call will return successful even if the token doesn\'t\n                have permissions to register this check.\n\n                *script*, *interval*, *ttl*, *http*, and *timeout* arguments\n                are deprecated. use *check* instead.\n\n                Returns *True* on success.\n                """\n                payload = {\'name\': name}\n\n                assert check or script or ttl or http, \\\n                    \'check is required\'\n\n                if check:\n                    payload.update(check)\n\n                else:\n                    payload.update(Check._compat(\n                        script=script,\n                        interval=interval,\n                        ttl=ttl,\n                        http=http,\n                        timeout=timeout)[\'check\'])\n\n                if check_id:\n                    payload[\'id\'] = check_id\n                if notes:\n                    payload[\'notes\'] = notes\n                if service_id:\n                    payload[\'serviceid\'] = service_id\n\n                params = []\n                token = token or self.agent.token\n                if token:\n                    params.append((\'token\', token))\n\n                return self.agent.http.put(\n                    CB.bool(),\n                    \'/v1/agent/check/register\',\n                    params=params,\n                    data=json.dumps(payload))\n\n            def deregister(self, check_id):\n                """\n                Remove a check from the local agent.\n                """\n                return self.agent.http.put(\n                    CB.bool(),\n                    \'/v1/agent/check/deregister/%s\' % check_id)\n\n            def ttl_pass(self, check_id, notes=None):\n                """\n                Mark a ttl based check as passing. Optional notes can be\n                attached to describe the status of the check.\n                """\n                params = []\n                if notes:\n                    params.append((\'note\', notes))\n\n                return self.agent.http.put(\n                    CB.bool(),\n                    \'/v1/agent/check/pass/%s\' % check_id,\n                    params=params)\n\n            def ttl_fail(self, check_id, notes=None):\n                """\n                Mark a ttl based check as failing. Optional notes can be\n                attached to describe why check is failing. The status of the\n                check will be set to critical and the ttl clock will be reset.\n                """\n                params = []\n                if notes:\n                    params.append((\'note\', notes))\n\n                return self.agent.http.put(\n                    CB.bool(),\n                    \'/v1/agent/check/fail/%s\' % check_id,\n                    params=params)\n\n            def ttl_warn(self, check_id, notes=None):\n                """\n                Mark a ttl based check with warning. Optional notes can be\n                attached to describe the warning. The status of the\n                check will be set to warn and the ttl clock will be reset.\n                """\n                params = []\n                if notes:\n                    params.append((\'note\', notes))\n\n                return self.agent.http.put(\n                    CB.bool(),\n                    \'/v1/agent/check/warn/%s\' % check_id,\n                    params=params)\n\n    class Catalog(object):\n        def __init__(self, agent):\n            self.agent = agent\n\n        def register(self,\n                     node,\n                     address,\n                     service=None,\n                     check=None,\n                     dc=None,\n                     token=None,\n                     node_meta=None):\n            """\n            A low level mechanism for directly registering or updating entries\n            in the catalog. It is usually recommended to use\n            agent.service.register and agent.check.register, as they are\n            simpler and perform anti-entropy.\n\n            *node* is the name of the node to register.\n\n            *address* is the ip of the node.\n\n            *service* is an optional service to register. if supplied this is a\n            dict::\n\n                {\n                    "Service": "redis",\n                    "ID": "redis1",\n                    "Tags": [\n                        "master",\n                        "v1"\n                    ],\n                    "Port": 8000\n                }\n\n            where\n\n                *Service* is required and is the name of the service\n\n                *ID* is optional, and will be set to *Service* if not provided.\n                Note *ID* must be unique for the given *node*.\n\n                *Tags* and *Port* are optional.\n\n            *check* is an optional check to register. if supplied this is a\n            dict::\n\n                {\n                    "Node": "foobar",\n                    "CheckID": "service:redis1",\n                    "Name": "Redis health check",\n                    "Notes": "Script based health check",\n                    "Status": "passing",\n                    "ServiceID": "redis1"\n                }\n\n            *dc* is the datacenter of the node and defaults to this agents\n            datacenter.\n\n            *token* is an optional `ACL token`_ to apply to this request.\n\n            *node_meta* is an optional meta data used for filtering, a\n            dictionary formatted as {k1:v1, k2:v2}.\n\n            This manipulates the health check entry, but does not setup a\n            script or TTL to actually update the status. The full documentation\n            is `here <https://consul.io/docs/agent/http.html#catalog>`_.\n\n            Returns *True* on success.\n            """\n            data = {\'node\': node, \'address\': address}\n            params = []\n            dc = dc or self.agent.dc\n            if dc:\n                data[\'datacenter\'] = dc\n            if service:\n                data[\'service\'] = service\n            if check:\n                data[\'check\'] = check\n            token = token or self.agent.token\n            if token:\n                data[\'WriteRequest\'] = {\'Token\': token}\n                params.append((\'token\', token))\n            if node_meta:\n                for nodemeta_name, nodemeta_value in node_meta.items():\n                    params.append((\'node-meta\', \'{0}:{1}\'.\n                                   format(nodemeta_name, nodemeta_value)))\n            return self.agent.http.put(\n                CB.bool(),\n                \'/v1/catalog/register\',\n                data=json.dumps(data),\n                params=params)\n\n        def deregister(self,\n                       node,\n                       service_id=None,\n                       check_id=None,\n                       dc=None,\n                       token=None):\n            """\n            A low level mechanism for directly removing entries in the catalog.\n            It is usually recommended to use the agent APIs, as they are\n            simpler and perform anti-entropy.\n\n            *node* and *dc* specify which node on which datacenter to remove.\n            If *service_id* and *check_id* are not provided, all associated\n            services and checks are deleted. Otherwise only one of *service_id*\n            and *check_id* should be provided and only that service or check\n            will be removed.\n\n            *token* is an optional `ACL token`_ to apply to this request.\n\n            Returns *True* on success.\n            """\n            assert not (service_id and check_id)\n            data = {\'node\': node}\n            dc = dc or self.agent.dc\n            if dc:\n                data[\'datacenter\'] = dc\n            if service_id:\n                data[\'serviceid\'] = service_id\n            if check_id:\n                data[\'checkid\'] = check_id\n            token = token or self.agent.token\n            if token:\n                data[\'WriteRequest\'] = {\'Token\': token}\n            return self.agent.http.put(\n                CB.bool(), \'/v1/catalog/deregister\', data=json.dumps(data))\n\n        def datacenters(self):\n            """\n            Returns all the datacenters that are known by the Consul server.\n            """\n            return self.agent.http.get(\n                CB.json(), \'/v1/catalog/datacenters\')\n\n        def nodes(\n                self,\n                index=None,\n                wait=None,\n                consistency=None,\n                dc=None,\n                near=None,\n                token=None,\n                node_meta=None):\n            """\n            Returns a tuple of (*index*, *nodes*) of all nodes known\n            about in the *dc* datacenter. *dc* defaults to the current\n            datacenter of this agent.\n\n            *index* is the current Consul index, suitable for making subsequent\n            calls to wait for changes since this query was last run.\n\n            *wait* the maximum duration to wait (e.g. \'10s\') to retrieve\n            a given index. this parameter is only applied if *index* is also\n            specified. the wait time by default is 5 minutes.\n\n            *near* is a node name to sort the resulting list in ascending\n            order based on the estimated round trip time from that node\n\n            *consistency* can be either \'default\', \'consistent\' or \'stale\'. if\n            not specified *consistency* will the consistency level this client\n            was configured with.\n\n            *token* is an optional `ACL token`_ to apply to this request.\n\n            *node_meta* is an optional meta data used for filtering, a\n            dictionary formatted as {k1:v1, k2:v2}.\n\n            The response looks like this::\n\n                (index, [\n                    {\n                        "Node": "baz",\n                        "Address": "10.1.10.11"\n                    },\n                    {\n                        "Node": "foobar",\n                        "Address": "10.1.10.12"\n                    }\n                ])\n            """\n            params = []\n            dc = dc or self.agent.dc\n            if dc:\n                params.append((\'dc\', dc))\n            if index:\n                params.append((\'index\', index))\n                if wait:\n                    params.append((\'wait\', wait))\n            if near:\n                params.append((\'near\', near))\n            token = token or self.agent.token\n            if token:\n                params.append((\'token\', token))\n            consistency = consistency or self.agent.consistency\n            if consistency in (\'consistent\', \'stale\'):\n                params.append((consistency, \'1\'))\n            if node_meta:\n                for nodemeta_name, nodemeta_value in node_meta.items():\n                    params.append((\'node-meta\', \'{0}:{1}\'.\n                                   format(nodemeta_name, nodemeta_value)))\n            return self.agent.http.get(\n                CB.json(index=True), \'/v1/catalog/nodes\', params=params)\n\n        def services(self,\n                     index=None,\n                     wait=None,\n                     consistency=None,\n                     dc=None,\n                     token=None,\n                     node_meta=None):\n            """\n            Returns a tuple of (*index*, *services*) of all services known\n            about in the *dc* datacenter. *dc* defaults to the current\n            datacenter of this agent.\n\n            *index* is the current Consul index, suitable for making subsequent\n            calls to wait for changes since this query was last run.\n\n            *wait* the maximum duration to wait (e.g. \'10s\') to retrieve\n            a given index. this parameter is only applied if *index* is also\n            specified. the wait time by default is 5 minutes.\n\n            *consistency* can be either \'default\', \'consistent\' or \'stale\'. if\n            not specified *consistency* will the consistency level this client\n            was configured with.\n\n            *token* is an optional `ACL token`_ to apply to this request.\n\n            *node_meta* is an optional meta data used for filtering, a\n            dictionary formatted as {k1:v1, k2:v2}.\n\n            The response looks like this::\n\n                (index, {\n                    "consul": [],\n                    "redis": [],\n                    "postgresql": [\n                        "master",\n                        "slave"\n                    ]\n                })\n\n            The main keys are the service names and the list provides all the\n            known tags for a given service.\n            """\n            params = []\n            dc = dc or self.agent.dc\n            if dc:\n                params.append((\'dc\', dc))\n            if index:\n                params.append((\'index\', index))\n                if wait:\n                    params.append((\'wait\', wait))\n            token = token or self.agent.token\n            if token:\n                params.append((\'token\', token))\n            consistency = consistency or self.agent.consistency\n            if consistency in (\'consistent\', \'stale\'):\n                params.append((consistency, \'1\'))\n            if node_meta:\n                for nodemeta_name, nodemeta_value in node_meta.items():\n                    params.append((\'node-meta\', \'{0}:{1}\'.\n                                   format(nodemeta_name, nodemeta_value)))\n            return self.agent.http.get(\n                CB.json(index=True), \'/v1/catalog/services\', params=params)\n\n        def node(self,\n                 node,\n                 index=None,\n                 wait=None,\n                 consistency=None,\n                 dc=None,\n                 token=None):\n            """\n            Returns a tuple of (*index*, *services*) of all services provided\n            by *node*.\n\n            *index* is the current Consul index, suitable for making subsequent\n            calls to wait for changes since this query was last run.\n\n            *wait* the maximum duration to wait (e.g. \'10s\') to retrieve\n            a given index. this parameter is only applied if *index* is also\n            specified. the wait time by default is 5 minutes.\n\n            *consistency* can be either \'default\', \'consistent\' or \'stale\'. if\n            not specified *consistency* will the consistency level this client\n            was configured with.\n\n            *dc* is the datacenter of the node and defaults to this agents\n            datacenter.\n\n            *token* is an optional `ACL token`_ to apply to this request.\n\n            The response looks like this::\n\n                (index, {\n                    "Node": {\n                        "Node": "foobar",\n                        "Address": "10.1.10.12"\n                    },\n                    "Services": {\n                        "consul": {\n                            "ID": "consul",\n                            "Service": "consul",\n                            "Tags": null,\n                            "Port": 8300\n                        },\n                        "redis": {\n                            "ID": "redis",\n                            "Service": "redis",\n                            "Tags": [\n                                "v1"\n                            ],\n                            "Port": 8000\n                        }\n                    }\n                })\n            """\n            params = []\n            dc = dc or self.agent.dc\n            if dc:\n                params.append((\'dc\', dc))\n            if index:\n                params.append((\'index\', index))\n                if wait:\n                    params.append((\'wait\', wait))\n            token = token or self.agent.token\n            if token:\n                params.append((\'token\', token))\n            consistency = consistency or self.agent.consistency\n            if consistency in (\'consistent\', \'stale\'):\n                params.append((consistency, \'1\'))\n            return self.agent.http.get(\n                CB.json(index=True),\n                \'/v1/catalog/node/%s\' % node,\n                params=params)\n\n        def service(\n                self,\n                service,\n                index=None,\n                wait=None,\n                tag=None,\n                consistency=None,\n                dc=None,\n                near=None,\n                token=None,\n                node_meta=None):\n            """\n            Returns a tuple of (*index*, *nodes*) of the nodes providing\n            *service* in the *dc* datacenter. *dc* defaults to the current\n            datacenter of this agent.\n\n            *index* is the current Consul index, suitable for making subsequent\n            calls to wait for changes since this query was last run.\n\n            *wait* the maximum duration to wait (e.g. \'10s\') to retrieve\n            a given index. this parameter is only applied if *index* is also\n            specified. the wait time by default is 5 minutes.\n\n            If *tag* is provided, the list of nodes returned will be filtered\n            by that tag.\n\n            *near* is a node name to sort the resulting list in ascending\n            order based on the estimated round trip time from that node\n\n            *consistency* can be either \'default\', \'consistent\' or \'stale\'. if\n            not specified *consistency* will the consistency level this client\n            was configured with.\n\n            *token* is an optional `ACL token`_ to apply to this request.\n\n            *node_meta* is an optional meta data used for filtering, a\n            dictionary formatted as {k1:v1, k2:v2}.\n\n            The response looks like this::\n\n                (index, [\n                    {\n                        "Node": "foobar",\n                        "Address": "10.1.10.12",\n                        "ServiceID": "redis",\n                        "ServiceName": "redis",\n                        "ServiceTags": null,\n                        "ServicePort": 8000\n                    }\n                ])\n            """\n            params = []\n            dc = dc or self.agent.dc\n            if dc:\n                params.append((\'dc\', dc))\n            if tag:\n                params.append((\'tag\', tag))\n            if index:\n                params.append((\'index\', index))\n                if wait:\n                    params.append((\'wait\', wait))\n            if near:\n                params.append((\'near\', near))\n            token = token or self.agent.token\n            if token:\n                params.append((\'token\', token))\n            consistency = consistency or self.agent.consistency\n            if consistency in (\'consistent\', \'stale\'):\n                params.append((consistency, \'1\'))\n            if node_meta:\n                for nodemeta_name, nodemeta_value in node_meta.items():\n                    params.append((\'node-meta\', \'{0}:{1}\'.\n                                   format(nodemeta_name, nodemeta_value)))\n            return self.agent.http.get(\n                CB.json(index=True),\n                \'/v1/catalog/service/%s\' % service,\n                params=params)\n\n    class Health(object):\n        # TODO: All of the health endpoints support all consistency modes\n        def __init__(self, agent):\n            self.agent = agent\n\n        def service(self,\n                    service,\n                    index=None,\n                    wait=None,\n                    passing=None,\n                    tag=None,\n                    dc=None,\n                    near=None,\n                    token=None,\n                    node_meta=None):\n            """\n            Returns a tuple of (*index*, *nodes*)\n\n            *index* is the current Consul index, suitable for making subsequent\n            calls to wait for changes since this query was last run.\n\n            *wait* the maximum duration to wait (e.g. \'10s\') to retrieve\n            a given index. this parameter is only applied if *index* is also\n            specified. the wait time by default is 5 minutes.\n\n            *nodes* are the nodes providing the given service.\n\n            Calling with *passing* set to True will filter results to only\n            those nodes whose checks are currently passing.\n\n            Calling with *tag* will filter the results by tag.\n\n            *dc* is the datacenter of the node and defaults to this agents\n            datacenter.\n\n            *near* is a node name to sort the resulting list in ascending\n            order based on the estimated round trip time from that node\n\n            *token* is an optional `ACL token`_ to apply to this request.\n\n            *node_meta* is an optional meta data used for filtering, a\n            dictionary formatted as {k1:v1, k2:v2}.\n            """\n            params = []\n            if index:\n                params.append((\'index\', index))\n                if wait:\n                    params.append((\'wait\', wait))\n            if passing:\n                params.append((\'passing\', \'1\'))\n            if tag is not None:\n                params.append((\'tag\', tag))\n            dc = dc or self.agent.dc\n            if dc:\n                params.append((\'dc\', dc))\n            if near:\n                params.append((\'near\', near))\n            token = token or self.agent.token\n            if token:\n                params.append((\'token\', token))\n            if node_meta:\n                for nodemeta_name, nodemeta_value in node_meta.items():\n                    params.append((\'node-meta\', \'{0}:{1}\'.\n                                   format(nodemeta_name, nodemeta_value)))\n            return self.agent.http.get(\n                CB.json(index=True),\n                \'/v1/health/service/%s\' % service,\n                params=params)\n\n        def checks(\n                self,\n                service,\n                index=None,\n                wait=None,\n                dc=None,\n                near=None,\n                token=None,\n                node_meta=None):\n            """\n            Returns a tuple of (*index*, *checks*) with *checks* being the\n            checks associated with the service.\n\n            *service* is the name of the service being checked.\n\n            *index* is the current Consul index, suitable for making subsequent\n            calls to wait for changes since this query was last run.\n\n            *wait* the maximum duration to wait (e.g. \'10s\') to retrieve\n            a given index. this parameter is only applied if *index* is also\n            specified. the wait time by default is 5 minutes.\n\n            *dc* is the datacenter of the node and defaults to this agents\n            datacenter.\n\n            *near* is a node name to sort the resulting list in ascending\n            order based on the estimated round trip time from that node\n\n            *token* is an optional `ACL token`_ to apply to this request.\n\n            *node_meta* is an optional meta data used for filtering, a\n            dictionary formatted as {k1:v1, k2:v2}.\n            """\n            params = []\n            if index:\n                params.append((\'index\', index))\n                if wait:\n                    params.append((\'wait\', wait))\n            dc = dc or self.agent.dc\n            if dc:\n                params.append((\'dc\', dc))\n            if near:\n                params.append((\'near\', near))\n            token = token or self.agent.token\n            if token:\n                params.append((\'token\', token))\n            if node_meta:\n                for nodemeta_name, nodemeta_value in node_meta.items():\n                    params.append((\'node-meta\', \'{0}:{1}\'.\n                                   format(nodemeta_name, nodemeta_value)))\n            return self.agent.http.get(\n                CB.json(index=True),\n                \'/v1/health/checks/%s\' % service,\n                params=params)\n\n        def state(self,\n                  name,\n                  index=None,\n                  wait=None,\n                  dc=None,\n                  near=None,\n                  token=None,\n                  node_meta=None):\n            """\n            Returns a tuple of (*index*, *nodes*)\n\n            *name* is a supported state. From the Consul docs:\n\n                The supported states are any, unknown, passing, warning, or\n                critical. The any state is a wildcard that can be used to\n                return all checks.\n\n            *index* is the current Consul index, suitable for making subsequent\n            calls to wait for changes since this query was last run.\n\n            *wait* the maximum duration to wait (e.g. \'10s\') to retrieve\n            a given index. this parameter is only applied if *index* is also\n            specified. the wait time by default is 5 minutes.\n\n            *dc* is the datacenter of the node and defaults to this agents\n            datacenter.\n\n            *near* is a node name to sort the resulting list in ascending\n            order based on the estimated round trip time from that node\n\n            *token* is an optional `ACL token`_ to apply to this request.\n\n            *node_meta* is an optional meta data used for filtering, a\n            dictionary formatted as {k1:v1, k2:v2}.\n\n            *nodes* are the nodes providing the given service.\n            """\n            assert name in [\'any\', \'unknown\', \'passing\', \'warning\', \'critical\']\n            params = []\n            if index:\n                params.append((\'index\', index))\n                if wait:\n                    params.append((\'wait\', wait))\n            dc = dc or self.agent.dc\n            if dc:\n                params.append((\'dc\', dc))\n            if near:\n                params.append((\'near\', near))\n            token = token or self.agent.token\n            if token:\n                params.append((\'token\', token))\n            if node_meta:\n                for nodemeta_name, nodemeta_value in node_meta.items():\n                    params.append((\'node-meta\', \'{0}:{1}\'.\n                                   format(nodemeta_name, nodemeta_value)))\n            return self.agent.http.get(\n                CB.json(index=True),\n                \'/v1/health/state/%s\' % name,\n                params=params)\n\n        def node(self, node, index=None, wait=None, dc=None, token=None):\n            """\n            Returns a tuple of (*index*, *checks*)\n\n            *index* is the current Consul index, suitable for making subsequent\n            calls to wait for changes since this query was last run.\n\n            *wait* the maximum duration to wait (e.g. \'10s\') to retrieve\n            a given index. this parameter is only applied if *index* is also\n            specified. the wait time by default is 5 minutes.\n\n            *dc* is the datacenter of the node and defaults to this agents\n            datacenter.\n\n            *token* is an optional `ACL token`_ to apply to this request.\n\n            *nodes* are the nodes providing the given service.\n            """\n            params = []\n            if index:\n                params.append((\'index\', index))\n                if wait:\n                    params.append((\'wait\', wait))\n            dc = dc or self.agent.dc\n            if dc:\n                params.append((\'dc\', dc))\n            token = token or self.agent.token\n            if token:\n                params.append((\'token\', token))\n\n            return self.agent.http.get(\n                CB.json(index=True),\n                \'/v1/health/node/%s\' % node,\n                params=params)\n\n    class Session(object):\n        def __init__(self, agent):\n            self.agent = agent\n\n        def create(\n                self,\n                name=None,\n                node=None,\n                checks=None,\n                lock_delay=15,\n                behavior=\'release\',\n                ttl=None,\n                dc=None):\n            """\n            Creates a new session. There is more documentation for sessions\n            `here <https://consul.io/docs/internals/sessions.html>`_.\n\n            *name* is an optional human readable name for the session.\n\n            *node* is the node to create the session on. if not provided the\n            current agent\'s node will be used.\n\n            *checks* is a list of checks to associate with the session. if not\n            provided it defaults to the *serfHealth* check. It is highly\n            recommended that, if you override this list, you include the\n            default *serfHealth*.\n\n            *lock_delay* is an integer of seconds.\n\n            *behavior* can be set to either \'release\' or \'delete\'. This\n            controls the behavior when a session is invalidated. By default,\n            this is \'release\', causing any locks that are held to be released.\n            Changing this to \'delete\' causes any locks that are held to be\n            deleted. \'delete\' is useful for creating ephemeral key/value\n            entries.\n\n            when *ttl* is provided, the session is invalidated if it is not\n            renewed before the TTL expires.  If specified, it is an integer of\n            seconds.  Currently it must be between 10 and 86400 seconds.\n\n            By default the session will be created in the current datacenter\n            but an optional *dc* can be provided.\n\n            Returns the string *session_id* for the session.\n            """\n            params = []\n            dc = dc or self.agent.dc\n            if dc:\n                params.append((\'dc\', dc))\n            data = {}\n            if name:\n                data[\'name\'] = name\n            if node:\n                data[\'node\'] = node\n            if checks is not None:\n                data[\'checks\'] = checks\n            if lock_delay != 15:\n                data[\'lockdelay\'] = \'%ss\' % lock_delay\n            assert behavior in (\'release\', \'delete\'), \\\n                \'behavior must be release or delete\'\n            if behavior != \'release\':\n                data[\'behavior\'] = behavior\n            if ttl:\n                assert 10 <= ttl <= 86400\n                data[\'ttl\'] = \'%ss\' % ttl\n            if data:\n                data = json.dumps(data)\n            else:\n                data = \'\'\n\n            return self.agent.http.put(\n                CB.json(is_id=True),\n                \'/v1/session/create\',\n                params=params,\n                data=data)\n\n        def destroy(self, session_id, dc=None):\n            """\n            Destroys the session *session_id*\n\n            Returns *True* on success.\n            """\n            params = []\n            dc = dc or self.agent.dc\n            if dc:\n                params.append((\'dc\', dc))\n            return self.agent.http.put(\n                CB.bool(),\n                \'/v1/session/destroy/%s\' % session_id,\n                params=params)\n\n        def list(self, index=None, wait=None, consistency=None, dc=None):\n            """\n            Returns a tuple of (*index*, *sessions*) of all active sessions in\n            the *dc* datacenter. *dc* defaults to the current datacenter of\n            this agent.\n\n            *index* is the current Consul index, suitable for making subsequent\n            calls to wait for changes since this query was last run.\n\n            *wait* the maximum duration to wait (e.g. \'10s\') to retrieve\n            a given index. this parameter is only applied if *index* is also\n            specified. the wait time by default is 5 minutes.\n\n            *consistency* can be either \'default\', \'consistent\' or \'stale\'. if\n            not specified *consistency* will the consistency level this client\n            was configured with.\n\n            The response looks like this::\n\n                (index, [\n                    {\n                        "LockDelay": 1.5e+10,\n                        "Checks": [\n                            "serfHealth"\n                        ],\n                        "Node": "foobar",\n                        "ID": "adf4238a-882b-9ddc-4a9d-5b6758e4159e",\n                        "CreateIndex": 1086449\n                    },\n                  ...\n               ])\n            """\n            params = []\n            dc = dc or self.agent.dc\n            if dc:\n                params.append((\'dc\', dc))\n            if index:\n                params.append((\'index\', index))\n                if wait:\n                    params.append((\'wait\', wait))\n            consistency = consistency or self.agent.consistency\n            if consistency in (\'consistent\', \'stale\'):\n                params.append((consistency, \'1\'))\n            return self.agent.http.get(\n                CB.json(index=True), \'/v1/session/list\', params=params)\n\n        def node(self, node, index=None, wait=None, consistency=None, dc=None):\n            """\n            Returns a tuple of (*index*, *sessions*) as per *session.list*, but\n            filters the sessions returned to only those active for *node*.\n\n            *index* is the current Consul index, suitable for making subsequent\n            calls to wait for changes since this query was last run.\n\n            *wait* the maximum duration to wait (e.g. \'10s\') to retrieve\n            a given index. this parameter is only applied if *index* is also\n            specified. the wait time by default is 5 minutes.\n\n            *consistency* can be either \'default\', \'consistent\' or \'stale\'. if\n            not specified *consistency* will the consistency level this client\n            was configured with.\n            """\n            params = []\n            dc = dc or self.agent.dc\n            if dc:\n                params.append((\'dc\', dc))\n            if index:\n                params.append((\'index\', index))\n                if wait:\n                    params.append((\'wait\', wait))\n            consistency = consistency or self.agent.consistency\n            if consistency in (\'consistent\', \'stale\'):\n                params.append((consistency, \'1\'))\n            return self.agent.http.get(\n                CB.json(index=True),\n                \'/v1/session/node/%s\' % node, params=params)\n\n        def info(self,\n                 session_id,\n                 index=None,\n                 wait=None,\n                 consistency=None,\n                 dc=None):\n            """\n            Returns a tuple of (*index*, *session*) for the session\n            *session_id* in the *dc* datacenter. *dc* defaults to the current\n            datacenter of this agent.\n\n            *index* is the current Consul index, suitable for making subsequent\n            calls to wait for changes since this query was last run.\n\n            *wait* the maximum duration to wait (e.g. \'10s\') to retrieve\n            a given index. this parameter is only applied if *index* is also\n            specified. the wait time by default is 5 minutes.\n\n            *consistency* can be either \'default\', \'consistent\' or \'stale\'. if\n            not specified *consistency* will the consistency level this client\n            was configured with.\n            """\n            params = []\n            dc = dc or self.agent.dc\n            if dc:\n                params.append((\'dc\', dc))\n            if index:\n                params.append((\'index\', index))\n                if wait:\n                    params.append((\'wait\', wait))\n            consistency = consistency or self.agent.consistency\n            if consistency in (\'consistent\', \'stale\'):\n                params.append((consistency, \'1\'))\n            return self.agent.http.get(\n                CB.json(index=True, one=True),\n                \'/v1/session/info/%s\' % session_id,\n                params=params)\n\n        def renew(self, session_id, dc=None):\n            """\n            This is used with sessions that have a TTL, and it extends the\n            expiration by the TTL.\n\n            *dc* is the optional datacenter that you wish to communicate with.\n            If None is provided, defaults to the agent\'s datacenter.\n\n            Returns the session.\n            """\n            params = []\n            dc = dc or self.agent.dc\n            if dc:\n                params.append((\'dc\', dc))\n            return self.agent.http.put(\n                CB.json(one=True, allow_404=False),\n                \'/v1/session/renew/%s\' % session_id,\n                params=params)\n\n    class ACL(object):\n        def __init__(self, agent):\n            self.agent = agent\n\n        def list(self, token=None):\n            """\n            Lists all the active ACL tokens. This is a privileged endpoint, and\n            requires a management token. *token* will override this client\'s\n            default token.  An *ACLPermissionDenied* exception will be raised\n            if a management token is not used.\n            """\n            params = []\n            token = token or self.agent.token\n            if token:\n                params.append((\'token\', token))\n            return self.agent.http.get(\n                CB.json(), \'/v1/acl/list\', params=params)\n\n        def info(self, acl_id, token=None):\n            """\n            Returns the token information for *acl_id*.\n            """\n            params = []\n            token = token or self.agent.token\n            if token:\n                params.append((\'token\', token))\n            return self.agent.http.get(\n                CB.json(one=True), \'/v1/acl/info/%s\' % acl_id, params=params)\n\n        def create(self,\n                   name=None,\n                   type=\'client\',\n                   rules=None,\n                   acl_id=None,\n                   token=None):\n            """\n            Creates a new ACL token. This is a privileged endpoint, and\n            requires a management token. *token* will override this client\'s\n            default token.  An *ACLPermissionDenied* exception will be raised\n            if a management token is not used.\n\n            *name* is an optional name for this token.\n\n            *type* is either \'management\' or \'client\'. A management token is\n            effectively like a root user, and has the ability to perform any\n            action including creating, modifying, and deleting ACLs. A client\n            token can only perform actions as permitted by *rules*.\n\n            *rules* is an optional `HCL`_ string for this `ACL Token`_ Rule\n            Specification.\n\n            Rules look like this::\n\n                # Default all keys to read-only\n                key "" {\n                  policy = "read"\n                }\n                key "foo/" {\n                  policy = "write"\n                }\n                key "foo/private/" {\n                  # Deny access to the private dir\n                  policy = "deny"\n                }\n\n            Returns the string *acl_id* for the new token.\n            """\n            params = []\n            token = token or self.agent.token\n            if token:\n                params.append((\'token\', token))\n\n            payload = {}\n            if name:\n                payload[\'Name\'] = name\n            if type:\n                assert type in (\'client\', \'management\'), \\\n                    \'type must be client or management\'\n                payload[\'Type\'] = type\n            if rules:\n                assert isinstance(rules, str), \\\n                    \'Only HCL or JSON encoded strings supported for the moment\'\n                payload[\'Rules\'] = rules\n            if acl_id:\n                payload[\'ID\'] = acl_id\n\n            if payload:\n                data = json.dumps(payload)\n            else:\n                data = \'\'\n\n            return self.agent.http.put(\n                CB.json(is_id=True),\n                \'/v1/acl/create\',\n                params=params,\n                data=data)\n\n        def update(self, acl_id, name=None, type=None, rules=None, token=None):\n            """\n            Updates the ACL token *acl_id*. This is a privileged endpoint, and\n            requires a management token. *token* will override this client\'s\n            default token. An *ACLPermissionDenied* exception will be raised if\n            a management token is not used.\n\n            *name* is an optional name for this token.\n\n            *type* is either \'management\' or \'client\'. A management token is\n            effectively like a root user, and has the ability to perform any\n            action including creating, modifying, and deleting ACLs. A client\n            token can only perform actions as permitted by *rules*.\n\n            *rules* is an optional `HCL`_ string for this `ACL Token`_ Rule\n            Specification.\n\n            Returns the string *acl_id* of this token on success.\n            """\n            params = []\n            token = token or self.agent.token\n            if token:\n                params.append((\'token\', token))\n\n            payload = {\'ID\': acl_id}\n            if name:\n                payload[\'Name\'] = name\n            if type:\n                assert type in (\'client\', \'management\'), \\\n                    \'type must be client or management\'\n                payload[\'Type\'] = type\n            if rules:\n                assert isinstance(rules, str), \\\n                    \'Only HCL or JSON encoded strings supported for the moment\'\n                payload[\'Rules\'] = rules\n\n            data = json.dumps(payload)\n\n            return self.agent.http.put(\n                CB.json(is_id=True),\n                \'/v1/acl/update\',\n                params=params,\n                data=data)\n\n        def clone(self, acl_id, token=None):\n            """\n            Clones the ACL token *acl_id*. This is a privileged endpoint, and\n            requires a management token. *token* will override this client\'s\n            default token. An *ACLPermissionDenied* exception will be raised if\n            a management token is not used.\n\n            Returns the string of the newly created *acl_id*.\n            """\n            params = []\n            token = token or self.agent.token\n            if token:\n                params.append((\'token\', token))\n            return self.agent.http.put(\n                CB.json(is_id=True),\n                \'/v1/acl/clone/%s\' % acl_id,\n                params=params)\n\n        def destroy(self, acl_id, token=None):\n            """\n            Destroys the ACL token *acl_id*. This is a privileged endpoint, and\n            requires a management token. *token* will override this client\'s\n            default token. An *ACLPermissionDenied* exception will be raised if\n            a management token is not used.\n\n            Returns *True* on success.\n            """\n            params = []\n            token = token or self.agent.token\n            if token:\n                params.append((\'token\', token))\n            return self.agent.http.put(\n                CB.json(),\n                \'/v1/acl/destroy/%s\' % acl_id,\n                params=params)\n\n    class Status(object):\n        """\n        The Status endpoints are used to get information about the status\n         of the Consul cluster.\n        """\n        def __init__(self, agent):\n            self.agent = agent\n\n        def leader(self):\n            """\n            This endpoint is used to get the Raft leader for the datacenter\n            in which the agent is running.\n            """\n            return self.agent.http.get(CB.json(), \'/v1/status/leader\')\n\n        def peers(self):\n            """\n            This endpoint retrieves the Raft peers for the datacenter in which\n            the the agent is running.\n            """\n            return self.agent.http.get(CB.json(), \'/v1/status/peers\')\n\n    class Query(object):\n        def __init__(self, agent):\n            self.agent = agent\n\n        def list(self, dc=None, token=None):\n            """\n            Lists all the active  queries. This is a privileged endpoint,\n            therefore you will only be able to get the prepared queries\n            which the token supplied has read privileges to.\n\n            *dc* is the datacenter that this agent will communicate with. By\n            default the datacenter of the host is used.\n\n            *token* is an optional `ACL token`_ to apply to this request.\n            """\n            params = []\n            token = token or self.agent.token\n            if token:\n                params.append((\'token\', token))\n            if dc:\n                params.append((\'dc\', dc))\n\n            return self.agent.http.get(CB.json(), \'/v1/query\', params=params)\n\n        def _query_data(self, service=None,\n                        name=None,\n                        session=None,\n                        token=None,\n                        nearestn=None,\n                        datacenters=None,\n                        onlypassing=None,\n                        tags=None,\n                        ttl=None,\n                        regexp=None):\n            service_body = dict([\n                (k, v) for k, v in {\n                    \'service\': service,\n                    \'onlypassing\': onlypassing,\n                    \'tags\': tags,\n                    \'failover\': dict([\n                        (k, v) for k, v in {\n                            \'nearestn\': nearestn,\n                            \'datacenters\': datacenters\n                        }.items() if v is not None\n                    ])\n                }.items() if v is not None\n            ])\n\n            data = dict([\n                (k, v) for k, v in {\n                    \'name\': name,\n                    \'session\': session,\n                    \'token\': token or self.agent.token,\n                    \'dns\': {\n                        \'ttl\': ttl\n                    } if ttl is not None else None,\n                    \'template\': dict([\n                        (k, v) for k, v in {\n                            \'type\': \'name_prefix_match\',\n                            \'regexp\': regexp\n                        }.items() if v is not None\n                    ]),\n                    \'service\': service_body\n                }.items() if v is not None\n            ])\n            return json.dumps(data)\n\n        def create(self, service,\n                   name=None,\n                   dc=None,\n                   session=None,\n                   token=None,\n                   nearestn=None,\n                   datacenters=None,\n                   onlypassing=None,\n                   tags=None,\n                   ttl=None,\n                   regexp=None):\n            """\n            Creates a new query. This is a privileged endpoint, and\n            requires a management token for a certain query name.*token* will\n            override this client\'s default token.\n\n            *service* is mandatory for new query. represent service name to\n            query.\n\n            *name* is an optional name for this query.\n\n            *dc* is the datacenter that this agent will communicate with. By\n            default the datacenter of the host is used.\n\n            *token* is an optional `ACL token`_ to apply to this request.\n\n            *nearestn* if set to a value greater than zero, then the query will\n            be forwarded to up to NearestN other datacenters based on their\n            estimated network round trip time using Network Coordinates from\n            the WAN gossip pool.\n\n            *datacenters* is a fixed list of remote datacenters to forward the\n            query to if there are no healthy nodes in the local datacenter.\n\n            *onlypassing* controls the behavior of the query\'s health check\n            filtering.\n\n            *tags* is a list of service tags to filter the query results.\n\n            *ttl*  is a duration string that can use "s" as a suffix for\n            seconds. It controls how the TTL is set when query results are\n            served over DNS.\n\n            *regexp* is optional for template this option is only supported\n            in Consul 0.6.4 or later. The only option for type is\n            name_prefix_match so if you want a query template with no regexp\n            enter an empty string.\n\n            For more information about query\n            https://www.consul.io/docs/agent/http/query.html\n            """\n            path = \'/v1/query\'\n            params = None if dc is None else [(\'dc\', dc)]\n            data = self._query_data(\n                service, name, session, token, nearestn, datacenters,\n                onlypassing, tags, ttl, regexp\n            )\n            return self.agent.http.post(\n                CB.json(), path, params=params, data=data)\n\n        def update(self, query_id,\n                   service=None,\n                   name=None,\n                   dc=None,\n                   session=None,\n                   token=None,\n                   nearestn=None,\n                   datacenters=None,\n                   onlypassing=None,\n                   tags=None,\n                   ttl=None,\n                   regexp=None):\n            """\n            This endpoint will update a certain query\n\n            *query_id* is the query id for update\n\n            all the other setting remains the same as the query create method\n            """\n            path = \'/v1/query/%s\' % query_id\n            params = None if dc is None else [(\'dc\', dc)]\n            data = self._query_data(\n                service, name, session, token, nearestn, datacenters,\n                onlypassing, tags, ttl, regexp\n            )\n            return self.agent.http.put(\n                CB.bool(), path, params=params, data=data)\n\n        def get(self,\n                query_id,\n                token=None,\n                dc=None):\n            """\n            This endpoint will return information about a certain query\n\n            *query_id* the query id to retrieve information about\n\n            *token* is an optional `ACL token`_ to apply to this request.\n\n            *dc* is the datacenter that this agent will communicate with. By\n            default the datacenter of the host is used.\n            """\n            params = []\n            token = token or self.agent.token\n            if token:\n                params.append((\'token\', token))\n            if dc:\n                params.append((\'dc\', dc))\n            return self.agent.http.get(\n                CB.json(), \'/v1/query/%s\' % query_id, params=params)\n\n        def delete(self, query_id, token=None, dc=None):\n            """\n            This endpoint will delete certain query\n\n            *query_id* the query id delete\n\n            *token* is an optional `ACL token`_ to apply to this request.\n\n            *dc* is the datacenter that this agent will communicate with. By\n            default the datacenter of the host is used.\n            """\n            params = []\n            token = token or self.agent.token\n            if token:\n                params.append((\'token\', token))\n            if dc:\n                params.append((\'dc\', dc))\n            return self.agent.http.delete(\n                CB.bool(), \'/v1/query/%s\' % query_id, params=params)\n\n        def execute(self,\n                    query,\n                    token=None,\n                    dc=None,\n                    near=None,\n                    limit=None):\n            """\n            This endpoint will execute certain query\n\n            *query* name or query id to execute\n\n            *token* is an optional `ACL token`_ to apply to this request.\n\n            *dc* is the datacenter that this agent will communicate with. By\n            default the datacenter of the host is used.\n\n            *near* is a node name to sort the resulting list in ascending\n            order based on the estimated round trip time from that node\n\n            *limit* is used to limit the size of the list to the given number\n            of nodes. This is applied after any sorting or shuffling.\n            """\n            params = []\n            token = token or self.agent.token\n            if token:\n                params.append((\'token\', token))\n            if dc:\n                params.append((\'dc\', dc))\n            if near:\n                params.append((\'near\', near))\n            if limit:\n                params.append((\'limit\', limit))\n            return self.agent.http.get(\n                CB.json(), \'/v1/query/%s/execute\' % query, params=params)\n\n        def explain(self,\n                    query,\n                    token=None,\n                    dc=None):\n            """\n            This endpoint shows a fully-rendered query for a given name\n\n            *query* name to explain. This cannot be query id.\n\n            *token* is an optional `ACL token`_ to apply to this request.\n\n            *dc* is the datacenter that this agent will communicate with. By\n            default the datacenter of the host is used.\n            """\n            params = []\n            token = token or self.agent.token\n            if token:\n                params.append((\'token\', token))\n            if dc:\n                params.append((\'dc\', dc))\n            return self.agent.http.get(\n                CB.json(), \'/v1/query/%s/explain\' % query, params=params)\n\n    class Coordinate(object):\n        def __init__(self, agent):\n            self.agent = agent\n\n        def datacenters(self):\n            """\n            Returns the WAN network coordinates for all Consul servers,\n            organized by DCs.\n            """\n            return self.agent.http.get(CB.json(), \'/v1/coordinate/datacenters\')\n\n        def nodes(self, dc=None, index=None, wait=None, consistency=None):\n            """\n            *dc* is the datacenter that this agent will communicate with. By\n            default the datacenter of the host is used.\n\n            *index* is the current Consul index, suitable for making subsequent\n            calls to wait for changes since this query was last run.\n\n            *wait* the maximum duration to wait (e.g. \'10s\') to retrieve\n            a given index. this parameter is only applied if *index* is also\n            specified. the wait time by default is 5 minutes.\n\n            *consistency* can be either \'default\', \'consistent\' or \'stale\'. if\n            not specified *consistency* will the consistency level this client\n            was configured with.\n            """\n            params = []\n            if dc:\n                params.append((\'dc\', dc))\n            if index:\n                params.append((\'index\', index))\n                if wait:\n                    params.append((\'wait\', wait))\n            consistency = consistency or self.agent.consistency\n            if consistency in (\'consistent\', \'stale\'):\n                params.append((consistency, \'1\'))\n            return self.agent.http.get(\n                CB.json(index=True), \'/v1/coordinate/nodes\', params=params)\n\n    class Operator(object):\n        def __init__(self, agent):\n            self.agent = agent\n\n        def raft_config(self):\n            """\n            Returns raft configuration.\n            """\n            return self.agent.http.get(\n                CB.json(), \'/v1/operator/raft/configuration\')\n', 'message': 'Merge pull request #28 from monetate/kv-get-500\n\nRaise ConsulException on kv GET 500 response code.', 'author_name': 'Andy Gayton', 'lang': 'py'}