44"""
55from consulate .api import base
66
7+ _TOKENS = [
8+ 'acl_token' ,
9+ 'acl_agent_token' ,
10+ 'acl_agent_master_token' ,
11+ 'acl_replication_token'
12+ ]
13+
14+
15+ def _validate_check (script , http , interval , ttl ):
16+ """Validate the check arguments passed into check or service creation.
17+
18+ :param script: The optional script to run in the check
19+ :type script: str or None
20+ :param http: The optional HTTP endpoint to use in the check
21+ :type http: str or None
22+ :param interval: The optional check interval to specify
23+ :type interval: int or None
24+ :param ttl: The optional TTL interval for the check
25+ :type ttl: int or None
26+ :raises: ValueError
27+
28+ """
29+ if script is not None and http is not None :
30+ raise ValueError ('Can not specify script and http in the same check' )
31+ if (script is not None or http is not None ) and ttl is not None :
32+ raise ValueError ('Can not specify a script or http check and ttl' )
33+ elif (script or http ) and interval is None :
34+ raise ValueError (
35+ 'An interval is required for check scripts and '
36+ 'http checks.' )
37+ elif interval is not None and \
38+ (not isinstance (interval , int ) or interval < 1 ):
39+ raise ValueError ('interval must be a positive integer' )
40+ elif ttl is not None and (not isinstance (ttl , int ) or ttl < 1 ):
41+ raise ValueError ('ttl must be a positive integer' )
42+
743
844class Agent (base .Endpoint ):
945 """The Consul agent is the core process of Consul. The agent maintains
@@ -24,7 +60,8 @@ def __init__(self, uri, adapter, datacenter=None, token=None):
2460 """
2561 super (Agent , self ).__init__ (uri , adapter , datacenter , token )
2662 self .check = Agent .Check (self ._base_uri , adapter , datacenter , token )
27- self .service = Agent .Service (self ._base_uri , adapter , datacenter , token )
63+ self .service = Agent .Service (
64+ self ._base_uri , adapter , datacenter , token )
2865
2966 class Check (base .Endpoint ):
3067 """One of the primary roles of the agent is the management of system
@@ -94,21 +131,7 @@ def register(self, name,
94131 :raises: ValueError
95132
96133 """
97- # Validate the parameters
98- if script and not interval :
99- raise ValueError ('Must specify interval when using script' )
100- elif script and ttl :
101- raise ValueError ('Can not specify script and ttl together' )
102-
103- if http and not interval :
104- raise ValueError ('Must specify interval when using http' )
105- elif http and ttl :
106- raise ValueError ('Can not specify http and ttl together' )
107-
108- if http and script :
109- raise ValueError ('Can not specify script and http together' )
110-
111- # Register the check
134+ _validate_check (script , http , interval , ttl )
112135 return self ._put_no_response_body (['register' ], None , {
113136 'ID' : check_id ,
114137 'Name' : name ,
@@ -124,39 +147,49 @@ def deregister(self, check_id):
124147 of deregistering the check with the Catalog.
125148
126149 :param str check_id: The check id
150+ :rtype: bool
127151
128152 """
129153 return self ._put_no_response_body (['deregister' , check_id ])
130154
131- def ttl_pass (self , check_id ):
155+ def ttl_pass (self , check_id , note = None ):
132156 """This endpoint is used with a check that is of the TTL type.
133157 When this endpoint is accessed, the status of the check is set to
134158 "passing", and the TTL clock is reset.
135159
136160 :param str check_id: The check id
161+ :param str note: Note to include with the check pass
162+ :rtype: bool
137163
138164 """
139- return self ._put_no_response_body (['pass' , check_id ])
165+ return self ._put_no_response_body (
166+ ['pass' , check_id ], {'note' : note } if note else None )
140167
141- def ttl_warn (self , check_id ):
168+ def ttl_warn (self , check_id , note = None ):
142169 """This endpoint is used with a check that is of the TTL type.
143170 When this endpoint is accessed, the status of the check is set
144171 to "warning", and the TTL clock is reset.
145172
146173 :param str check_id: The check id
174+ :param str note: Note to include with the check warning
175+ :rtype: bool
147176
148177 """
149- return self ._put_no_response_body (['warn' , check_id ])
178+ return self ._put_no_response_body (
179+ ['warn' , check_id ], {'note' : note } if note else None )
150180
151- def ttl_fail (self , check_id ):
181+ def ttl_fail (self , check_id , note = None ):
152182 """This endpoint is used with a check that is of the TTL type.
153183 When this endpoint is accessed, the status of the check is set
154184 to "critical", and the TTL clock is reset.
155185
156186 :param str check_id: The check id
187+ :param str note: Note to include with the check failure
188+ :rtype: bool
157189
158190 """
159- return self ._put_no_response_body (['fail' , check_id ])
191+ return self ._put_no_response_body (
192+ ['fail' , check_id ], {'note' : note } if note else None )
160193
161194 class Service (base .Endpoint ):
162195 """One of the main goals of service discovery is to provide a catalog
@@ -168,51 +201,46 @@ class Service(base.Endpoint):
168201 the HTTP interface.
169202
170203 """
171- CHECK_EXCEPTION = 'check must be a tuple of script, interval, and ttl'
172-
173204 def register (self , name ,
174205 service_id = None ,
175206 address = None ,
176207 port = None ,
177208 tags = None ,
178- check = None ,
209+ script = None ,
179210 interval = None ,
180211 ttl = None ,
181- httpcheck = None ):
212+ http = None ,
213+ enable_tag_override = None ):
182214 """Add a new service to the local agent.
183215
184216 :param str name: The name of the service
185217 :param str service_id: The id for the service (optional)
186218 :param str address: The service IP address
187219 :param int port: The service port
188220 :param list tags: A list of tags for the service
189- :param str check: The path to the check script to run
190- :param str interval: The check execution interval
191- :param str ttl: The TTL for external script check pings
192- :param str httpcheck: An URL to check every interval
221+ :param str script: Optional script to execute to check service
222+ :param int interval: The check execution interval
223+ :param int ttl: The TTL for external script check pings
224+ :param str http: An URL to check every interval
225+ :param bool enable_tag_override: Toggle the tag override feature
193226 :rtype: bool
194227 :raises: ValueError
195228
196229 """
197230 # Validate the parameters
198- if port and not isinstance (port , int ):
231+ if port is not None and not isinstance (port , int ):
199232 raise ValueError ('port must be an integer' )
200- elif tags and not isinstance (tags , list ):
233+ elif tags is not None and not isinstance (tags , list ):
201234 raise ValueError ('tags must be a list of strings' )
202- elif (check or httpcheck ) and ttl :
203- raise ValueError ('Can not specify both a check and ttl' )
204235
205- if (check or httpcheck ) and not interval :
206- raise ValueError ('An interval is required for check scripts and http checks.' )
236+ _validate_check (script , http , interval , ttl )
207237
208238 check_spec = None
209- if check :
210- check_spec = {'script' : check ,
211- 'interval' : interval }
212- elif httpcheck :
213- check_spec = {'HTTP' : httpcheck ,
214- 'interval' : interval }
215- elif ttl :
239+ if script is not None :
240+ check_spec = {'script' : script , 'interval' : interval }
241+ elif http is not None :
242+ check_spec = {'HTTP' : http , 'interval' : interval }
243+ elif ttl is not None :
216244 check_spec = {'TTL' : ttl }
217245
218246 # Build the payload to send to consul
@@ -221,7 +249,8 @@ def register(self, name,
221249 'name' : name ,
222250 'port' : port ,
223251 'address' : address ,
224- 'tags' : tags
252+ 'tags' : tags ,
253+ 'EnableTagOverride' : enable_tag_override
225254 }
226255
227256 if check_spec :
@@ -246,18 +275,18 @@ def deregister(self, service_id):
246275 return self ._put_no_response_body (['deregister' , service_id ])
247276
248277 def checks (self ):
249- """return the all the checks that are registered with the local agent.
278+ """Return the all the checks that are registered with the local agent.
250279 These checks were either provided through configuration files, or
251280 added dynamically using the HTTP API. It is important to note that
252281 the checks known by the agent may be different than those reported
253282 by the Catalog. This is usually due to changes being made while there
254283 is no leader elected. The agent performs active anti-entropy, so in
255284 most situations everything will be in sync within a few seconds.
256285
257- :rtype: list
286+ :rtype: dict
258287
259288 """
260- return self ._get_list (['checks' ])
289+ return self ._get (['checks' ])
261290
262291 def force_leave (self , node ):
263292 """Instructs the agent to force a node into the left state. If a node
@@ -283,6 +312,21 @@ def join(self, address, wan=False):
283312 query_params = {'wan' : 1 } if wan else None
284313 return self ._put_no_response_body (['join' , address ], query_params )
285314
315+ def maintenance (self , enable = True , reason = None ):
316+ """Places the agent into or removes the agent from "maintenance mode".
317+
318+ .. versionadded:: 1.0.0
319+
320+ :param bool enable: Enable or disable maintenance. Default: `True`
321+ :param str reason: The reason for the maintenance
322+ :rtype: bool
323+
324+ """
325+ query_params = {'enable' : enable }
326+ if reason :
327+ query_params ['reason' ] = reason
328+ return self ._put_no_response_body (['maintenance' ], query_params )
329+
286330 def members (self ):
287331 """Returns the members the agent sees in the cluster gossip pool.
288332 Due to the nature of gossip, this is eventually consistent and the
@@ -294,6 +338,38 @@ def members(self):
294338 """
295339 return self ._get_list (['members' ])
296340
341+ def metrics (self ):
342+ """Returns agent's metrics for the most recent finished interval
343+
344+ .. versionadded:: 1.0.0
345+
346+ :rtype: dict
347+
348+ """
349+ return self ._get (['metrics' ])
350+
351+ def monitor (self ):
352+ """Iterator over logs from the local agent.
353+
354+ .. versionadded:: 1.0.0
355+
356+ :rtype: iterator
357+
358+ """
359+ for line in self ._get_stream (['monitor' ]):
360+ yield line
361+
362+ def reload (self ):
363+ """This endpoint instructs the agent to reload its configuration.
364+ Any errors encountered during this process are returned.
365+
366+ .. versionadded:: 1.0.0
367+
368+ :rtype: list
369+
370+ """
371+ return self ._put_response_body (['reload' ]) or None
372+
297373 def services (self ):
298374 """return the all the services that are registered with the local
299375 agent. These services were either provided through configuration
@@ -304,16 +380,43 @@ def services(self):
304380 anti-entropy, so in most situations everything will be in sync
305381 within a few seconds.
306382
307- :rtype: list
383+ :rtype: dict
308384
309385 """
310- return self ._get_list (['services' ])
386+ return self ._get (['services' ])
311387
312388 def self (self ):
313389 """ This endpoint is used to return the configuration and member
314390 information of the local agent under the Config key.
315391
316- :rtype: list
392+ :rtype: dict
393+
394+ """
395+ return self ._get (['self' ])
396+
397+ def token (self , name , value ):
398+ """Update the ACL tokens currently in use by the agent. It can be used
399+ to introduce ACL tokens to the agent for the first time, or to update
400+ tokens that were initially loaded from the agent's configuration.
401+ Tokens are not persisted, so will need to be updated again if the agent
402+ is restarted.
403+
404+ Valid names:
405+
406+ - ``acl_token``
407+ - ``acl_agent_token``
408+ - ``acl_agent_master_token``
409+ - ``acl_replication_token``
410+
411+ .. versionadded:: 1.0.0
412+
413+ :param str name: One of the valid token names.
414+ :param str value: The new token value
415+ :rtype: bool
416+ :raises: ValueError
317417
318418 """
319- return self ._get_list (['self' ])
419+ if name not in _TOKENS :
420+ raise ValueError ('Invalid token name: {}' .format (name ))
421+ return self ._put_no_response_body (
422+ ['token' , name ], {}, {'Token' : value })
0 commit comments