Codebase list golang-github-hashicorp-serf / 05e8e26
Imported Upstream version 0.6.4 Tianon Gravi 8 years ago
242 changed file(s) with 36795 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 # Platform
1 .DS_Store
2
3 # Compiled Object files, Static and Dynamic libs (Shared Objects)
4 *.o
5 *.a
6 *.so
7
8 # Folders
9 bin
10 _obj
11 _test
12 pkg/
13
14 # Architecture specific extensions/prefixes
15 *.[568vq]
16 [568vq].out
17
18 *.cgo1.go
19 *.cgo2.c
20 _cgo_defun.c
21 _cgo_gotypes.go
22 _cgo_export.*
23
24 _testmain.go
25
26 *.exe
27 *.test
28
29 # Website
30 website/build/
31 .vagrant
0 language: go
1
2 go:
3 - tip
4
5 install: make deps
6 script:
7 - make test
8
9 notifications:
10 flowdock:
11 secure: fZrcf9rlh2IrQrlch1sHkn3YI7SKvjGnAl/zyV5D6NROe1Bbr6d3QRMuCXWWdhJHzjKmXk5rIzbqJhUc0PNF7YjxGNKSzqWMQ56KcvN1k8DzlqxpqkcA3Jbs6fXCWo2fssRtZ7hj/wOP1f5n6cc7kzHDt9dgaYJ6nO2fqNPJiTc=
0 ## 0.6.4 (Febuary 12, 2015)
1
2 IMPROVEMENTS:
3
4 * Added merge delegate to Serf library to support application
5 specific logic in cluster merging.
6 * `SERF_RPC_AUTH` environment variable can be used in place of CLI flags.
7 * Display if encryption is enabled in Serf stats
8 * Improved `join` behavior when using DNS resolution
9
10 BUG FIXES:
11
12 * Fixed snapshot file compaction on Windows
13 * Fixed device binding on Windows
14 * Fixed bug with empty keyring
15 * Fixed parsing of ports in some cases
16 * Fixing stability issues under high churn
17
18 MISC:
19
20 * Increased user event size limit to 512 bytes (previously 256)
21
22 ## 0.6.3 (July 10, 2014)
23
24 IMPROVEMENTS:
25
26 * Added `statsite_addr` configuration to stream to statsite
27
28 BUG FIXES:
29
30 * Fixed issue with mDNS flooding when using IPv4 and IPv6
31 * Fixed issue with reloading event handlers
32
33 MISC:
34
35 * Improved failure detection reliability under load
36 * Reduced fsync() use in snapshot file
37 * Improved snapshot file performance
38 * Additional logging to help debug flapping
39
40 ## 0.6.2 (June 16, 2014)
41
42 IMPROVEMENTS:
43
44 * Added `syslog_facility` configuration to set facility
45
46 BUG FIXES:
47
48 * Fixed memory leak in in-memory stats system
49 * Fixed issue that would cause syslog to deadlock
50
51 MISC:
52
53 * Fixed missing prefixes on some log messages
54 * Docs fixes
55
56 ## 0.6.1 (May 29, 2014)
57
58 BUG FIXES:
59
60 * On Windows, a "failed to decode request header" error will no
61 longer be shown on every RPC request.
62
63 * Avoiding holding a lock which can cause monitor/stream commands to
64 fail when an event handler is blocking
65
66 * Fixing conflict response decoding errors
67
68 IMPROVEMENTS:
69
70 * Improved agent CLI usage documentation
71
72 * Warn if an event handler is slow, potentially blocking other events
73
74 ## 0.6.0 (May 8, 2014)
75
76 FEATURES:
77
78 * Support for key rotation when using encryption. This adds a new
79 `serf keys` command, and a `-keyring-file` configuration. Thanks
80 to @ryanuber.
81
82 * New `-tags-file` can be specified to persist changes to tags made
83 via the RPC interface. Thanks to @ryanuber.
84
85 * New `serf info` command to provide operator debugging information,
86 and to get info about the local node.
87
88 * Adding `-retry-join` flag to agent which enables retrying the join
89 until success or `-retry-max` attempts have been made.
90
91 IMPROVEMENTS:
92
93 * New `-rejoin` flag can be used along with a snapshot file to
94 automatically rejoin a cluster.
95
96 * Agent uses circular buffer to invoke handlers, guards against unbounded
97 output lengths.
98
99 * Adding support for logging to syslog
100
101 * The SERF_RPC_ADDR environment variable can be used instead of the
102 `-rpc-addr` flags. Thanks to @lalyos [GH-209].
103
104 * `serf query` can now output the results in a JSON format.
105
106 * Unknown configuration directives generate an error [GH-186].
107 Thanks to @vincentbernat.
108
109 BUG FIXES:
110
111 * Fixing environmental variables with invalid characters. [GH-200].
112 Thanks to @arschles.
113
114 * Fixing issue with tag changes with hard restart before
115 failure detection.
116
117 * Fixing issue with reconnect when using dynamic ports.
118
119 MISC:
120
121 * Improved logging of various error messages
122
123 * Improved debian packaging. Thanks to @vincentbernat.
124
125 ## 0.5.0 (March 12, 2014)
126
127 FEATURES:
128
129 * New `query` command provides a request/response mechanism to do realtime
130 queries across the cluster. [GH-139]
131
132 * Automatic conflict resolution. Serf will detect name conflicts, and use an
133 internal query to determine which node is in the minority and perform a shutdown.
134 [GH-167] [GH-119]
135
136 * New `reachability` command can be used to help diagnose network and configuration
137 issues.
138
139 * Added `member-reap` event to get notified of when Serf removes a failed or left
140 node from the cluster. The reap interval is controlled by `reconnect_timeout` and
141 `tombstone_timeout` respectively. [GH-172]
142
143 IMPROVEMENTS:
144
145 * New Recipes section on the site to share Serf tips. Thanks to @ryanuber. [GH-177]
146
147 * `members` command has new `-name` filter flag. Thanks to @ryanuber [GH-164]
148
149 * New RPC command "members-filtered" to move filtering logic to the agent.
150 Thanks to @ryanuber. [GH-149]
151
152 * `reconnect_interval` and `reconnect_timeout` can be provided to configure
153 agent behavior for attempting to reconnect to failed nodes. [GH-155]
154
155 * `tombstone_interval` can be provided to configure the reap time for nodes
156 that have gracefully left. [GH_172]
157
158 * Agent can be provided `rpc_auth` config to require that RPC is authenticated.
159 All commands can take a `-rpc-auth` flag now. [GH-148]
160
161 BUG FIXES:
162
163 * Fixed config folder in Upstart script. Thanks to @llchen223. [GH-174]
164
165 * Event handlers are correctly invoked when BusyBox is the shell. [GH-156]
166
167 * Event handlers were not being invoked with the correct SERF_TAG_* values
168 if tags were changed using the `tags` command. [GH-169]
169
170 MISC:
171
172 * Support for protocol version 1 (Serf 0.2) has been removed. Serf 0.5 cannot
173 join a cluster that has members running version 0.2.
174
175 ## 0.4.5 (February 25, 2014)
176
177 FEATURES:
178
179 * New `tags` command is available to dynamically update tags without
180 reloading the agent. Thanks to @ryanuber. [GH-126]
181
182 IMPROVEMENTS:
183
184 * Upstart receipe logs output thanks to @breerly [GH-128]
185
186 * `members` can filter on any tag thanks to @hmrm [GH-124]
187
188 * Added vagrant demo to make a simple cluster
189
190 * `members` now columnizes the output thanks to @ryanuber [GH-138]
191
192 * Agent passes its own environment variables through thanks to @mcroydon [GH-142]
193
194 * `-iface` flag can be used to bind to interfaces [GH-145]
195
196 BUG FIXES:
197
198 * -config-dir would cause protocol to be set to 0 if there are no
199 configuration files in the directory [GH-129]
200
201 * Event handlers can filter on 'member-update'
202
203 * User event handler appends new line, this was being omitted
204
205 ## 0.4.1 (February 3, 2014)
206
207 IMPROVEMENTS:
208
209 * mDNS service uses the advertise address instead of bind address
210
211 ## 0.4.0 (January 31, 2014)
212
213 FEATURES:
214
215 * Static `role` has been replaced with dynamic tags. Each agent can have
216 multiple key/value tags associated using `-tag`. Tags can be updated using
217 a SIGHUP and are advertised to the cluster, causing the `member-update` event
218 to be triggered. [GH-111] [GH-98]
219
220 * Serf can automatically discover peers uing mDNS when provided the `-discover`
221 flag. In network environments supporting multicast, no explicit join is needed
222 to find peers. [GH-53]
223
224 * Serf collects telemetry information and simple runtime profiling. Stats can
225 be dumped to stderr by sending a `USR1` signal to Serf. Windows users must use
226 the `BREAK` signal instead. [GH-103]
227
228 * `advertise` flag can be used to set an advertise address different
229 from the bind address. Used for NAT traversal. Thanks to @benagricola [GH-93]
230
231 * `members` command now takes `-format` flag to specify either text or JSON
232 output. Fixed by @ryanuber [GH-97]
233
234 IMPROVEMENTS:
235
236 * User payload always appends a newline when invoking a shell script
237
238 * Severity of "Potential blocking operation" reduced to debug to prevent
239 spurious messages on slow or busy machines.
240
241 BUG FIXES:
242
243 * If an agent is restarted with the same bind address but new name, it
244 will not respond to the old name, causing the old name to enter the
245 `failed` state, instead of having duplicate entries in the `alive` state.
246
247 * `leave_on_interrupt` set to false when not specified, if
248 any config file is provided. This flag is deprecated for
249 `skip_leave_on_interrupt` instead. [GH-94]
250
251 MISC:
252
253 * `-role` configuration has been deprecated in favor of `-tag role=foo`.
254 The flag is still supported but will generate warnings.
255
256 * Support for protocol version 0 (Serf 0.1) has been removed. Serf 0.4 cannot
257 join a cluster that has members running version 0.1.
258
259 ## 0.3.0 (December 5, 2013)
260
261 FEATURES:
262
263 * Dynamic port support, cluster wide consistent config not necessary
264 * Snapshots to automaticaly rejoin cluster after failure and prevent replays [GH-84] [GH-71]
265 * Adding `profile` config to agent, to support WAN, LAN, and Local modes
266 * MsgPack over TCP RPC protocol which can be used to control Serf, send events, and
267 receive events with low latency.
268 * New `leave` CLI command and RPC endpoint to control graceful leaves
269 * Signal handling is controlable, graceful leave behavior on SIGINT/SIGTERM
270 can be specified
271 * SIGHUP can be used to reload configuration
272
273 IMPROVEMENTS:
274
275 * Event handler provides lamport time of user events via SERF_USER_LTIME [GH-68]
276 * Memberlist encryption overhead has been reduced
277 * Filter output of `members` using regular expressions on role and status
278 * `replay_on_join` parameter to control replay with `start_join`
279 * `monitor` works even if the client is behind a NAT
280 * Serf generates warning if binding to public IP without encryption
281
282 BUG FIXES:
283
284 * Prevent unbounded transmit queues [GH-78]
285 * IPv6 addresses can be bound to [GH-72]
286 * Serf join won't hang on a slow/dead node [GH-70]
287 * Serf Leave won't block Shutdown [GH-1]
288
289 ## 0.2.1 (November 6, 2013)
290
291 BUG FIXES:
292
293 * Member role and address not updated on re-join [GH-58]
294
295 ## 0.2.0 (November 1, 2013)
296
297 FEATURES:
298
299 * Protocol versioning features so that upgrades can be done safely.
300 See the website on upgrading Serf for more info.
301 * Can now configure Serf with files or directories of files by specifying
302 the `-config-file` and/or `-config-dir` flags to the agent.
303 * New command `serf force-leave` can be used to force a "failed" node
304 to the "left" state.
305 * Serf now supports message encryption and verification so that it can
306 be used on untrusted networks [GH-25]
307 * The `-join` flag on `serf agent` can be used to join a cluster when
308 starting an agent. [GH-42]
309
310 IMPROVEMENTS:
311
312 * Random staggering of periodic routines to avoid cluster-wide
313 synchronization
314 * Push/Pull timer automatically slows down as cluster grows to avoid
315 congestion
316 * Messages are compressed to reduce bandwidth utilization
317 * `serf members` now provides node roles in output
318 * Joining a cluster will no longer replay all the old events by default,
319 but it can using the `-replay` flag.
320 * User events are coalesced by default, meaning duplicate events (by name)
321 within a short period of time are merged. [GH-8]
322
323 BUG FIXES:
324
325 * Event handlers work on Windows now by executing commands through
326 `cmd /C` [GH-37]
327 * Nodes that previously left and rejoin won't get stuck in 'leaving' state.
328 [GH-18]
329 * Fixing alignment issues on i386 for atomic operations [GH-20]
330 * "trace" log level works [GH-31]
331
332 ## 0.1.1 (October 23, 2013)
333
334 BUG FIXES:
335
336 * Default node name is outputted when "serf agent" is called with no args.
337 * Remove node from reap list after join so a fast re-join doesn't lose the
338 member.
339
340 ## 0.1.0 (October 23, 2013)
341
342 * Initial release
0 Mozilla Public License, version 2.0
1
2 1. Definitions
3
4 1.1. “Contributor”
5
6 means each individual or legal entity that creates, contributes to the
7 creation of, or owns Covered Software.
8
9 1.2. “Contributor Version”
10
11 means the combination of the Contributions of others (if any) used by a
12 Contributor and that particular Contributor’s Contribution.
13
14 1.3. “Contribution”
15
16 means Covered Software of a particular Contributor.
17
18 1.4. “Covered Software”
19
20 means Source Code Form to which the initial Contributor has attached the
21 notice in Exhibit A, the Executable Form of such Source Code Form, and
22 Modifications of such Source Code Form, in each case including portions
23 thereof.
24
25 1.5. “Incompatible With Secondary Licenses”
26 means
27
28 a. that the initial Contributor has attached the notice described in
29 Exhibit B to the Covered Software; or
30
31 b. that the Covered Software was made available under the terms of version
32 1.1 or earlier of the License, but not also under the terms of a
33 Secondary License.
34
35 1.6. “Executable Form”
36
37 means any form of the work other than Source Code Form.
38
39 1.7. “Larger Work”
40
41 means a work that combines Covered Software with other material, in a separate
42 file or files, that is not Covered Software.
43
44 1.8. “License”
45
46 means this document.
47
48 1.9. “Licensable”
49
50 means having the right to grant, to the maximum extent possible, whether at the
51 time of the initial grant or subsequently, any and all of the rights conveyed by
52 this License.
53
54 1.10. “Modifications”
55
56 means any of the following:
57
58 a. any file in Source Code Form that results from an addition to, deletion
59 from, or modification of the contents of Covered Software; or
60
61 b. any new file in Source Code Form that contains any Covered Software.
62
63 1.11. “Patent Claims” of a Contributor
64
65 means any patent claim(s), including without limitation, method, process,
66 and apparatus claims, in any patent Licensable by such Contributor that
67 would be infringed, but for the grant of the License, by the making,
68 using, selling, offering for sale, having made, import, or transfer of
69 either its Contributions or its Contributor Version.
70
71 1.12. “Secondary License”
72
73 means either the GNU General Public License, Version 2.0, the GNU Lesser
74 General Public License, Version 2.1, the GNU Affero General Public
75 License, Version 3.0, or any later versions of those licenses.
76
77 1.13. “Source Code Form”
78
79 means the form of the work preferred for making modifications.
80
81 1.14. “You” (or “Your”)
82
83 means an individual or a legal entity exercising rights under this
84 License. For legal entities, “You” includes any entity that controls, is
85 controlled by, or is under common control with You. For purposes of this
86 definition, “control” means (a) the power, direct or indirect, to cause
87 the direction or management of such entity, whether by contract or
88 otherwise, or (b) ownership of more than fifty percent (50%) of the
89 outstanding shares or beneficial ownership of such entity.
90
91
92 2. License Grants and Conditions
93
94 2.1. Grants
95
96 Each Contributor hereby grants You a world-wide, royalty-free,
97 non-exclusive license:
98
99 a. under intellectual property rights (other than patent or trademark)
100 Licensable by such Contributor to use, reproduce, make available,
101 modify, display, perform, distribute, and otherwise exploit its
102 Contributions, either on an unmodified basis, with Modifications, or as
103 part of a Larger Work; and
104
105 b. under Patent Claims of such Contributor to make, use, sell, offer for
106 sale, have made, import, and otherwise transfer either its Contributions
107 or its Contributor Version.
108
109 2.2. Effective Date
110
111 The licenses granted in Section 2.1 with respect to any Contribution become
112 effective for each Contribution on the date the Contributor first distributes
113 such Contribution.
114
115 2.3. Limitations on Grant Scope
116
117 The licenses granted in this Section 2 are the only rights granted under this
118 License. No additional rights or licenses will be implied from the distribution
119 or licensing of Covered Software under this License. Notwithstanding Section
120 2.1(b) above, no patent license is granted by a Contributor:
121
122 a. for any code that a Contributor has removed from Covered Software; or
123
124 b. for infringements caused by: (i) Your and any other third party’s
125 modifications of Covered Software, or (ii) the combination of its
126 Contributions with other software (except as part of its Contributor
127 Version); or
128
129 c. under Patent Claims infringed by Covered Software in the absence of its
130 Contributions.
131
132 This License does not grant any rights in the trademarks, service marks, or
133 logos of any Contributor (except as may be necessary to comply with the
134 notice requirements in Section 3.4).
135
136 2.4. Subsequent Licenses
137
138 No Contributor makes additional grants as a result of Your choice to
139 distribute the Covered Software under a subsequent version of this License
140 (see Section 10.2) or under the terms of a Secondary License (if permitted
141 under the terms of Section 3.3).
142
143 2.5. Representation
144
145 Each Contributor represents that the Contributor believes its Contributions
146 are its original creation(s) or it has sufficient rights to grant the
147 rights to its Contributions conveyed by this License.
148
149 2.6. Fair Use
150
151 This License is not intended to limit any rights You have under applicable
152 copyright doctrines of fair use, fair dealing, or other equivalents.
153
154 2.7. Conditions
155
156 Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
157 Section 2.1.
158
159
160 3. Responsibilities
161
162 3.1. Distribution of Source Form
163
164 All distribution of Covered Software in Source Code Form, including any
165 Modifications that You create or to which You contribute, must be under the
166 terms of this License. You must inform recipients that the Source Code Form
167 of the Covered Software is governed by the terms of this License, and how
168 they can obtain a copy of this License. You may not attempt to alter or
169 restrict the recipients’ rights in the Source Code Form.
170
171 3.2. Distribution of Executable Form
172
173 If You distribute Covered Software in Executable Form then:
174
175 a. such Covered Software must also be made available in Source Code Form,
176 as described in Section 3.1, and You must inform recipients of the
177 Executable Form how they can obtain a copy of such Source Code Form by
178 reasonable means in a timely manner, at a charge no more than the cost
179 of distribution to the recipient; and
180
181 b. You may distribute such Executable Form under the terms of this License,
182 or sublicense it under different terms, provided that the license for
183 the Executable Form does not attempt to limit or alter the recipients’
184 rights in the Source Code Form under this License.
185
186 3.3. Distribution of a Larger Work
187
188 You may create and distribute a Larger Work under terms of Your choice,
189 provided that You also comply with the requirements of this License for the
190 Covered Software. If the Larger Work is a combination of Covered Software
191 with a work governed by one or more Secondary Licenses, and the Covered
192 Software is not Incompatible With Secondary Licenses, this License permits
193 You to additionally distribute such Covered Software under the terms of
194 such Secondary License(s), so that the recipient of the Larger Work may, at
195 their option, further distribute the Covered Software under the terms of
196 either this License or such Secondary License(s).
197
198 3.4. Notices
199
200 You may not remove or alter the substance of any license notices (including
201 copyright notices, patent notices, disclaimers of warranty, or limitations
202 of liability) contained within the Source Code Form of the Covered
203 Software, except that You may alter any license notices to the extent
204 required to remedy known factual inaccuracies.
205
206 3.5. Application of Additional Terms
207
208 You may choose to offer, and to charge a fee for, warranty, support,
209 indemnity or liability obligations to one or more recipients of Covered
210 Software. However, You may do so only on Your own behalf, and not on behalf
211 of any Contributor. You must make it absolutely clear that any such
212 warranty, support, indemnity, or liability obligation is offered by You
213 alone, and You hereby agree to indemnify every Contributor for any
214 liability incurred by such Contributor as a result of warranty, support,
215 indemnity or liability terms You offer. You may include additional
216 disclaimers of warranty and limitations of liability specific to any
217 jurisdiction.
218
219 4. Inability to Comply Due to Statute or Regulation
220
221 If it is impossible for You to comply with any of the terms of this License
222 with respect to some or all of the Covered Software due to statute, judicial
223 order, or regulation then You must: (a) comply with the terms of this License
224 to the maximum extent possible; and (b) describe the limitations and the code
225 they affect. Such description must be placed in a text file included with all
226 distributions of the Covered Software under this License. Except to the
227 extent prohibited by statute or regulation, such description must be
228 sufficiently detailed for a recipient of ordinary skill to be able to
229 understand it.
230
231 5. Termination
232
233 5.1. The rights granted under this License will terminate automatically if You
234 fail to comply with any of its terms. However, if You become compliant,
235 then the rights granted under this License from a particular Contributor
236 are reinstated (a) provisionally, unless and until such Contributor
237 explicitly and finally terminates Your grants, and (b) on an ongoing basis,
238 if such Contributor fails to notify You of the non-compliance by some
239 reasonable means prior to 60 days after You have come back into compliance.
240 Moreover, Your grants from a particular Contributor are reinstated on an
241 ongoing basis if such Contributor notifies You of the non-compliance by
242 some reasonable means, this is the first time You have received notice of
243 non-compliance with this License from such Contributor, and You become
244 compliant prior to 30 days after Your receipt of the notice.
245
246 5.2. If You initiate litigation against any entity by asserting a patent
247 infringement claim (excluding declaratory judgment actions, counter-claims,
248 and cross-claims) alleging that a Contributor Version directly or
249 indirectly infringes any patent, then the rights granted to You by any and
250 all Contributors for the Covered Software under Section 2.1 of this License
251 shall terminate.
252
253 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
254 license agreements (excluding distributors and resellers) which have been
255 validly granted by You or Your distributors under this License prior to
256 termination shall survive termination.
257
258 6. Disclaimer of Warranty
259
260 Covered Software is provided under this License on an “as is” basis, without
261 warranty of any kind, either expressed, implied, or statutory, including,
262 without limitation, warranties that the Covered Software is free of defects,
263 merchantable, fit for a particular purpose or non-infringing. The entire
264 risk as to the quality and performance of the Covered Software is with You.
265 Should any Covered Software prove defective in any respect, You (not any
266 Contributor) assume the cost of any necessary servicing, repair, or
267 correction. This disclaimer of warranty constitutes an essential part of this
268 License. No use of any Covered Software is authorized under this License
269 except under this disclaimer.
270
271 7. Limitation of Liability
272
273 Under no circumstances and under no legal theory, whether tort (including
274 negligence), contract, or otherwise, shall any Contributor, or anyone who
275 distributes Covered Software as permitted above, be liable to You for any
276 direct, indirect, special, incidental, or consequential damages of any
277 character including, without limitation, damages for lost profits, loss of
278 goodwill, work stoppage, computer failure or malfunction, or any and all
279 other commercial damages or losses, even if such party shall have been
280 informed of the possibility of such damages. This limitation of liability
281 shall not apply to liability for death or personal injury resulting from such
282 party’s negligence to the extent applicable law prohibits such limitation.
283 Some jurisdictions do not allow the exclusion or limitation of incidental or
284 consequential damages, so this exclusion and limitation may not apply to You.
285
286 8. Litigation
287
288 Any litigation relating to this License may be brought only in the courts of
289 a jurisdiction where the defendant maintains its principal place of business
290 and such litigation shall be governed by laws of that jurisdiction, without
291 reference to its conflict-of-law provisions. Nothing in this Section shall
292 prevent a party’s ability to bring cross-claims or counter-claims.
293
294 9. Miscellaneous
295
296 This License represents the complete agreement concerning the subject matter
297 hereof. If any provision of this License is held to be unenforceable, such
298 provision shall be reformed only to the extent necessary to make it
299 enforceable. Any law or regulation which provides that the language of a
300 contract shall be construed against the drafter shall not be used to construe
301 this License against a Contributor.
302
303
304 10. Versions of the License
305
306 10.1. New Versions
307
308 Mozilla Foundation is the license steward. Except as provided in Section
309 10.3, no one other than the license steward has the right to modify or
310 publish new versions of this License. Each version will be given a
311 distinguishing version number.
312
313 10.2. Effect of New Versions
314
315 You may distribute the Covered Software under the terms of the version of
316 the License under which You originally received the Covered Software, or
317 under the terms of any subsequent version published by the license
318 steward.
319
320 10.3. Modified Versions
321
322 If you create software not governed by this License, and you want to
323 create a new license for such software, you may create and use a modified
324 version of this License if you rename the license and remove any
325 references to the name of the license steward (except to note that such
326 modified license differs from this License).
327
328 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
329 If You choose to distribute Source Code Form that is Incompatible With
330 Secondary Licenses under the terms of this version of the License, the
331 notice described in Exhibit B of this License must be attached.
332
333 Exhibit A - Source Code Form License Notice
334
335 This Source Code Form is subject to the
336 terms of the Mozilla Public License, v.
337 2.0. If a copy of the MPL was not
338 distributed with this file, You can
339 obtain one at
340 http://mozilla.org/MPL/2.0/.
341
342 If it is not possible or desirable to put the notice in a particular file, then
343 You may include the notice in a location (such as a LICENSE file in a relevant
344 directory) where a recipient would be likely to look for such a notice.
345
346 You may add additional accurate notices of copyright ownership.
347
348 Exhibit B - “Incompatible With Secondary Licenses” Notice
349
350 This Source Code Form is “Incompatible
351 With Secondary Licenses”, as defined by
352 the Mozilla Public License, v. 2.0.
353
0 DEPS = $(go list -f '{{range .TestImports}}{{.}} {{end}}' ./...)
1
2 all: deps
3 @mkdir -p bin/
4 @bash --norc -i ./scripts/build.sh
5
6 cov:
7 gocov test ./... | gocov-html > /tmp/coverage.html
8 open /tmp/coverage.html
9
10 deps:
11 go get -d -v ./...
12 echo $(DEPS) | xargs -n1 go get -d
13
14 test: deps subnet
15 go list ./... | xargs -n1 go test
16
17 integ: subnet
18 go list ./... | INTEG_TESTS=yes xargs -n1 go test
19
20 subnet:
21 ./scripts/setup_test_subnet.sh
22
23 web:
24 ./scripts/website_run.sh
25
26 web-push:
27 ./scripts/website_push.sh
28
29 .PHONY: all cov deps integ subnet test web web-push
0 # Serf
1
2 * Website: http://www.serfdom.io
3 * IRC: `#serfdom` on Freenode
4 * Mailing list: [Google Groups](https://groups.google.com/group/serfdom/)
5
6 Serf is a decentralized solution for service discovery and orchestration
7 that is lightweight, highly available, and fault tolerant.
8
9 Serf runs on Linux, Mac OS X, and Windows. An efficient and lightweight gossip
10 protocol is used to communicate with other nodes. Serf can detect node failures
11 and notify the rest of the cluster. An event system is built on top of
12 Serf, letting you use Serf's gossip protocol to propagate events such
13 as deploys, configuration changes, etc. Serf is completely masterless
14 with no single point of failure.
15
16 Here are some example use cases of Serf, though there are many others:
17
18 * Discovering web servers and automatically adding them to a load balancer
19 * Organizing many memcached or redis nodes into a cluster, perhaps with
20 something like [twemproxy](https://github.com/twitter/twemproxy) or
21 maybe just configuring an application with the address of all the
22 nodes
23 * Triggering web deploys using the event system built on top of Serf
24 * Propagating changes to configuration to relevant nodes.
25 * Updating DNS records to reflect cluster changes as they occur.
26 * Much, much more.
27
28 ## Quick Start
29
30 First, [download a pre-built Serf binary](http://www.serfdom.io/downloads.html)
31 for your operating system or [compile Serf yourself](#developing-serf).
32
33 Next, let's start a couple Serf agents. Agents run until they're told to quit
34 and handle the communication of maintenance tasks of Serf. In a real Serf
35 setup, each node in your system will run one or more Serf agents (it can
36 run multiple agents if you're running multiple cluster types. e.g. web
37 servers vs. memcached servers).
38
39 Start each Serf agent in a separate terminal session so that we can see
40 the output of each. Start the first agent:
41
42 ```
43 $ serf agent -node=foo -bind=127.0.0.1:5000 -rpc-addr=127.0.0.1:7373
44 ...
45 ```
46
47 Start the second agent in another terminal session (while the first is still
48 running):
49
50 ```
51 $ serf agent -node=bar -bind=127.0.0.1:5001 -rpc-addr=127.0.0.1:7374
52 ...
53 ```
54
55 At this point two Serf agents are running independently but are still
56 unaware of each other. Let's now tell the first agent to join an existing
57 cluster (the second agent). When starting a Serf agent, you must join an
58 existing cluster by specifying at least one existing member. After this,
59 Serf gossips and the remainder of the cluster becomes aware of the join.
60 Run the following commands in a third terminal session.
61
62 ```
63 $ serf join 127.0.0.1:5001
64 ...
65 ```
66
67 If you're watching your terminals, you should see both Serf agents
68 become aware of the join. You can prove it by running `serf members`
69 to see the members of the Serf cluster:
70
71 ```
72 $ serf members
73 foo 127.0.0.1:5000 alive
74 bar 127.0.0.1:5001 alive
75 ...
76 ```
77
78 At this point, you can ctrl-C or force kill either Serf agent, and they'll
79 update their membership lists appropriately. If you ctrl-C a Serf agent,
80 it will gracefully leave by notifying the cluster of its intent to leave.
81 If you force kill an agent, it will eventually (usually within seconds)
82 be detected by another member of the cluster which will notify the
83 cluster of the node failure.
84
85 ## Documentation
86
87 Full, comprehensive documentation is viewable on the Serf website:
88
89 http://www.serfdom.io/docs
90
91 ## Developing Serf
92
93 If you wish to work on Serf itself, you'll first need [Go](http://golang.org)
94 installed (version 1.2+ is _required_). Make sure you have Go properly installed,
95 including setting up your [GOPATH](http://golang.org/doc/code.html#GOPATH).
96
97 Next, clone this repository into `$GOPATH/src/github.com/hashicorp/serf` and
98 then just type `make`. In a few moments, you'll have a working `serf` executable:
99
100 ```
101 $ make
102 ...
103 $ bin/serf
104 ...
105 ```
106
107 *note: `make` will also place a copy of the binary in the first part of your $GOPATH*
108
109 You can run tests by typing `make test`.
110
111 If you make any changes to the code, run `make format` in order to automatically
112 format the code according to Go standards.
0 # Serf Client
1
2 This repo provide the `client` package, which is used to interact with
3 a Serf agent using the msgpack RPC system it supports. This is the official
4 reference implementation, and is used inside the Serf CLI to support the various
5 commands.
6
7 Full documentation can be found on [godoc here](http://godoc.org/github.com/hashicorp/serf/client).
8
0 package client
1
2 import (
3 "github.com/hashicorp/serf/serf"
4 "net"
5 "time"
6 )
7
8 const (
9 maxIPCVersion = 1
10 )
11
12 const (
13 handshakeCommand = "handshake"
14 eventCommand = "event"
15 forceLeaveCommand = "force-leave"
16 joinCommand = "join"
17 membersCommand = "members"
18 membersFilteredCommand = "members-filtered"
19 streamCommand = "stream"
20 stopCommand = "stop"
21 monitorCommand = "monitor"
22 leaveCommand = "leave"
23 installKeyCommand = "install-key"
24 useKeyCommand = "use-key"
25 removeKeyCommand = "remove-key"
26 listKeysCommand = "list-keys"
27 tagsCommand = "tags"
28 queryCommand = "query"
29 respondCommand = "respond"
30 authCommand = "auth"
31 statsCommand = "stats"
32 )
33
34 const (
35 unsupportedCommand = "Unsupported command"
36 unsupportedIPCVersion = "Unsupported IPC version"
37 duplicateHandshake = "Handshake already performed"
38 handshakeRequired = "Handshake required"
39 monitorExists = "Monitor already exists"
40 invalidFilter = "Invalid event filter"
41 streamExists = "Stream with given sequence exists"
42 invalidQueryID = "No pending queries matching ID"
43 authRequired = "Authentication required"
44 invalidAuthToken = "Invalid authentication token"
45 )
46
47 const (
48 queryRecordAck = "ack"
49 queryRecordResponse = "response"
50 queryRecordDone = "done"
51 )
52
53 // Request header is sent before each request
54 type requestHeader struct {
55 Command string
56 Seq uint64
57 }
58
59 // Response header is sent before each response
60 type responseHeader struct {
61 Seq uint64
62 Error string
63 }
64
65 type handshakeRequest struct {
66 Version int32
67 }
68
69 type authRequest struct {
70 AuthKey string
71 }
72
73 type eventRequest struct {
74 Name string
75 Payload []byte
76 Coalesce bool
77 }
78
79 type forceLeaveRequest struct {
80 Node string
81 }
82
83 type joinRequest struct {
84 Existing []string
85 Replay bool
86 }
87
88 type joinResponse struct {
89 Num int32
90 }
91
92 type membersFilteredRequest struct {
93 Tags map[string]string
94 Status string
95 Name string
96 }
97
98 type membersResponse struct {
99 Members []Member
100 }
101
102 type keyRequest struct {
103 Key string
104 }
105
106 type keyResponse struct {
107 Messages map[string]string
108 Keys map[string]int
109 NumNodes int
110 NumErr int
111 NumResp int
112 }
113
114 type monitorRequest struct {
115 LogLevel string
116 }
117
118 type streamRequest struct {
119 Type string
120 }
121
122 type stopRequest struct {
123 Stop uint64
124 }
125
126 type tagsRequest struct {
127 Tags map[string]string
128 DeleteTags []string
129 }
130
131 type queryRequest struct {
132 FilterNodes []string
133 FilterTags map[string]string
134 RequestAck bool
135 Timeout time.Duration
136 Name string
137 Payload []byte
138 }
139
140 type respondRequest struct {
141 ID uint64
142 Payload []byte
143 }
144
145 type queryRecord struct {
146 Type string
147 From string
148 Payload []byte
149 }
150
151 // NodeResponse is used to return the response of a query
152 type NodeResponse struct {
153 From string
154 Payload []byte
155 }
156
157 type logRecord struct {
158 Log string
159 }
160
161 type userEventRecord struct {
162 Event string
163 LTime serf.LamportTime
164 Name string
165 Payload []byte
166 Coalesce bool
167 }
168
169 // Member is used to represent a single member of the
170 // Serf cluster
171 type Member struct {
172 Name string // Node name
173 Addr net.IP // Address of the Serf node
174 Port uint16 // Gossip port used by Serf
175 Tags map[string]string
176 Status string
177 ProtocolMin uint8 // Minimum supported Memberlist protocol
178 ProtocolMax uint8 // Maximum supported Memberlist protocol
179 ProtocolCur uint8 // Currently set Memberlist protocol
180 DelegateMin uint8 // Minimum supported Serf protocol
181 DelegateMax uint8 // Maximum supported Serf protocol
182 DelegateCur uint8 // Currently set Serf protocol
183 }
184
185 type memberEventRecord struct {
186 Event string
187 Members []Member
188 }
0 package client
1
2 import (
3 "bufio"
4 "fmt"
5 "github.com/hashicorp/go-msgpack/codec"
6 "github.com/hashicorp/logutils"
7 "log"
8 "net"
9 "sync"
10 "sync/atomic"
11 "time"
12 )
13
14 const (
15 // This is the default IO timeout for the client
16 DefaultTimeout = 10 * time.Second
17 )
18
19 var (
20 clientClosed = fmt.Errorf("client closed")
21 )
22
23 type seqCallback struct {
24 handler func(*responseHeader)
25 }
26
27 func (sc *seqCallback) Handle(resp *responseHeader) {
28 sc.handler(resp)
29 }
30 func (sc *seqCallback) Cleanup() {}
31
32 // seqHandler interface is used to handle responses
33 type seqHandler interface {
34 Handle(*responseHeader)
35 Cleanup()
36 }
37
38 // Config is provided to ClientFromConfig to make
39 // a new RPCClient from the given configuration
40 type Config struct {
41 // Addr must be the RPC address to contact
42 Addr string
43
44 // If provided, the client will perform key based auth
45 AuthKey string
46
47 // If provided, overrides the DefaultTimeout used for
48 // IO deadlines
49 Timeout time.Duration
50 }
51
52 // RPCClient is used to make requests to the Agent using an RPC mechanism.
53 // Additionally, the client manages event streams and monitors, enabling a client
54 // to easily receive event notifications instead of using the fork/exec mechanism.
55 type RPCClient struct {
56 seq uint64
57
58 timeout time.Duration
59 conn *net.TCPConn
60 reader *bufio.Reader
61 writer *bufio.Writer
62 dec *codec.Decoder
63 enc *codec.Encoder
64 writeLock sync.Mutex
65
66 dispatch map[uint64]seqHandler
67 dispatchLock sync.Mutex
68
69 shutdown bool
70 shutdownCh chan struct{}
71 shutdownLock sync.Mutex
72 }
73
74 // send is used to send an object using the MsgPack encoding. send
75 // is serialized to prevent write overlaps, while properly buffering.
76 func (c *RPCClient) send(header *requestHeader, obj interface{}) error {
77 c.writeLock.Lock()
78 defer c.writeLock.Unlock()
79
80 if c.shutdown {
81 return clientClosed
82 }
83
84 // Setup an IO deadline, this way we won't wait indefinitely
85 // if the client has hung.
86 if err := c.conn.SetWriteDeadline(time.Now().Add(c.timeout)); err != nil {
87 return err
88 }
89
90 if err := c.enc.Encode(header); err != nil {
91 return err
92 }
93
94 if obj != nil {
95 if err := c.enc.Encode(obj); err != nil {
96 return err
97 }
98 }
99
100 if err := c.writer.Flush(); err != nil {
101 return err
102 }
103
104 return nil
105 }
106
107 // NewRPCClient is used to create a new RPC client given the
108 // RPC address of the Serf agent. This will return a client,
109 // or an error if the connection could not be established.
110 // This will use the DefaultTimeout for the client.
111 func NewRPCClient(addr string) (*RPCClient, error) {
112 conf := Config{Addr: addr}
113 return ClientFromConfig(&conf)
114 }
115
116 // ClientFromConfig is used to create a new RPC client given the
117 // configuration object. This will return a client, or an error if
118 // the connection could not be established.
119 func ClientFromConfig(c *Config) (*RPCClient, error) {
120 // Setup the defaults
121 if c.Timeout == 0 {
122 c.Timeout = DefaultTimeout
123 }
124
125 // Try to dial to serf
126 conn, err := net.DialTimeout("tcp", c.Addr, c.Timeout)
127 if err != nil {
128 return nil, err
129 }
130
131 // Create the client
132 client := &RPCClient{
133 seq: 0,
134 timeout: c.Timeout,
135 conn: conn.(*net.TCPConn),
136 reader: bufio.NewReader(conn),
137 writer: bufio.NewWriter(conn),
138 dispatch: make(map[uint64]seqHandler),
139 shutdownCh: make(chan struct{}),
140 }
141 client.dec = codec.NewDecoder(client.reader,
142 &codec.MsgpackHandle{RawToString: true, WriteExt: true})
143 client.enc = codec.NewEncoder(client.writer,
144 &codec.MsgpackHandle{RawToString: true, WriteExt: true})
145 go client.listen()
146
147 // Do the initial handshake
148 if err := client.handshake(); err != nil {
149 client.Close()
150 return nil, err
151 }
152
153 // Do the initial authentication if needed
154 if c.AuthKey != "" {
155 if err := client.auth(c.AuthKey); err != nil {
156 client.Close()
157 return nil, err
158 }
159 }
160
161 return client, err
162 }
163
164 // StreamHandle is an opaque handle passed to stop to stop streaming
165 type StreamHandle uint64
166
167 func (c *RPCClient) IsClosed() bool {
168 return c.shutdown
169 }
170
171 // Close is used to free any resources associated with the client
172 func (c *RPCClient) Close() error {
173 c.shutdownLock.Lock()
174 defer c.shutdownLock.Unlock()
175
176 if !c.shutdown {
177 c.shutdown = true
178 close(c.shutdownCh)
179 c.deregisterAll()
180 return c.conn.Close()
181 }
182 return nil
183 }
184
185 // ForceLeave is used to ask the agent to issue a leave command for
186 // a given node
187 func (c *RPCClient) ForceLeave(node string) error {
188 header := requestHeader{
189 Command: forceLeaveCommand,
190 Seq: c.getSeq(),
191 }
192 req := forceLeaveRequest{
193 Node: node,
194 }
195 return c.genericRPC(&header, &req, nil)
196 }
197
198 // Join is used to instruct the agent to attempt a join
199 func (c *RPCClient) Join(addrs []string, replay bool) (int, error) {
200 header := requestHeader{
201 Command: joinCommand,
202 Seq: c.getSeq(),
203 }
204 req := joinRequest{
205 Existing: addrs,
206 Replay: replay,
207 }
208 var resp joinResponse
209
210 err := c.genericRPC(&header, &req, &resp)
211 return int(resp.Num), err
212 }
213
214 // Members is used to fetch a list of known members
215 func (c *RPCClient) Members() ([]Member, error) {
216 header := requestHeader{
217 Command: membersCommand,
218 Seq: c.getSeq(),
219 }
220 var resp membersResponse
221
222 err := c.genericRPC(&header, nil, &resp)
223 return resp.Members, err
224 }
225
226 // MembersFiltered returns a subset of members
227 func (c *RPCClient) MembersFiltered(tags map[string]string, status string,
228 name string) ([]Member, error) {
229 header := requestHeader{
230 Command: membersFilteredCommand,
231 Seq: c.getSeq(),
232 }
233 req := membersFilteredRequest{
234 Tags: tags,
235 Status: status,
236 Name: name,
237 }
238 var resp membersResponse
239
240 err := c.genericRPC(&header, &req, &resp)
241 return resp.Members, err
242 }
243
244 // UserEvent is used to trigger sending an event
245 func (c *RPCClient) UserEvent(name string, payload []byte, coalesce bool) error {
246 header := requestHeader{
247 Command: eventCommand,
248 Seq: c.getSeq(),
249 }
250 req := eventRequest{
251 Name: name,
252 Payload: payload,
253 Coalesce: coalesce,
254 }
255 return c.genericRPC(&header, &req, nil)
256 }
257
258 // Leave is used to trigger a graceful leave and shutdown of the agent
259 func (c *RPCClient) Leave() error {
260 header := requestHeader{
261 Command: leaveCommand,
262 Seq: c.getSeq(),
263 }
264 return c.genericRPC(&header, nil, nil)
265 }
266
267 // UpdateTags will modify the tags on a running serf agent
268 func (c *RPCClient) UpdateTags(tags map[string]string, delTags []string) error {
269 header := requestHeader{
270 Command: tagsCommand,
271 Seq: c.getSeq(),
272 }
273 req := tagsRequest{
274 Tags: tags,
275 DeleteTags: delTags,
276 }
277 return c.genericRPC(&header, &req, nil)
278 }
279
280 // Respond allows a client to respond to a query event. The ID is the
281 // ID of the Query to respond to, and the given payload is the response.
282 func (c *RPCClient) Respond(id uint64, buf []byte) error {
283 header := requestHeader{
284 Command: respondCommand,
285 Seq: c.getSeq(),
286 }
287 req := respondRequest{
288 ID: id,
289 Payload: buf,
290 }
291 return c.genericRPC(&header, &req, nil)
292 }
293
294 // IntallKey installs a new encryption key onto the keyring
295 func (c *RPCClient) InstallKey(key string) (map[string]string, error) {
296 header := requestHeader{
297 Command: installKeyCommand,
298 Seq: c.getSeq(),
299 }
300 req := keyRequest{
301 Key: key,
302 }
303
304 resp := keyResponse{}
305 err := c.genericRPC(&header, &req, &resp)
306
307 return resp.Messages, err
308 }
309
310 // UseKey changes the primary encryption key on the keyring
311 func (c *RPCClient) UseKey(key string) (map[string]string, error) {
312 header := requestHeader{
313 Command: useKeyCommand,
314 Seq: c.getSeq(),
315 }
316 req := keyRequest{
317 Key: key,
318 }
319
320 resp := keyResponse{}
321 err := c.genericRPC(&header, &req, &resp)
322
323 return resp.Messages, err
324 }
325
326 // RemoveKey changes the primary encryption key on the keyring
327 func (c *RPCClient) RemoveKey(key string) (map[string]string, error) {
328 header := requestHeader{
329 Command: removeKeyCommand,
330 Seq: c.getSeq(),
331 }
332 req := keyRequest{
333 Key: key,
334 }
335
336 resp := keyResponse{}
337 err := c.genericRPC(&header, &req, &resp)
338
339 return resp.Messages, err
340 }
341
342 // ListKeys returns all of the active keys on each member of the cluster
343 func (c *RPCClient) ListKeys() (map[string]int, int, map[string]string, error) {
344 header := requestHeader{
345 Command: listKeysCommand,
346 Seq: c.getSeq(),
347 }
348
349 resp := keyResponse{}
350 err := c.genericRPC(&header, nil, &resp)
351
352 return resp.Keys, resp.NumNodes, resp.Messages, err
353 }
354
355 // Stats is used to get debugging state information
356 func (c *RPCClient) Stats() (map[string]map[string]string, error) {
357 header := requestHeader{
358 Command: statsCommand,
359 Seq: c.getSeq(),
360 }
361 var resp map[string]map[string]string
362
363 err := c.genericRPC(&header, nil, &resp)
364 return resp, err
365 }
366
367 type monitorHandler struct {
368 client *RPCClient
369 closed bool
370 init bool
371 initCh chan<- error
372 logCh chan<- string
373 seq uint64
374 }
375
376 func (mh *monitorHandler) Handle(resp *responseHeader) {
377 // Initialize on the first response
378 if !mh.init {
379 mh.init = true
380 mh.initCh <- strToError(resp.Error)
381 return
382 }
383
384 // Decode logs for all other responses
385 var rec logRecord
386 if err := mh.client.dec.Decode(&rec); err != nil {
387 log.Printf("[ERR] Failed to decode log: %v", err)
388 mh.client.deregisterHandler(mh.seq)
389 return
390 }
391 select {
392 case mh.logCh <- rec.Log:
393 default:
394 log.Printf("[ERR] Dropping log! Monitor channel full")
395 }
396 }
397
398 func (mh *monitorHandler) Cleanup() {
399 if !mh.closed {
400 if !mh.init {
401 mh.init = true
402 mh.initCh <- fmt.Errorf("Stream closed")
403 }
404 if mh.logCh != nil {
405 close(mh.logCh)
406 }
407 mh.closed = true
408 }
409 }
410
411 // Monitor is used to subscribe to the logs of the agent
412 func (c *RPCClient) Monitor(level logutils.LogLevel, ch chan<- string) (StreamHandle, error) {
413 // Setup the request
414 seq := c.getSeq()
415 header := requestHeader{
416 Command: monitorCommand,
417 Seq: seq,
418 }
419 req := monitorRequest{
420 LogLevel: string(level),
421 }
422
423 // Create a monitor handler
424 initCh := make(chan error, 1)
425 handler := &monitorHandler{
426 client: c,
427 initCh: initCh,
428 logCh: ch,
429 seq: seq,
430 }
431 c.handleSeq(seq, handler)
432
433 // Send the request
434 if err := c.send(&header, &req); err != nil {
435 c.deregisterHandler(seq)
436 return 0, err
437 }
438
439 // Wait for a response
440 select {
441 case err := <-initCh:
442 return StreamHandle(seq), err
443 case <-c.shutdownCh:
444 c.deregisterHandler(seq)
445 return 0, clientClosed
446 }
447 }
448
449 type streamHandler struct {
450 client *RPCClient
451 closed bool
452 init bool
453 initCh chan<- error
454 eventCh chan<- map[string]interface{}
455 seq uint64
456 }
457
458 func (sh *streamHandler) Handle(resp *responseHeader) {
459 // Initialize on the first response
460 if !sh.init {
461 sh.init = true
462 sh.initCh <- strToError(resp.Error)
463 return
464 }
465
466 // Decode logs for all other responses
467 var rec map[string]interface{}
468 if err := sh.client.dec.Decode(&rec); err != nil {
469 log.Printf("[ERR] Failed to decode stream record: %v", err)
470 sh.client.deregisterHandler(sh.seq)
471 return
472 }
473 select {
474 case sh.eventCh <- rec:
475 default:
476 log.Printf("[ERR] Dropping event! Stream channel full")
477 }
478 }
479
480 func (sh *streamHandler) Cleanup() {
481 if !sh.closed {
482 if !sh.init {
483 sh.init = true
484 sh.initCh <- fmt.Errorf("Stream closed")
485 }
486 if sh.eventCh != nil {
487 close(sh.eventCh)
488 }
489 sh.closed = true
490 }
491 }
492
493 // Stream is used to subscribe to events
494 func (c *RPCClient) Stream(filter string, ch chan<- map[string]interface{}) (StreamHandle, error) {
495 // Setup the request
496 seq := c.getSeq()
497 header := requestHeader{
498 Command: streamCommand,
499 Seq: seq,
500 }
501 req := streamRequest{
502 Type: filter,
503 }
504
505 // Create a monitor handler
506 initCh := make(chan error, 1)
507 handler := &streamHandler{
508 client: c,
509 initCh: initCh,
510 eventCh: ch,
511 seq: seq,
512 }
513 c.handleSeq(seq, handler)
514
515 // Send the request
516 if err := c.send(&header, &req); err != nil {
517 c.deregisterHandler(seq)
518 return 0, err
519 }
520
521 // Wait for a response
522 select {
523 case err := <-initCh:
524 return StreamHandle(seq), err
525 case <-c.shutdownCh:
526 c.deregisterHandler(seq)
527 return 0, clientClosed
528 }
529 }
530
531 type queryHandler struct {
532 client *RPCClient
533 closed bool
534 init bool
535 initCh chan<- error
536 ackCh chan<- string
537 respCh chan<- NodeResponse
538 seq uint64
539 }
540
541 func (qh *queryHandler) Handle(resp *responseHeader) {
542 // Initialize on the first response
543 if !qh.init {
544 qh.init = true
545 qh.initCh <- strToError(resp.Error)
546 return
547 }
548
549 // Decode the query response
550 var rec queryRecord
551 if err := qh.client.dec.Decode(&rec); err != nil {
552 log.Printf("[ERR] Failed to decode query response: %v", err)
553 qh.client.deregisterHandler(qh.seq)
554 return
555 }
556
557 switch rec.Type {
558 case queryRecordAck:
559 select {
560 case qh.ackCh <- rec.From:
561 default:
562 log.Printf("[ERR] Dropping query ack, channel full")
563 }
564
565 case queryRecordResponse:
566 select {
567 case qh.respCh <- NodeResponse{rec.From, rec.Payload}:
568 default:
569 log.Printf("[ERR] Dropping query response, channel full")
570 }
571
572 case queryRecordDone:
573 // No further records coming
574 qh.client.deregisterHandler(qh.seq)
575
576 default:
577 log.Printf("[ERR] Unrecognized query record type: %s", rec.Type)
578 }
579 }
580
581 func (qh *queryHandler) Cleanup() {
582 if !qh.closed {
583 if !qh.init {
584 qh.init = true
585 qh.initCh <- fmt.Errorf("Stream closed")
586 }
587 if qh.ackCh != nil {
588 close(qh.ackCh)
589 }
590 if qh.respCh != nil {
591 close(qh.respCh)
592 }
593 qh.closed = true
594 }
595 }
596
597 // QueryParam is provided to query set various settings.
598 type QueryParam struct {
599 FilterNodes []string // A list of node names to restrict query to
600 FilterTags map[string]string // A map of tag name to regex to filter on
601 RequestAck bool // Should nodes ack the query receipt
602 Timeout time.Duration // Maximum query duration. Optional, will be set automatically.
603 Name string // Opaque query name
604 Payload []byte // Opaque query payload
605 AckCh chan<- string // Channel to send Ack replies on
606 RespCh chan<- NodeResponse // Channel to send responses on
607 }
608
609 // Query initiates a new query message using the given parameters, and streams
610 // acks and responses over the given channels. The channels will not block on
611 // sends and should be buffered. At the end of the query, the channels will be
612 // closed.
613 func (c *RPCClient) Query(params *QueryParam) error {
614 // Setup the request
615 seq := c.getSeq()
616 header := requestHeader{
617 Command: queryCommand,
618 Seq: seq,
619 }
620 req := queryRequest{
621 FilterNodes: params.FilterNodes,
622 FilterTags: params.FilterTags,
623 RequestAck: params.RequestAck,
624 Timeout: params.Timeout,
625 Name: params.Name,
626 Payload: params.Payload,
627 }
628
629 // Create a query handler
630 initCh := make(chan error, 1)
631 handler := &queryHandler{
632 client: c,
633 initCh: initCh,
634 ackCh: params.AckCh,
635 respCh: params.RespCh,
636 seq: seq,
637 }
638 c.handleSeq(seq, handler)
639
640 // Send the request
641 if err := c.send(&header, &req); err != nil {
642 c.deregisterHandler(seq)
643 return err
644 }
645
646 // Wait for a response
647 select {
648 case err := <-initCh:
649 return err
650 case <-c.shutdownCh:
651 c.deregisterHandler(seq)
652 return clientClosed
653 }
654 }
655
656 // Stop is used to unsubscribe from logs or event streams
657 func (c *RPCClient) Stop(handle StreamHandle) error {
658 // Deregister locally first to stop delivery
659 c.deregisterHandler(uint64(handle))
660
661 header := requestHeader{
662 Command: stopCommand,
663 Seq: c.getSeq(),
664 }
665 req := stopRequest{
666 Stop: uint64(handle),
667 }
668 return c.genericRPC(&header, &req, nil)
669 }
670
671 // handshake is used to perform the initial handshake on connect
672 func (c *RPCClient) handshake() error {
673 header := requestHeader{
674 Command: handshakeCommand,
675 Seq: c.getSeq(),
676 }
677 req := handshakeRequest{
678 Version: maxIPCVersion,
679 }
680 return c.genericRPC(&header, &req, nil)
681 }
682
683 // auth is used to perform the initial authentication on connect
684 func (c *RPCClient) auth(authKey string) error {
685 header := requestHeader{
686 Command: authCommand,
687 Seq: c.getSeq(),
688 }
689 req := authRequest{
690 AuthKey: authKey,
691 }
692 return c.genericRPC(&header, &req, nil)
693 }
694
695 // genericRPC is used to send a request and wait for an
696 // errorSequenceResponse, potentially returning an error
697 func (c *RPCClient) genericRPC(header *requestHeader, req interface{}, resp interface{}) error {
698 // Setup a response handler
699 errCh := make(chan error, 1)
700 handler := func(respHeader *responseHeader) {
701 // If we get an auth error, we should not wait for a request body
702 if respHeader.Error == authRequired {
703 goto SEND_ERR
704 }
705 if resp != nil {
706 err := c.dec.Decode(resp)
707 if err != nil {
708 errCh <- err
709 return
710 }
711 }
712 SEND_ERR:
713 errCh <- strToError(respHeader.Error)
714 }
715 c.handleSeq(header.Seq, &seqCallback{handler: handler})
716 defer c.deregisterHandler(header.Seq)
717
718 // Send the request
719 if err := c.send(header, req); err != nil {
720 return err
721 }
722
723 // Wait for a response
724 select {
725 case err := <-errCh:
726 return err
727 case <-c.shutdownCh:
728 return clientClosed
729 }
730 }
731
732 // strToError converts a string to an error if not blank
733 func strToError(s string) error {
734 if s != "" {
735 return fmt.Errorf(s)
736 }
737 return nil
738 }
739
740 // getSeq returns the next sequence number in a safe manner
741 func (c *RPCClient) getSeq() uint64 {
742 return atomic.AddUint64(&c.seq, 1)
743 }
744
745 // deregisterAll is used to deregister all handlers
746 func (c *RPCClient) deregisterAll() {
747 c.dispatchLock.Lock()
748 defer c.dispatchLock.Unlock()
749
750 for _, seqH := range c.dispatch {
751 seqH.Cleanup()
752 }
753 c.dispatch = make(map[uint64]seqHandler)
754 }
755
756 // deregisterHandler is used to deregister a handler
757 func (c *RPCClient) deregisterHandler(seq uint64) {
758 c.dispatchLock.Lock()
759 seqH, ok := c.dispatch[seq]
760 delete(c.dispatch, seq)
761 c.dispatchLock.Unlock()
762
763 if ok {
764 seqH.Cleanup()
765 }
766 }
767
768 // handleSeq is used to setup a handlerto wait on a response for
769 // a given sequence number.
770 func (c *RPCClient) handleSeq(seq uint64, handler seqHandler) {
771 c.dispatchLock.Lock()
772 defer c.dispatchLock.Unlock()
773 c.dispatch[seq] = handler
774 }
775
776 // respondSeq is used to respond to a given sequence number
777 func (c *RPCClient) respondSeq(seq uint64, respHeader *responseHeader) {
778 c.dispatchLock.Lock()
779 seqL, ok := c.dispatch[seq]
780 c.dispatchLock.Unlock()
781
782 // Get a registered listener, ignore if none
783 if ok {
784 seqL.Handle(respHeader)
785 }
786 }
787
788 // listen is used to processes data coming over the IPC channel,
789 // and wrote it to the correct destination based on seq no
790 func (c *RPCClient) listen() {
791 defer c.Close()
792 var respHeader responseHeader
793 for {
794 if err := c.dec.Decode(&respHeader); err != nil {
795 if !c.shutdown {
796 log.Printf("[ERR] agent.client: Failed to decode response header: %v", err)
797 }
798 break
799 }
800 c.respondSeq(respHeader.Seq, &respHeader)
801 }
802 }
0 package agent
1
2 import (
3 "encoding/base64"
4 "encoding/json"
5 "fmt"
6 "github.com/hashicorp/memberlist"
7 "github.com/hashicorp/serf/serf"
8 "io"
9 "io/ioutil"
10 "log"
11 "os"
12 "strings"
13 "sync"
14 )
15
16 // Agent starts and manages a Serf instance, adding some niceties
17 // on top of Serf such as storing logs that you can later retrieve,
18 // and invoking EventHandlers when events occur.
19 type Agent struct {
20 // Stores the serf configuration
21 conf *serf.Config
22
23 // Stores the agent configuration
24 agentConf *Config
25
26 // eventCh is used for Serf to deliver events on
27 eventCh chan serf.Event
28
29 // eventHandlers is the registered handlers for events
30 eventHandlers map[EventHandler]struct{}
31 eventHandlerList []EventHandler
32 eventHandlersLock sync.Mutex
33
34 // logger instance wraps the logOutput
35 logger *log.Logger
36
37 // This is the underlying Serf we are wrapping
38 serf *serf.Serf
39
40 // shutdownCh is used for shutdowns
41 shutdown bool
42 shutdownCh chan struct{}
43 shutdownLock sync.Mutex
44 }
45
46 // Start creates a new agent, potentially returning an error
47 func Create(agentConf *Config, conf *serf.Config, logOutput io.Writer) (*Agent, error) {
48 // Ensure we have a log sink
49 if logOutput == nil {
50 logOutput = os.Stderr
51 }
52
53 // Setup the underlying loggers
54 conf.MemberlistConfig.LogOutput = logOutput
55 conf.LogOutput = logOutput
56
57 // Create a channel to listen for events from Serf
58 eventCh := make(chan serf.Event, 64)
59 conf.EventCh = eventCh
60
61 // Setup the agent
62 agent := &Agent{
63 conf: conf,
64 agentConf: agentConf,
65 eventCh: eventCh,
66 eventHandlers: make(map[EventHandler]struct{}),
67 logger: log.New(logOutput, "", log.LstdFlags),
68 shutdownCh: make(chan struct{}),
69 }
70
71 // Restore agent tags from a tags file
72 if agentConf.TagsFile != "" {
73 if err := agent.loadTagsFile(agentConf.TagsFile); err != nil {
74 return nil, err
75 }
76 }
77
78 // Load in a keyring file if provided
79 if agentConf.KeyringFile != "" {
80 if err := agent.loadKeyringFile(agentConf.KeyringFile); err != nil {
81 return nil, err
82 }
83 }
84
85 return agent, nil
86 }
87
88 // Start is used to initiate the event listeners. It is separate from
89 // create so that there isn't a race condition between creating the
90 // agent and registering handlers
91 func (a *Agent) Start() error {
92 a.logger.Printf("[INFO] agent: Serf agent starting")
93
94 // Create serf first
95 serf, err := serf.Create(a.conf)
96 if err != nil {
97 return fmt.Errorf("Error creating Serf: %s", err)
98 }
99 a.serf = serf
100
101 // Start event loop
102 go a.eventLoop()
103 return nil
104 }
105
106 // Leave prepares for a graceful shutdown of the agent and its processes
107 func (a *Agent) Leave() error {
108 if a.serf == nil {
109 return nil
110 }
111
112 a.logger.Println("[INFO] agent: requesting graceful leave from Serf")
113 return a.serf.Leave()
114 }
115
116 // Shutdown closes this agent and all of its processes. Should be preceded
117 // by a Leave for a graceful shutdown.
118 func (a *Agent) Shutdown() error {
119 a.shutdownLock.Lock()
120 defer a.shutdownLock.Unlock()
121
122 if a.shutdown {
123 return nil
124 }
125
126 if a.serf == nil {
127 goto EXIT
128 }
129
130 a.logger.Println("[INFO] agent: requesting serf shutdown")
131 if err := a.serf.Shutdown(); err != nil {
132 return err
133 }
134
135 EXIT:
136 a.logger.Println("[INFO] agent: shutdown complete")
137 a.shutdown = true
138 close(a.shutdownCh)
139 return nil
140 }
141
142 // ShutdownCh returns a channel that can be selected to wait
143 // for the agent to perform a shutdown.
144 func (a *Agent) ShutdownCh() <-chan struct{} {
145 return a.shutdownCh
146 }
147
148 // Returns the Serf agent of the running Agent.
149 func (a *Agent) Serf() *serf.Serf {
150 return a.serf
151 }
152
153 // Returns the Serf config of the running Agent.
154 func (a *Agent) SerfConfig() *serf.Config {
155 return a.conf
156 }
157
158 // Join asks the Serf instance to join. See the Serf.Join function.
159 func (a *Agent) Join(addrs []string, replay bool) (n int, err error) {
160 a.logger.Printf("[INFO] agent: joining: %v replay: %v", addrs, replay)
161 ignoreOld := !replay
162 n, err = a.serf.Join(addrs, ignoreOld)
163 if n > 0 {
164 a.logger.Printf("[INFO] agent: joined: %d nodes", n)
165 }
166 if err != nil {
167 a.logger.Printf("[WARN] agent: error joining: %v", err)
168 }
169 return
170 }
171
172 // ForceLeave is used to eject a failed node from the cluster
173 func (a *Agent) ForceLeave(node string) error {
174 a.logger.Printf("[INFO] agent: Force leaving node: %s", node)
175 err := a.serf.RemoveFailedNode(node)
176 if err != nil {
177 a.logger.Printf("[WARN] agent: failed to remove node: %v", err)
178 }
179 return err
180 }
181
182 // UserEvent sends a UserEvent on Serf, see Serf.UserEvent.
183 func (a *Agent) UserEvent(name string, payload []byte, coalesce bool) error {
184 a.logger.Printf("[DEBUG] agent: Requesting user event send: %s. Coalesced: %#v. Payload: %#v",
185 name, coalesce, string(payload))
186 err := a.serf.UserEvent(name, payload, coalesce)
187 if err != nil {
188 a.logger.Printf("[WARN] agent: failed to send user event: %v", err)
189 }
190 return err
191 }
192
193 // Query sends a Query on Serf, see Serf.Query.
194 func (a *Agent) Query(name string, payload []byte, params *serf.QueryParam) (*serf.QueryResponse, error) {
195 // Prevent the use of the internal prefix
196 if strings.HasPrefix(name, serf.InternalQueryPrefix) {
197 // Allow the special "ping" query
198 if name != serf.InternalQueryPrefix+"ping" || payload != nil {
199 return nil, fmt.Errorf("Queries cannot contain the '%s' prefix", serf.InternalQueryPrefix)
200 }
201 }
202 a.logger.Printf("[DEBUG] agent: Requesting query send: %s. Payload: %#v",
203 name, string(payload))
204 resp, err := a.serf.Query(name, payload, params)
205 if err != nil {
206 a.logger.Printf("[WARN] agent: failed to start user query: %v", err)
207 }
208 return resp, err
209 }
210
211 // RegisterEventHandler adds an event handler to receive event notifications
212 func (a *Agent) RegisterEventHandler(eh EventHandler) {
213 a.eventHandlersLock.Lock()
214 defer a.eventHandlersLock.Unlock()
215
216 a.eventHandlers[eh] = struct{}{}
217 a.eventHandlerList = nil
218 for eh := range a.eventHandlers {
219 a.eventHandlerList = append(a.eventHandlerList, eh)
220 }
221 }
222
223 // DeregisterEventHandler removes an EventHandler and prevents more invocations
224 func (a *Agent) DeregisterEventHandler(eh EventHandler) {
225 a.eventHandlersLock.Lock()
226 defer a.eventHandlersLock.Unlock()
227
228 delete(a.eventHandlers, eh)
229 a.eventHandlerList = nil
230 for eh := range a.eventHandlers {
231 a.eventHandlerList = append(a.eventHandlerList, eh)
232 }
233 }
234
235 // eventLoop listens to events from Serf and fans out to event handlers
236 func (a *Agent) eventLoop() {
237 serfShutdownCh := a.serf.ShutdownCh()
238 for {
239 select {
240 case e := <-a.eventCh:
241 a.logger.Printf("[INFO] agent: Received event: %s", e.String())
242 a.eventHandlersLock.Lock()
243 handlers := a.eventHandlerList
244 a.eventHandlersLock.Unlock()
245 for _, eh := range handlers {
246 eh.HandleEvent(e)
247 }
248
249 case <-serfShutdownCh:
250 a.logger.Printf("[WARN] agent: Serf shutdown detected, quitting")
251 a.Shutdown()
252 return
253
254 case <-a.shutdownCh:
255 return
256 }
257 }
258 }
259
260 // InstallKey initiates a query to install a new key on all members
261 func (a *Agent) InstallKey(key string) (*serf.KeyResponse, error) {
262 a.logger.Print("[INFO] agent: Initiating key installation")
263 manager := a.serf.KeyManager()
264 return manager.InstallKey(key)
265 }
266
267 // UseKey sends a query instructing all members to switch primary keys
268 func (a *Agent) UseKey(key string) (*serf.KeyResponse, error) {
269 a.logger.Print("[INFO] agent: Initiating primary key change")
270 manager := a.serf.KeyManager()
271 return manager.UseKey(key)
272 }
273
274 // RemoveKey sends a query to all members to remove a key from the keyring
275 func (a *Agent) RemoveKey(key string) (*serf.KeyResponse, error) {
276 a.logger.Print("[INFO] agent: Initiating key removal")
277 manager := a.serf.KeyManager()
278 return manager.RemoveKey(key)
279 }
280
281 // ListKeys sends a query to all members to return a list of their keys
282 func (a *Agent) ListKeys() (*serf.KeyResponse, error) {
283 a.logger.Print("[INFO] agent: Initiating key listing")
284 manager := a.serf.KeyManager()
285 return manager.ListKeys()
286 }
287
288 // SetTags is used to update the tags. The agent will make sure to
289 // persist tags if necessary before gossiping to the cluster.
290 func (a *Agent) SetTags(tags map[string]string) error {
291 // Update the tags file if we have one
292 if a.agentConf.TagsFile != "" {
293 if err := a.writeTagsFile(tags); err != nil {
294 a.logger.Printf("[ERR] agent: %s", err)
295 return err
296 }
297 }
298
299 // Set the tags in Serf, start gossiping out
300 return a.serf.SetTags(tags)
301 }
302
303 // loadTagsFile will load agent tags out of a file and set them in the
304 // current serf configuration.
305 func (a *Agent) loadTagsFile(tagsFile string) error {
306 // Avoid passing tags and using a tags file at the same time
307 if len(a.agentConf.Tags) > 0 {
308 return fmt.Errorf("Tags config not allowed while using tag files")
309 }
310
311 if _, err := os.Stat(tagsFile); err == nil {
312 tagData, err := ioutil.ReadFile(tagsFile)
313 if err != nil {
314 return fmt.Errorf("Failed to read tags file: %s", err)
315 }
316 if err := json.Unmarshal(tagData, &a.conf.Tags); err != nil {
317 return fmt.Errorf("Failed to decode tags file: %s", err)
318 }
319 a.logger.Printf("[INFO] agent: Restored %d tag(s) from %s",
320 len(a.conf.Tags), tagsFile)
321 }
322
323 // Success!
324 return nil
325 }
326
327 // writeTagsFile will write the current tags to the configured tags file.
328 func (a *Agent) writeTagsFile(tags map[string]string) error {
329 encoded, err := json.MarshalIndent(tags, "", " ")
330 if err != nil {
331 return fmt.Errorf("Failed to encode tags: %s", err)
332 }
333
334 // Use 0600 for permissions, in case tag data is sensitive
335 if err = ioutil.WriteFile(a.agentConf.TagsFile, encoded, 0600); err != nil {
336 return fmt.Errorf("Failed to write tags file: %s", err)
337 }
338
339 // Success!
340 return nil
341 }
342
343 // MarshalTags is a utility function which takes a map of tag key/value pairs
344 // and returns the same tags as strings in 'key=value' format.
345 func MarshalTags(tags map[string]string) []string {
346 var result []string
347 for name, value := range tags {
348 result = append(result, fmt.Sprintf("%s=%s", name, value))
349 }
350 return result
351 }
352
353 // UnmarshalTags is a utility function which takes a slice of strings in
354 // key=value format and returns them as a tag mapping.
355 func UnmarshalTags(tags []string) (map[string]string, error) {
356 result := make(map[string]string)
357 for _, tag := range tags {
358 parts := strings.SplitN(tag, "=", 2)
359 if len(parts) != 2 || len(parts[0]) == 0 {
360 return nil, fmt.Errorf("Invalid tag: '%s'", tag)
361 }
362 result[parts[0]] = parts[1]
363 }
364 return result, nil
365 }
366
367 // loadKeyringFile will load a keyring out of a file
368 func (a *Agent) loadKeyringFile(keyringFile string) error {
369 // Avoid passing an encryption key and a keyring file at the same time
370 if len(a.agentConf.EncryptKey) > 0 {
371 return fmt.Errorf("Encryption key not allowed while using a keyring")
372 }
373
374 if _, err := os.Stat(keyringFile); err != nil {
375 return err
376 }
377
378 // Read in the keyring file data
379 keyringData, err := ioutil.ReadFile(keyringFile)
380 if err != nil {
381 return fmt.Errorf("Failed to read keyring file: %s", err)
382 }
383
384 // Decode keyring JSON
385 keys := make([]string, 0)
386 if err := json.Unmarshal(keyringData, &keys); err != nil {
387 return fmt.Errorf("Failed to decode keyring file: %s", err)
388 }
389
390 // Decode base64 values
391 keysDecoded := make([][]byte, len(keys))
392 for i, key := range keys {
393 keyBytes, err := base64.StdEncoding.DecodeString(key)
394 if err != nil {
395 return fmt.Errorf("Failed to decode key from keyring: %s", err)
396 }
397 keysDecoded[i] = keyBytes
398 }
399
400 // Guard against empty keyring file
401 if len(keysDecoded) == 0 {
402 return fmt.Errorf("Keyring file contains no keys")
403 }
404
405 // Create the keyring
406 keyring, err := memberlist.NewKeyring(keysDecoded, keysDecoded[0])
407 if err != nil {
408 return fmt.Errorf("Failed to restore keyring: %s", err)
409 }
410 a.conf.MemberlistConfig.Keyring = keyring
411 a.logger.Printf("[INFO] agent: Restored keyring with %d keys from %s",
412 len(keys), keyringFile)
413
414 // Success!
415 return nil
416 }
417
418 // Stats is used to get various runtime information and stats
419 func (a *Agent) Stats() map[string]map[string]string {
420 local := a.serf.LocalMember()
421 output := map[string]map[string]string{
422 "agent": map[string]string{
423 "name": local.Name,
424 },
425 "runtime": runtimeStats(),
426 "serf": a.serf.Stats(),
427 "tags": local.Tags,
428 }
429 return output
430 }
0 package agent
1
2 import (
3 "encoding/json"
4 "github.com/hashicorp/serf/serf"
5 "github.com/hashicorp/serf/testutil"
6 "io/ioutil"
7 "os"
8 "path/filepath"
9 "reflect"
10 "strings"
11 "testing"
12 )
13
14 func TestAgent_eventHandler(t *testing.T) {
15 a1 := testAgent(nil)
16 defer a1.Shutdown()
17 defer a1.Leave()
18
19 handler := new(MockEventHandler)
20 a1.RegisterEventHandler(handler)
21
22 if err := a1.Start(); err != nil {
23 t.Fatalf("err: %s", err)
24 }
25
26 testutil.Yield()
27
28 if len(handler.Events) != 1 {
29 t.Fatalf("bad: %#v", handler.Events)
30 }
31
32 if handler.Events[0].EventType() != serf.EventMemberJoin {
33 t.Fatalf("bad: %#v", handler.Events[0])
34 }
35 }
36
37 func TestAgentShutdown_multiple(t *testing.T) {
38 a := testAgent(nil)
39 if err := a.Start(); err != nil {
40 t.Fatalf("err: %s", err)
41 }
42
43 for i := 0; i < 5; i++ {
44 if err := a.Shutdown(); err != nil {
45 t.Fatalf("err: %s", err)
46 }
47 }
48 }
49
50 func TestAgentUserEvent(t *testing.T) {
51 a1 := testAgent(nil)
52 defer a1.Shutdown()
53 defer a1.Leave()
54
55 handler := new(MockEventHandler)
56 a1.RegisterEventHandler(handler)
57
58 if err := a1.Start(); err != nil {
59 t.Fatalf("err: %s", err)
60 }
61
62 testutil.Yield()
63
64 if err := a1.UserEvent("deploy", []byte("foo"), false); err != nil {
65 t.Fatalf("err: %s", err)
66 }
67
68 testutil.Yield()
69
70 handler.Lock()
71 defer handler.Unlock()
72
73 if len(handler.Events) == 0 {
74 t.Fatal("no events")
75 }
76
77 e, ok := handler.Events[len(handler.Events)-1].(serf.UserEvent)
78 if !ok {
79 t.Fatalf("bad: %#v", e)
80 }
81
82 if e.Name != "deploy" {
83 t.Fatalf("bad: %#v", e)
84 }
85
86 if string(e.Payload) != "foo" {
87 t.Fatalf("bad: %#v", e)
88 }
89 }
90
91 func TestAgentQuery_BadPrefix(t *testing.T) {
92 a1 := testAgent(nil)
93 defer a1.Shutdown()
94 defer a1.Leave()
95
96 if err := a1.Start(); err != nil {
97 t.Fatalf("err: %s", err)
98 }
99
100 testutil.Yield()
101
102 _, err := a1.Query("_serf_test", nil, nil)
103 if err == nil || !strings.Contains(err.Error(), "cannot contain") {
104 t.Fatalf("err: %s", err)
105 }
106 }
107
108 func TestAgentTagsFile(t *testing.T) {
109 tags := map[string]string{
110 "role": "webserver",
111 "datacenter": "us-east",
112 }
113
114 td, err := ioutil.TempDir("", "serf")
115 if err != nil {
116 t.Fatalf("err: %s", err)
117 }
118 defer os.RemoveAll(td)
119
120 agentConfig := DefaultConfig()
121 agentConfig.TagsFile = filepath.Join(td, "tags.json")
122
123 a1 := testAgentWithConfig(agentConfig, serf.DefaultConfig(), nil)
124
125 if err := a1.Start(); err != nil {
126 t.Fatalf("err: %s", err)
127 }
128 defer a1.Shutdown()
129 defer a1.Leave()
130
131 testutil.Yield()
132
133 err = a1.SetTags(tags)
134
135 if err != nil {
136 t.Fatalf("err: %s", err)
137 }
138
139 testutil.Yield()
140
141 a2 := testAgentWithConfig(agentConfig, serf.DefaultConfig(), nil)
142
143 if err := a2.Start(); err != nil {
144 t.Fatalf("err: %s", err)
145 }
146 defer a2.Shutdown()
147 defer a2.Leave()
148
149 testutil.Yield()
150
151 m := a2.Serf().LocalMember()
152
153 if !reflect.DeepEqual(m.Tags, tags) {
154 t.Fatalf("tags not restored: %#v", m.Tags)
155 }
156 }
157
158 func TestAgentTagsFile_BadOptions(t *testing.T) {
159 agentConfig := DefaultConfig()
160 agentConfig.TagsFile = "/some/path"
161 agentConfig.Tags = map[string]string{
162 "tag1": "val1",
163 }
164
165 _, err := Create(agentConfig, serf.DefaultConfig(), nil)
166 if err == nil || !strings.Contains(err.Error(), "not allowed") {
167 t.Fatalf("err: %s", err)
168 }
169 }
170
171 func TestAgent_MarshalTags(t *testing.T) {
172 tags := map[string]string{
173 "tag1": "val1",
174 "tag2": "val2",
175 }
176
177 tagPairs := MarshalTags(tags)
178
179 if !containsKey(tagPairs, "tag1=val1") {
180 t.Fatalf("bad: %v", tagPairs)
181 }
182 if !containsKey(tagPairs, "tag2=val2") {
183 t.Fatalf("bad: %v", tagPairs)
184 }
185 }
186
187 func TestAgent_UnmarshalTags(t *testing.T) {
188 tagPairs := []string{
189 "tag1=val1",
190 "tag2=val2",
191 }
192
193 tags, err := UnmarshalTags(tagPairs)
194
195 if err != nil {
196 t.Fatalf("err: %s", err)
197 }
198
199 if v, ok := tags["tag1"]; !ok || v != "val1" {
200 t.Fatalf("bad: %v", tags)
201 }
202 if v, ok := tags["tag2"]; !ok || v != "val2" {
203 t.Fatalf("bad: %v", tags)
204 }
205 }
206
207 func TestAgent_UnmarshalTagsError(t *testing.T) {
208 tagSets := [][]string{
209 []string{"="},
210 []string{"=x"},
211 []string{""},
212 []string{"x"},
213 }
214 for _, tagPairs := range tagSets {
215 if _, err := UnmarshalTags(tagPairs); err == nil {
216 t.Fatalf("Expected tag error: %s", tagPairs[0])
217 }
218 }
219 }
220
221 func TestAgentKeyringFile(t *testing.T) {
222 keys := []string{
223 "enjTwAFRe4IE71bOFhirzQ==",
224 "csT9mxI7aTf9ap3HLBbdmA==",
225 "noha2tVc0OyD/2LtCBoAOQ==",
226 }
227
228 td, err := ioutil.TempDir("", "serf")
229 if err != nil {
230 t.Fatalf("err: %s", err)
231 }
232 defer os.RemoveAll(td)
233
234 keyringFile := filepath.Join(td, "keyring.json")
235
236 serfConfig := serf.DefaultConfig()
237 agentConfig := DefaultConfig()
238 agentConfig.KeyringFile = keyringFile
239
240 encodedKeys, err := json.Marshal(keys)
241 if err != nil {
242 t.Fatalf("err: %s", err)
243 }
244
245 if err := ioutil.WriteFile(keyringFile, encodedKeys, 0600); err != nil {
246 t.Fatalf("err: %s", err)
247 }
248
249 a1 := testAgentWithConfig(agentConfig, serfConfig, nil)
250
251 if err := a1.Start(); err != nil {
252 t.Fatalf("err: %s", err)
253 }
254 defer a1.Shutdown()
255
256 testutil.Yield()
257
258 totalLoadedKeys := len(serfConfig.MemberlistConfig.Keyring.GetKeys())
259 if totalLoadedKeys != 3 {
260 t.Fatalf("Expected to load 3 keys but got %d", totalLoadedKeys)
261 }
262 }
263
264 func TestAgentKeyringFile_BadOptions(t *testing.T) {
265 agentConfig := DefaultConfig()
266 agentConfig.KeyringFile = "/some/path"
267 agentConfig.EncryptKey = "pL4owv4IE1x+ZXCyd5vLLg=="
268
269 _, err := Create(agentConfig, serf.DefaultConfig(), nil)
270 if err == nil || !strings.Contains(err.Error(), "not allowed") {
271 t.Fatalf("err: %s", err)
272 }
273 }
274
275 func TestAgentKeyringFile_NoKeys(t *testing.T) {
276 dir, err := ioutil.TempDir("", "serf")
277 if err != nil {
278 t.Fatalf("err: %s", err)
279 }
280 defer os.RemoveAll(dir)
281
282 keysFile := filepath.Join(dir, "keyring")
283 if err := ioutil.WriteFile(keysFile, []byte("[]"), 0600); err != nil {
284 t.Fatalf("err: %s", err)
285 }
286
287 agentConfig := DefaultConfig()
288 agentConfig.KeyringFile = keysFile
289
290 _, err = Create(agentConfig, serf.DefaultConfig(), nil)
291 if err == nil {
292 t.Fatalf("should have errored")
293 }
294 if !strings.Contains(err.Error(), "contains no keys") {
295 t.Fatalf("bad: %s", err)
296 }
297 }
0 package agent
1
2 import (
3 "flag"
4 "fmt"
5 "github.com/armon/go-metrics"
6 "github.com/hashicorp/go-syslog"
7 "github.com/hashicorp/logutils"
8 "github.com/hashicorp/memberlist"
9 "github.com/hashicorp/serf/serf"
10 "github.com/mitchellh/cli"
11 "io"
12 "log"
13 "net"
14 "os"
15 "os/signal"
16 "runtime"
17 "strings"
18 "syscall"
19 "time"
20 )
21
22 const (
23 // gracefulTimeout controls how long we wait before forcefully terminating
24 gracefulTimeout = 3 * time.Second
25
26 // minRetryInterval applies a lower bound to the join retry interval
27 minRetryInterval = time.Second
28 )
29
30 // Command is a Command implementation that runs a Serf agent.
31 // The command will not end unless a shutdown message is sent on the
32 // ShutdownCh. If two messages are sent on the ShutdownCh it will forcibly
33 // exit.
34 type Command struct {
35 Ui cli.Ui
36 ShutdownCh <-chan struct{}
37 args []string
38 scriptHandler *ScriptEventHandler
39 logFilter *logutils.LevelFilter
40 logger *log.Logger
41 }
42
43 // readConfig is responsible for setup of our configuration using
44 // the command line and any file configs
45 func (c *Command) readConfig() *Config {
46 var cmdConfig Config
47 var configFiles []string
48 var tags []string
49 var retryInterval string
50 cmdFlags := flag.NewFlagSet("agent", flag.ContinueOnError)
51 cmdFlags.Usage = func() { c.Ui.Output(c.Help()) }
52 cmdFlags.StringVar(&cmdConfig.BindAddr, "bind", "", "address to bind listeners to")
53 cmdFlags.StringVar(&cmdConfig.AdvertiseAddr, "advertise", "", "address to advertise to cluster")
54 cmdFlags.Var((*AppendSliceValue)(&configFiles), "config-file",
55 "json file to read config from")
56 cmdFlags.Var((*AppendSliceValue)(&configFiles), "config-dir",
57 "directory of json files to read")
58 cmdFlags.StringVar(&cmdConfig.EncryptKey, "encrypt", "", "encryption key")
59 cmdFlags.StringVar(&cmdConfig.KeyringFile, "keyring-file", "", "path to the keyring file")
60 cmdFlags.Var((*AppendSliceValue)(&cmdConfig.EventHandlers), "event-handler",
61 "command to execute when events occur")
62 cmdFlags.Var((*AppendSliceValue)(&cmdConfig.StartJoin), "join",
63 "address of agent to join on startup")
64 cmdFlags.BoolVar(&cmdConfig.ReplayOnJoin, "replay", false,
65 "replay events for startup join")
66 cmdFlags.StringVar(&cmdConfig.LogLevel, "log-level", "", "log level")
67 cmdFlags.StringVar(&cmdConfig.NodeName, "node", "", "node name")
68 cmdFlags.IntVar(&cmdConfig.Protocol, "protocol", -1, "protocol version")
69 cmdFlags.StringVar(&cmdConfig.Role, "role", "", "role name")
70 cmdFlags.StringVar(&cmdConfig.RPCAddr, "rpc-addr", "",
71 "address to bind RPC listener to")
72 cmdFlags.StringVar(&cmdConfig.Profile, "profile", "", "timing profile to use (lan, wan, local)")
73 cmdFlags.StringVar(&cmdConfig.SnapshotPath, "snapshot", "", "path to the snapshot file")
74 cmdFlags.Var((*AppendSliceValue)(&tags), "tag",
75 "tag pair, specified as key=value")
76 cmdFlags.StringVar(&cmdConfig.Discover, "discover", "", "mDNS discovery name")
77 cmdFlags.StringVar(&cmdConfig.Interface, "iface", "", "interface to bind to")
78 cmdFlags.StringVar(&cmdConfig.TagsFile, "tags-file", "", "tag persistence file")
79 cmdFlags.BoolVar(&cmdConfig.EnableSyslog, "syslog", false,
80 "enable logging to syslog facility")
81 cmdFlags.Var((*AppendSliceValue)(&cmdConfig.RetryJoin), "retry-join",
82 "address of agent to join on startup with retry")
83 cmdFlags.IntVar(&cmdConfig.RetryMaxAttempts, "retry-max", 0, "maximum retry join attempts")
84 cmdFlags.StringVar(&retryInterval, "retry-interval", "", "retry join interval")
85 cmdFlags.BoolVar(&cmdConfig.RejoinAfterLeave, "rejoin", false,
86 "enable re-joining after a previous leave")
87 if err := cmdFlags.Parse(c.args); err != nil {
88 return nil
89 }
90
91 // Parse any command line tag values
92 tagValues, err := UnmarshalTags(tags)
93 if err != nil {
94 c.Ui.Error(fmt.Sprintf("Error: %s", err))
95 return nil
96 }
97 cmdConfig.Tags = tagValues
98
99 // Decode the interval if given
100 if retryInterval != "" {
101 dur, err := time.ParseDuration(retryInterval)
102 if err != nil {
103 c.Ui.Error(fmt.Sprintf("Error: %s", err))
104 return nil
105 }
106 cmdConfig.RetryInterval = dur
107 }
108
109 config := DefaultConfig()
110 if len(configFiles) > 0 {
111 fileConfig, err := ReadConfigPaths(configFiles)
112 if err != nil {
113 c.Ui.Error(err.Error())
114 return nil
115 }
116
117 config = MergeConfig(config, fileConfig)
118 }
119
120 config = MergeConfig(config, &cmdConfig)
121
122 if config.NodeName == "" {
123 hostname, err := os.Hostname()
124 if err != nil {
125 c.Ui.Error(fmt.Sprintf("Error determining hostname: %s", err))
126 return nil
127 }
128 config.NodeName = hostname
129 }
130
131 eventScripts := config.EventScripts()
132 for _, script := range eventScripts {
133 if !script.Valid() {
134 c.Ui.Error(fmt.Sprintf("Invalid event script: %s", script.String()))
135 return nil
136 }
137 }
138
139 // Check for a valid interface
140 if _, err := config.NetworkInterface(); err != nil {
141 c.Ui.Error(fmt.Sprintf("Invalid network interface: %s", err))
142 return nil
143 }
144
145 // Backward compatibility hack for 'Role'
146 if config.Role != "" {
147 c.Ui.Output("Deprecation warning: 'Role' has been replaced with 'Tags'")
148 config.Tags["role"] = config.Role
149 }
150
151 // Check for sane retry interval
152 if config.RetryInterval < minRetryInterval {
153 c.Ui.Output(fmt.Sprintf("Warning: 'RetryInterval' is too low. Setting to %v", config.RetryInterval))
154 config.RetryInterval = minRetryInterval
155 }
156
157 // Check snapshot file is provided if we have RejoinAfterLeave
158 if config.RejoinAfterLeave && config.SnapshotPath == "" {
159 c.Ui.Output("Warning: 'RejoinAfterLeave' enabled without snapshot file")
160 }
161
162 return config
163 }
164
165 // setupAgent is used to create the agent we use
166 func (c *Command) setupAgent(config *Config, logOutput io.Writer) *Agent {
167 bindIP, bindPort, err := config.AddrParts(config.BindAddr)
168 if err != nil {
169 c.Ui.Error(fmt.Sprintf("Invalid bind address: %s", err))
170 return nil
171 }
172
173 // Check if we have an interface
174 if iface, _ := config.NetworkInterface(); iface != nil {
175 addrs, err := iface.Addrs()
176 if err != nil {
177 c.Ui.Error(fmt.Sprintf("Failed to get interface addresses: %s", err))
178 return nil
179 }
180 if len(addrs) == 0 {
181 c.Ui.Error(fmt.Sprintf("Interface '%s' has no addresses", config.Interface))
182 return nil
183 }
184
185 // If there is no bind IP, pick an address
186 if bindIP == "0.0.0.0" {
187 found := false
188 for _, a := range addrs {
189 var addrIP net.IP
190 if runtime.GOOS == "windows" {
191 // Waiting for https://github.com/golang/go/issues/5395 to use IPNet only
192 addr, ok := a.(*net.IPAddr)
193 if !ok {
194 continue
195 }
196 addrIP = addr.IP
197 } else {
198 addr, ok := a.(*net.IPNet)
199 if !ok {
200 continue
201 }
202 addrIP = addr.IP
203 }
204
205 // Skip self-assigned IPs
206 if addrIP.IsLinkLocalUnicast() {
207 continue
208 }
209
210 // Found an IP
211 found = true
212 bindIP = addrIP.String()
213 c.Ui.Output(fmt.Sprintf("Using interface '%s' address '%s'",
214 config.Interface, bindIP))
215
216 // Update the configuration
217 bindAddr := &net.TCPAddr{
218 IP: net.ParseIP(bindIP),
219 Port: bindPort,
220 }
221 config.BindAddr = bindAddr.String()
222 break
223 }
224 if !found {
225 c.Ui.Error(fmt.Sprintf("Failed to find usable address for interface '%s'", config.Interface))
226 return nil
227 }
228
229 } else {
230 // If there is a bind IP, ensure it is available
231 found := false
232 for _, a := range addrs {
233 addr, ok := a.(*net.IPNet)
234 if !ok {
235 continue
236 }
237 if addr.IP.String() == bindIP {
238 found = true
239 break
240 }
241 }
242 if !found {
243 c.Ui.Error(fmt.Sprintf("Interface '%s' has no '%s' address",
244 config.Interface, bindIP))
245 return nil
246 }
247 }
248 }
249
250 var advertiseIP string
251 var advertisePort int
252 if config.AdvertiseAddr != "" {
253 advertiseIP, advertisePort, err = config.AddrParts(config.AdvertiseAddr)
254 if err != nil {
255 c.Ui.Error(fmt.Sprintf("Invalid advertise address: %s", err))
256 return nil
257 }
258 }
259
260 encryptKey, err := config.EncryptBytes()
261 if err != nil {
262 c.Ui.Error(fmt.Sprintf("Invalid encryption key: %s", err))
263 return nil
264 }
265
266 serfConfig := serf.DefaultConfig()
267 switch config.Profile {
268 case "lan":
269 serfConfig.MemberlistConfig = memberlist.DefaultLANConfig()
270 case "wan":
271 serfConfig.MemberlistConfig = memberlist.DefaultWANConfig()
272 case "local":
273 serfConfig.MemberlistConfig = memberlist.DefaultLocalConfig()
274 default:
275 c.Ui.Error(fmt.Sprintf("Unknown profile: %s", config.Profile))
276 return nil
277 }
278
279 serfConfig.MemberlistConfig.BindAddr = bindIP
280 serfConfig.MemberlistConfig.BindPort = bindPort
281 serfConfig.MemberlistConfig.AdvertiseAddr = advertiseIP
282 serfConfig.MemberlistConfig.AdvertisePort = advertisePort
283 serfConfig.MemberlistConfig.SecretKey = encryptKey
284 serfConfig.NodeName = config.NodeName
285 serfConfig.Tags = config.Tags
286 serfConfig.SnapshotPath = config.SnapshotPath
287 serfConfig.ProtocolVersion = uint8(config.Protocol)
288 serfConfig.CoalescePeriod = 3 * time.Second
289 serfConfig.QuiescentPeriod = time.Second
290 serfConfig.UserCoalescePeriod = 3 * time.Second
291 serfConfig.UserQuiescentPeriod = time.Second
292 if config.ReconnectInterval != 0 {
293 serfConfig.ReconnectInterval = config.ReconnectInterval
294 }
295 if config.ReconnectTimeout != 0 {
296 serfConfig.ReconnectTimeout = config.ReconnectTimeout
297 }
298 if config.TombstoneTimeout != 0 {
299 serfConfig.TombstoneTimeout = config.TombstoneTimeout
300 }
301 serfConfig.EnableNameConflictResolution = !config.DisableNameResolution
302 if config.KeyringFile != "" {
303 serfConfig.KeyringFile = config.KeyringFile
304 }
305 serfConfig.RejoinAfterLeave = config.RejoinAfterLeave
306
307 // Start Serf
308 c.Ui.Output("Starting Serf agent...")
309 agent, err := Create(config, serfConfig, logOutput)
310 if err != nil {
311 c.Ui.Error(fmt.Sprintf("Failed to start the Serf agent: %v", err))
312 return nil
313 }
314 return agent
315 }
316
317 // setupLoggers is used to setup the logGate, logWriter, and our logOutput
318 func (c *Command) setupLoggers(config *Config) (*GatedWriter, *logWriter, io.Writer) {
319 // Setup logging. First create the gated log writer, which will
320 // store logs until we're ready to show them. Then create the level
321 // filter, filtering logs of the specified level.
322 logGate := &GatedWriter{
323 Writer: &cli.UiWriter{Ui: c.Ui},
324 }
325
326 c.logFilter = LevelFilter()
327 c.logFilter.MinLevel = logutils.LogLevel(strings.ToUpper(config.LogLevel))
328 c.logFilter.Writer = logGate
329 if !ValidateLevelFilter(c.logFilter.MinLevel, c.logFilter) {
330 c.Ui.Error(fmt.Sprintf(
331 "Invalid log level: %s. Valid log levels are: %v",
332 c.logFilter.MinLevel, c.logFilter.Levels))
333 return nil, nil, nil
334 }
335
336 // Check if syslog is enabled
337 var syslog io.Writer
338 if config.EnableSyslog {
339 l, err := gsyslog.NewLogger(gsyslog.LOG_NOTICE, config.SyslogFacility, "serf")
340 if err != nil {
341 c.Ui.Error(fmt.Sprintf("Syslog setup failed: %v", err))
342 return nil, nil, nil
343 }
344 syslog = &SyslogWrapper{l}
345 }
346
347 // Create a log writer, and wrap a logOutput around it
348 logWriter := NewLogWriter(512)
349 var logOutput io.Writer
350 if syslog != nil {
351 logOutput = io.MultiWriter(c.logFilter, logWriter, syslog)
352 } else {
353 logOutput = io.MultiWriter(c.logFilter, logWriter)
354 }
355
356 // Create a logger
357 c.logger = log.New(logOutput, "", log.LstdFlags)
358 return logGate, logWriter, logOutput
359 }
360
361 // startAgent is used to start the agent and IPC
362 func (c *Command) startAgent(config *Config, agent *Agent,
363 logWriter *logWriter, logOutput io.Writer) *AgentIPC {
364 // Add the script event handlers
365 c.scriptHandler = &ScriptEventHandler{
366 SelfFunc: func() serf.Member { return agent.Serf().LocalMember() },
367 Scripts: config.EventScripts(),
368 Logger: log.New(logOutput, "", log.LstdFlags),
369 }
370 agent.RegisterEventHandler(c.scriptHandler)
371
372 // Start the agent after the handler is registered
373 if err := agent.Start(); err != nil {
374 c.Ui.Error(fmt.Sprintf("Failed to start the Serf agent: %v", err))
375 return nil
376 }
377
378 // Parse the bind address information
379 bindIP, bindPort, err := config.AddrParts(config.BindAddr)
380 bindAddr := &net.TCPAddr{IP: net.ParseIP(bindIP), Port: bindPort}
381
382 // Start the discovery layer
383 if config.Discover != "" {
384 // Use the advertise addr and port
385 local := agent.Serf().Memberlist().LocalNode()
386
387 // Get the bind interface if any
388 iface, _ := config.NetworkInterface()
389
390 _, err := NewAgentMDNS(agent, logOutput, config.ReplayOnJoin,
391 config.NodeName, config.Discover, iface, local.Addr, int(local.Port))
392 if err != nil {
393 c.Ui.Error(fmt.Sprintf("Error starting mDNS listener: %s", err))
394 return nil
395
396 }
397 }
398
399 // Setup the RPC listener
400 rpcListener, err := net.Listen("tcp", config.RPCAddr)
401 if err != nil {
402 c.Ui.Error(fmt.Sprintf("Error starting RPC listener: %s", err))
403 return nil
404 }
405
406 // Start the IPC layer
407 c.Ui.Output("Starting Serf agent RPC...")
408 ipc := NewAgentIPC(agent, config.RPCAuthKey, rpcListener, logOutput, logWriter)
409
410 c.Ui.Output("Serf agent running!")
411 c.Ui.Info(fmt.Sprintf(" Node name: '%s'", config.NodeName))
412 c.Ui.Info(fmt.Sprintf(" Bind addr: '%s'", bindAddr.String()))
413
414 if config.AdvertiseAddr != "" {
415 advertiseIP, advertisePort, _ := config.AddrParts(config.AdvertiseAddr)
416 advertiseAddr := (&net.TCPAddr{IP: net.ParseIP(advertiseIP), Port: advertisePort}).String()
417 c.Ui.Info(fmt.Sprintf("Advertise addr: '%s'", advertiseAddr))
418 }
419
420 c.Ui.Info(fmt.Sprintf(" RPC addr: '%s'", config.RPCAddr))
421 c.Ui.Info(fmt.Sprintf(" Encrypted: %#v", agent.serf.EncryptionEnabled()))
422 c.Ui.Info(fmt.Sprintf(" Snapshot: %v", config.SnapshotPath != ""))
423 c.Ui.Info(fmt.Sprintf(" Profile: %s", config.Profile))
424
425 if config.Discover != "" {
426 c.Ui.Info(fmt.Sprintf(" mDNS cluster: %s", config.Discover))
427 }
428 return ipc
429 }
430
431 // startupJoin is invoked to handle any joins specified to take place at start time
432 func (c *Command) startupJoin(config *Config, agent *Agent) error {
433 if len(config.StartJoin) == 0 {
434 return nil
435 }
436
437 c.Ui.Output(fmt.Sprintf("Joining cluster...(replay: %v)", config.ReplayOnJoin))
438 n, err := agent.Join(config.StartJoin, config.ReplayOnJoin)
439 if err != nil {
440 return err
441 }
442
443 c.Ui.Info(fmt.Sprintf("Join completed. Synced with %d initial agents", n))
444 return nil
445 }
446
447 // retryJoin is invoked to handle joins with retries. This runs until at least a
448 // single successful join or RetryMaxAttempts is reached
449 func (c *Command) retryJoin(config *Config, agent *Agent, errCh chan struct{}) {
450 // Quit fast if there is no nodes to join
451 if len(config.RetryJoin) == 0 {
452 return
453 }
454
455 // Track the number of join attempts
456 attempt := 0
457 for {
458 // Try to perform the join
459 c.logger.Printf("[INFO] agent: Joining cluster...(replay: %v)", config.ReplayOnJoin)
460 n, err := agent.Join(config.RetryJoin, config.ReplayOnJoin)
461 if err == nil {
462 c.logger.Printf("[INFO] agent: Join completed. Synced with %d initial agents", n)
463 return
464 }
465
466 // Check if the maximum attempts has been exceeded
467 attempt++
468 if config.RetryMaxAttempts > 0 && attempt > config.RetryMaxAttempts {
469 c.logger.Printf("[ERR] agent: maximum retry join attempts made, exiting")
470 close(errCh)
471 return
472 }
473
474 // Log the failure and sleep
475 c.logger.Printf("[WARN] agent: Join failed: %v, retrying in %v", err, config.RetryInterval)
476 time.Sleep(config.RetryInterval)
477 }
478 }
479
480 func (c *Command) Run(args []string) int {
481 c.Ui = &cli.PrefixedUi{
482 OutputPrefix: "==> ",
483 InfoPrefix: " ",
484 ErrorPrefix: "==> ",
485 Ui: c.Ui,
486 }
487
488 // Parse our configs
489 c.args = args
490 config := c.readConfig()
491 if config == nil {
492 return 1
493 }
494
495 // Setup the log outputs
496 logGate, logWriter, logOutput := c.setupLoggers(config)
497 if logWriter == nil {
498 return 1
499 }
500
501 /*
502 Setup telemetry
503 Aggregate on 10 second intervals for 1 minute. Expose the
504 metrics over stderr when there is a SIGUSR1 received.
505 */
506 inm := metrics.NewInmemSink(10*time.Second, time.Minute)
507 metrics.DefaultInmemSignal(inm)
508 metricsConf := metrics.DefaultConfig("serf-agent")
509
510 if config.StatsiteAddr != "" {
511 sink, err := metrics.NewStatsiteSink(config.StatsiteAddr)
512 if err != nil {
513 c.Ui.Error(fmt.Sprintf("Failed to start statsite sink. Got: %s", err))
514 return 1
515 }
516 fanout := metrics.FanoutSink{inm, sink}
517 metrics.NewGlobal(metricsConf, fanout)
518 } else {
519 metricsConf.EnableHostname = false
520 metrics.NewGlobal(metricsConf, inm)
521 }
522
523 // Setup serf
524 agent := c.setupAgent(config, logOutput)
525 if agent == nil {
526 return 1
527 }
528 defer agent.Shutdown()
529
530 // Start the agent
531 ipc := c.startAgent(config, agent, logWriter, logOutput)
532 if ipc == nil {
533 return 1
534 }
535 defer ipc.Shutdown()
536
537 // Join startup nodes if specified
538 if err := c.startupJoin(config, agent); err != nil {
539 c.Ui.Error(err.Error())
540 return 1
541 }
542
543 // Enable log streaming
544 c.Ui.Info("")
545 c.Ui.Output("Log data will now stream in as it occurs:\n")
546 logGate.Flush()
547
548 // Start the retry joins
549 retryJoinCh := make(chan struct{})
550 go c.retryJoin(config, agent, retryJoinCh)
551
552 // Wait for exit
553 return c.handleSignals(config, agent, retryJoinCh)
554 }
555
556 // handleSignals blocks until we get an exit-causing signal
557 func (c *Command) handleSignals(config *Config, agent *Agent, retryJoin chan struct{}) int {
558 signalCh := make(chan os.Signal, 4)
559 signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
560
561 // Wait for a signal
562 WAIT:
563 var sig os.Signal
564 select {
565 case s := <-signalCh:
566 sig = s
567 case <-c.ShutdownCh:
568 sig = os.Interrupt
569 case <-retryJoin:
570 // Retry join failed!
571 return 1
572 case <-agent.ShutdownCh():
573 // Agent is already shutdown!
574 return 0
575 }
576 c.Ui.Output(fmt.Sprintf("Caught signal: %v", sig))
577
578 // Check if this is a SIGHUP
579 if sig == syscall.SIGHUP {
580 config = c.handleReload(config, agent)
581 goto WAIT
582 }
583
584 // Check if we should do a graceful leave
585 graceful := false
586 if sig == os.Interrupt && !config.SkipLeaveOnInt {
587 graceful = true
588 } else if sig == syscall.SIGTERM && config.LeaveOnTerm {
589 graceful = true
590 }
591
592 // Bail fast if not doing a graceful leave
593 if !graceful {
594 return 1
595 }
596
597 // Attempt a graceful leave
598 gracefulCh := make(chan struct{})
599 c.Ui.Output("Gracefully shutting down agent...")
600 go func() {
601 if err := agent.Leave(); err != nil {
602 c.Ui.Error(fmt.Sprintf("Error: %s", err))
603 return
604 }
605 close(gracefulCh)
606 }()
607
608 // Wait for leave or another signal
609 select {
610 case <-signalCh:
611 return 1
612 case <-time.After(gracefulTimeout):
613 return 1
614 case <-gracefulCh:
615 return 0
616 }
617 }
618
619 // handleReload is invoked when we should reload our configs, e.g. SIGHUP
620 func (c *Command) handleReload(config *Config, agent *Agent) *Config {
621 c.Ui.Output("Reloading configuration...")
622 newConf := c.readConfig()
623 if newConf == nil {
624 c.Ui.Error(fmt.Sprintf("Failed to reload configs"))
625 return config
626 }
627
628 // Change the log level
629 minLevel := logutils.LogLevel(strings.ToUpper(newConf.LogLevel))
630 if ValidateLevelFilter(minLevel, c.logFilter) {
631 c.logFilter.SetMinLevel(minLevel)
632 } else {
633 c.Ui.Error(fmt.Sprintf(
634 "Invalid log level: %s. Valid log levels are: %v",
635 minLevel, c.logFilter.Levels))
636
637 // Keep the current log level
638 newConf.LogLevel = config.LogLevel
639 }
640
641 // Change the event handlers
642 c.scriptHandler.UpdateScripts(newConf.EventScripts())
643
644 // Update the tags in serf
645 if err := agent.SetTags(newConf.Tags); err != nil {
646 c.Ui.Error(fmt.Sprintf("Failed to update tags: %v", err))
647 return newConf
648 }
649
650 return newConf
651 }
652
653 func (c *Command) Synopsis() string {
654 return "Runs a Serf agent"
655 }
656
657 func (c *Command) Help() string {
658 helpText := `
659 Usage: serf agent [options]
660
661 Starts the Serf agent and runs until an interrupt is received. The
662 agent represents a single node in a cluster.
663
664 Options:
665
666 -bind=0.0.0.0 Address to bind network listeners to
667 -iface Network interface to bind to. Can be used instead of
668 -bind if the interface is known but not the address.
669 If both are provided, then Serf verifies that the
670 interface has the bind address that is provided. This
671 flag also sets the multicast device used for -discover.
672 -advertise=0.0.0.0 Address to advertise to the other cluster members
673 -config-file=foo Path to a JSON file to read configuration from.
674 This can be specified multiple times.
675 -config-dir=foo Path to a directory to read configuration files
676 from. This will read every file ending in ".json"
677 as configuration in this directory in alphabetical
678 order.
679 -discover=cluster Discover is set to enable mDNS discovery of peer. On
680 networks that support multicast, this can be used to have
681 peers join each other without an explicit join.
682 -encrypt=foo Key for encrypting network traffic within Serf.
683 Must be a base64-encoded 16-byte key.
684 -keyring-file The keyring file is used to store encryption keys used
685 by Serf. As encryption keys are changed, the content of
686 this file is updated so that the same keys may be used
687 during later agent starts.
688 -event-handler=foo Script to execute when events occur. This can
689 be specified multiple times. See the event scripts
690 section below for more info.
691 -join=addr An initial agent to join with. This flag can be
692 specified multiple times.
693 -log-level=info Log level of the agent.
694 -node=hostname Name of this node. Must be unique in the cluster
695 -profile=[lan|wan|local] Profile is used to control the timing profiles used in Serf.
696 The default if not provided is lan.
697 -protocol=n Serf protocol version to use. This defaults to
698 the latest version, but can be set back for upgrades.
699 -rejoin Ignores a previous leave and attempts to rejoin the cluster.
700 Only works if provided along with a snapshot file.
701 -retry-join=addr An agent to join with. This flag be specified multiple times.
702 Does not exit on failure like -join, used to retry until success.
703 -retry-interval=30s Sets the interval on which a node will attempt to retry joining
704 nodes provided by -retry-join. Defaults to 30s.
705 -retry-max=0 Limits the number of retry events. Defaults to 0 for unlimited.
706 -role=foo The role of this node, if any. This can be used
707 by event scripts to differentiate different types
708 of nodes that may be part of the same cluster.
709 '-role' is deprecated in favor of '-tag role=foo'.
710 -rpc-addr=127.0.0.1:7373 Address to bind the RPC listener.
711 -snapshot=path/to/file The snapshot file is used to store alive nodes and
712 event information so that Serf can rejoin a cluster
713 and avoid event replay on restart.
714 -tag key=value Tag can be specified multiple times to attach multiple
715 key/value tag pairs to the given node.
716 -tags-file=/path/to/file The tags file is used to persist tag data. As an agent's
717 tags are changed, the tags file will be updated. Tags
718 can be reloaded during later agent starts. This option
719 is incompatible with the '-tag' option and requires there
720 be no tags in the agent configuration file, if given.
721 -syslog When provided, logs will also be sent to syslog.
722
723 Event handlers:
724
725 For more information on what event handlers are, please read the
726 Serf documentation. This section will document how to configure them
727 on the command-line. There are three methods of specifying an event
728 handler:
729
730 - The value can be a plain script, such as "event.sh". In this case,
731 Serf will send all events to this script, and you'll be responsible
732 for differentiating between them based on the SERF_EVENT.
733
734 - The value can be in the format of "TYPE=SCRIPT", such as
735 "member-join=join.sh". With this format, Serf will only send events
736 of that type to that script.
737
738 - The value can be in the format of "user:EVENT=SCRIPT", such as
739 "user:deploy=deploy.sh". This means that Serf will only invoke this
740 script in the case of user events named "deploy".
741 `
742 return strings.TrimSpace(helpText)
743 }
0 package agent
1
2 import (
3 "bytes"
4 "github.com/hashicorp/serf/client"
5 "github.com/hashicorp/serf/testutil"
6 "github.com/mitchellh/cli"
7 "log"
8 "os"
9 "testing"
10 "time"
11 )
12
13 func TestCommand_implements(t *testing.T) {
14 var _ cli.Command = new(Command)
15 }
16
17 func TestCommandRun(t *testing.T) {
18 shutdownCh := make(chan struct{})
19 defer close(shutdownCh)
20
21 ui := new(cli.MockUi)
22 c := &Command{
23 ShutdownCh: shutdownCh,
24 Ui: ui,
25 }
26
27 args := []string{
28 "-bind", testutil.GetBindAddr().String(),
29 "-rpc-addr", getRPCAddr(),
30 }
31
32 resultCh := make(chan int)
33 go func() {
34 resultCh <- c.Run(args)
35 }()
36
37 testutil.Yield()
38
39 // Verify it runs "forever"
40 select {
41 case <-resultCh:
42 t.Fatalf("ended too soon, err: %s", ui.ErrorWriter.String())
43 case <-time.After(50 * time.Millisecond):
44 }
45
46 // Send a shutdown request
47 shutdownCh <- struct{}{}
48
49 select {
50 case code := <-resultCh:
51 if code != 0 {
52 t.Fatalf("bad code: %d", code)
53 }
54 case <-time.After(50 * time.Millisecond):
55 t.Fatalf("timeout")
56 }
57 }
58
59 func TestCommandRun_rpc(t *testing.T) {
60 doneCh := make(chan struct{})
61 shutdownCh := make(chan struct{})
62 defer func() {
63 close(shutdownCh)
64 <-doneCh
65 }()
66
67 c := &Command{
68 ShutdownCh: shutdownCh,
69 Ui: new(cli.MockUi),
70 }
71
72 rpcAddr := getRPCAddr()
73 args := []string{
74 "-bind", testutil.GetBindAddr().String(),
75 "-rpc-addr", rpcAddr,
76 }
77
78 go func() {
79 code := c.Run(args)
80 if code != 0 {
81 log.Printf("bad: %d", code)
82 }
83
84 close(doneCh)
85 }()
86
87 testutil.Yield()
88
89 client, err := client.NewRPCClient(rpcAddr)
90 if err != nil {
91 t.Fatalf("err: %s", err)
92 }
93 defer client.Close()
94
95 members, err := client.Members()
96 if err != nil {
97 t.Fatalf("err: %s", err)
98 }
99
100 if len(members) != 1 {
101 t.Fatalf("bad: %#v", members)
102 }
103 }
104
105 func TestCommandRun_join(t *testing.T) {
106 a1 := testAgent(nil)
107 if err := a1.Start(); err != nil {
108 t.Fatalf("err: %s", err)
109 }
110 defer a1.Shutdown()
111
112 doneCh := make(chan struct{})
113 shutdownCh := make(chan struct{})
114 defer func() {
115 close(shutdownCh)
116 <-doneCh
117 }()
118
119 c := &Command{
120 ShutdownCh: shutdownCh,
121 Ui: new(cli.MockUi),
122 }
123
124 args := []string{
125 "-bind", testutil.GetBindAddr().String(),
126 "-join", a1.conf.MemberlistConfig.BindAddr,
127 "-replay",
128 }
129
130 go func() {
131 code := c.Run(args)
132 if code != 0 {
133 log.Printf("bad: %d", code)
134 }
135
136 close(doneCh)
137 }()
138
139 testutil.Yield()
140
141 if len(a1.Serf().Members()) != 2 {
142 t.Fatalf("bad: %#v", a1.Serf().Members())
143 }
144 }
145
146 func TestCommandRun_joinFail(t *testing.T) {
147 shutdownCh := make(chan struct{})
148 defer close(shutdownCh)
149
150 c := &Command{
151 ShutdownCh: shutdownCh,
152 Ui: new(cli.MockUi),
153 }
154
155 args := []string{
156 "-bind", testutil.GetBindAddr().String(),
157 "-join", testutil.GetBindAddr().String(),
158 }
159
160 code := c.Run(args)
161 if code == 0 {
162 t.Fatal("should fail")
163 }
164 }
165
166 func TestCommandRun_advertiseAddr(t *testing.T) {
167 doneCh := make(chan struct{})
168 shutdownCh := make(chan struct{})
169 defer func() {
170 close(shutdownCh)
171 <-doneCh
172 }()
173
174 c := &Command{
175 ShutdownCh: shutdownCh,
176 Ui: new(cli.MockUi),
177 }
178
179 rpcAddr := getRPCAddr()
180 args := []string{
181 "-bind", testutil.GetBindAddr().String(),
182 "-rpc-addr", rpcAddr,
183 "-advertise", "127.0.0.10:12345",
184 }
185
186 go func() {
187 code := c.Run(args)
188 if code != 0 {
189 log.Printf("bad: %d", code)
190 }
191
192 close(doneCh)
193 }()
194
195 testutil.Yield()
196
197 client, err := client.NewRPCClient(rpcAddr)
198 if err != nil {
199 t.Fatalf("err: %s", err)
200 }
201 defer client.Close()
202
203 members, err := client.Members()
204 if err != nil {
205 t.Fatalf("err: %s", err)
206 }
207
208 if len(members) != 1 {
209 t.Fatalf("bad: %#v", members)
210 }
211
212 // Check the addr and port is as advertised!
213 m := members[0]
214 if bytes.Compare(m.Addr, []byte{127, 0, 0, 10}) != 0 {
215 t.Fatalf("bad: %#v", m)
216 }
217 if m.Port != 12345 {
218 t.Fatalf("bad: %#v", m)
219 }
220 }
221
222 func TestCommandRun_mDNS(t *testing.T) {
223 // mDNS does not work in travis
224 if os.Getenv("TRAVIS") != "" {
225 t.SkipNow()
226 }
227
228 // Start an agent
229 doneCh := make(chan struct{})
230 shutdownCh := make(chan struct{})
231 defer func() {
232 close(shutdownCh)
233 <-doneCh
234 }()
235
236 c := &Command{
237 ShutdownCh: shutdownCh,
238 Ui: new(cli.MockUi),
239 }
240
241 args := []string{
242 "-node", "foo",
243 "-bind", testutil.GetBindAddr().String(),
244 "-discover", "test",
245 "-rpc-addr", getRPCAddr(),
246 }
247
248 go func() {
249 code := c.Run(args)
250 if code != 0 {
251 log.Printf("bad: %d", code)
252 }
253 close(doneCh)
254 }()
255
256 // Start a second agent
257 doneCh2 := make(chan struct{})
258 shutdownCh2 := make(chan struct{})
259 defer func() {
260 close(shutdownCh2)
261 <-doneCh2
262 }()
263
264 c2 := &Command{
265 ShutdownCh: shutdownCh2,
266 Ui: new(cli.MockUi),
267 }
268
269 addr2 := getRPCAddr()
270 args2 := []string{
271 "-node", "bar",
272 "-bind", testutil.GetBindAddr().String(),
273 "-discover", "test",
274 "-rpc-addr", addr2,
275 }
276
277 go func() {
278 code := c2.Run(args2)
279 if code != 0 {
280 log.Printf("bad: %d", code)
281 }
282 close(doneCh2)
283 }()
284
285 time.Sleep(150 * time.Millisecond)
286
287 client, err := client.NewRPCClient(addr2)
288 if err != nil {
289 t.Fatalf("err: %s", err)
290 }
291 defer client.Close()
292
293 members, err := client.Members()
294 if err != nil {
295 t.Fatalf("err: %s", err)
296 }
297
298 if len(members) != 2 {
299 t.Fatalf("bad: %#v", members)
300 }
301 }
302
303 func TestCommandRun_retry_join(t *testing.T) {
304 a1 := testAgent(nil)
305 if err := a1.Start(); err != nil {
306 t.Fatalf("err: %s", err)
307 }
308 defer a1.Shutdown()
309
310 doneCh := make(chan struct{})
311 shutdownCh := make(chan struct{})
312 defer func() {
313 close(shutdownCh)
314 <-doneCh
315 }()
316
317 c := &Command{
318 ShutdownCh: shutdownCh,
319 Ui: new(cli.MockUi),
320 }
321
322 args := []string{
323 "-bind", testutil.GetBindAddr().String(),
324 "-retry-join", a1.conf.MemberlistConfig.BindAddr,
325 "-replay",
326 }
327
328 go func() {
329 code := c.Run(args)
330 if code != 0 {
331 log.Printf("bad: %d", code)
332 }
333
334 close(doneCh)
335 }()
336
337 testutil.Yield()
338
339 if len(a1.Serf().Members()) != 2 {
340 t.Fatalf("bad: %#v", a1.Serf().Members())
341 }
342 }
343
344 func TestCommandRun_retry_joinFail(t *testing.T) {
345 shutdownCh := make(chan struct{})
346 defer close(shutdownCh)
347
348 c := &Command{
349 ShutdownCh: shutdownCh,
350 Ui: new(cli.MockUi),
351 }
352
353 args := []string{
354 "-bind", testutil.GetBindAddr().String(),
355 "-retry-join", testutil.GetBindAddr().String(),
356 "-retry-interval", "1s",
357 "-retry-max", "1",
358 }
359
360 code := c.Run(args)
361 if code == 0 {
362 t.Fatal("should fail")
363 }
364 }
0 package agent
1
2 import (
3 "encoding/base64"
4 "encoding/json"
5 "fmt"
6 "github.com/hashicorp/serf/serf"
7 "github.com/mitchellh/mapstructure"
8 "io"
9 "net"
10 "os"
11 "path/filepath"
12 "sort"
13 "strings"
14 "time"
15 )
16
17 // This is the default port that we use for Serf communication
18 const DefaultBindPort int = 7946
19
20 // DefaultConfig contains the defaults for configurations.
21 func DefaultConfig() *Config {
22 return &Config{
23 Tags: make(map[string]string),
24 BindAddr: "0.0.0.0",
25 AdvertiseAddr: "",
26 LogLevel: "INFO",
27 RPCAddr: "127.0.0.1:7373",
28 Protocol: serf.ProtocolVersionMax,
29 ReplayOnJoin: false,
30 Profile: "lan",
31 RetryInterval: 30 * time.Second,
32 SyslogFacility: "LOCAL0",
33 }
34 }
35
36 type dirEnts []os.FileInfo
37
38 // Config is the configuration that can be set for an Agent. Some of these
39 // configurations are exposed as command-line flags to `serf agent`, whereas
40 // many of the more advanced configurations can only be set by creating
41 // a configuration file.
42 type Config struct {
43 // All the configurations in this section are identical to their
44 // Serf counterparts. See the documentation for Serf.Config for
45 // more info.
46 NodeName string `mapstructure:"node_name"`
47 Role string `mapstructure:"role"`
48
49 // Tags are used to attach key/value metadata to a node. They have
50 // replaced 'Role' as a more flexible meta data mechanism. For compatibility,
51 // the 'role' key is special, and is used for backwards compatibility.
52 Tags map[string]string `mapstructure:"tags"`
53
54 // TagsFile is the path to a file where Serf can store its tags. Tag
55 // persistence is desirable since tags may be set or deleted while the
56 // agent is running. Tags can be reloaded from this file on later starts.
57 TagsFile string `mapstructure:"tags_file"`
58
59 // BindAddr is the address that the Serf agent's communication ports
60 // will bind to. Serf will use this address to bind to for both TCP
61 // and UDP connections. If no port is present in the address, the default
62 // port will be used.
63 BindAddr string `mapstructure:"bind"`
64
65 // AdvertiseAddr is the address that the Serf agent will advertise to
66 // other members of the cluster. Can be used for basic NAT traversal
67 // where both the internal ip:port and external ip:port are known.
68 AdvertiseAddr string `mapstructure:"advertise"`
69
70 // EncryptKey is the secret key to use for encrypting communication
71 // traffic for Serf. The secret key must be exactly 16-bytes, base64
72 // encoded. The easiest way to do this on Unix machines is this command:
73 // "head -c16 /dev/urandom | base64". If this is not specified, the
74 // traffic will not be encrypted.
75 EncryptKey string `mapstructure:"encrypt_key"`
76
77 // KeyringFile is the path to a file containing a serialized keyring.
78 // The keyring is used to facilitate encryption.
79 KeyringFile string `mapstructure:"keyring_file"`
80
81 // LogLevel is the level of the logs to output.
82 // This can be updated during a reload.
83 LogLevel string `mapstructure:"log_level"`
84
85 // RPCAddr is the address and port to listen on for the agent's RPC
86 // interface.
87 RPCAddr string `mapstructure:"rpc_addr"`
88
89 // RPCAuthKey is a key that can be set to optionally require that
90 // RPC's provide an authentication key. This is meant to be
91 // a very simple authentication control
92 RPCAuthKey string `mapstructure:"rpc_auth"`
93
94 // Protocol is the Serf protocol version to use.
95 Protocol int `mapstructure:"protocol"`
96
97 // ReplayOnJoin tells Serf to replay past user events
98 // when joining based on a `StartJoin`.
99 ReplayOnJoin bool `mapstructure:"replay_on_join"`
100
101 // StartJoin is a list of addresses to attempt to join when the
102 // agent starts. If Serf is unable to communicate with any of these
103 // addresses, then the agent will error and exit.
104 StartJoin []string `mapstructure:"start_join"`
105
106 // EventHandlers is a list of event handlers that will be invoked.
107 // These can be updated during a reload.
108 EventHandlers []string `mapstructure:"event_handlers"`
109
110 // Profile is used to select a timing profile for Serf. The supported choices
111 // are "wan", "lan", and "local". The default is "lan"
112 Profile string `mapstructure:"profile"`
113
114 // SnapshotPath is used to allow Serf to snapshot important transactional
115 // state to make a more graceful recovery possible. This enables auto
116 // re-joining a cluster on failure and avoids old message replay.
117 SnapshotPath string `mapstructure:"snapshot_path"`
118
119 // LeaveOnTerm controls if Serf does a graceful leave when receiving
120 // the TERM signal. Defaults false. This can be changed on reload.
121 LeaveOnTerm bool `mapstructure:"leave_on_terminate"`
122
123 // SkipLeaveOnInt controls if Serf skips a graceful leave when receiving
124 // the INT signal. Defaults false. This can be changed on reload.
125 SkipLeaveOnInt bool `mapstructure:"skip_leave_on_interrupt"`
126
127 // Discover is used to setup an mDNS Discovery name. When this is set, the
128 // agent will setup an mDNS responder and periodically run an mDNS query
129 // to look for peers. For peers on a network that supports multicast, this
130 // allows Serf agents to join each other with zero configuration.
131 Discover string `mapstructure:"discover"`
132
133 // Interface is used to provide a binding interface to use. It can be
134 // used instead of providing a bind address, as Serf will discover the
135 // address of the provided interface. It is also used to set the multicast
136 // device used with `-discover`.
137 Interface string `mapstructure:"interface"`
138
139 // ReconnectIntervalRaw is the string reconnect interval time. This interval
140 // controls how often we attempt to connect to a failed node.
141 ReconnectIntervalRaw string `mapstructure:"reconnect_interval"`
142 ReconnectInterval time.Duration `mapstructure:"-"`
143
144 // ReconnectTimeoutRaw is the string reconnect timeout. This timeout controls
145 // for how long we attempt to connect to a failed node before removing
146 // it from the cluster.
147 ReconnectTimeoutRaw string `mapstructure:"reconnect_timeout"`
148 ReconnectTimeout time.Duration `mapstructure:"-"`
149
150 // TombstoneTimeoutRaw is the string tombstone timeout. This timeout controls
151 // for how long we remember a left node before removing it from the cluster.
152 TombstoneTimeoutRaw string `mapstructure:"tombstone_timeout"`
153 TombstoneTimeout time.Duration `mapstructure:"-"`
154
155 // By default Serf will attempt to resolve name conflicts. This is done by
156 // determining which node the majority believe to be the proper node, and
157 // by having the minority node shutdown. If you want to disable this behavior,
158 // then this flag can be set to true.
159 DisableNameResolution bool `mapstructure:"disable_name_resolution"`
160
161 // EnableSyslog is used to also tee all the logs over to syslog. Only supported
162 // on linux and OSX. Other platforms will generate an error.
163 EnableSyslog bool `mapstructure:"enable_syslog"`
164
165 // SyslogFacility is used to control which syslog facility messages are
166 // sent to. Defaults to LOCAL0.
167 SyslogFacility string `mapstructure:"syslog_facility"`
168
169 // RetryJoin is a list of addresses to attempt to join when the
170 // agent starts. Serf will continue to retry the join until it
171 // succeeds or RetryMaxAttempts is reached.
172 RetryJoin []string `mapstructure:"retry_join"`
173
174 // RetryMaxAttempts is used to limit the maximum attempts made
175 // by RetryJoin to reach other nodes. If this is 0, then no limit
176 // is imposed, and Serf will continue to try forever. Defaults to 0.
177 RetryMaxAttempts int `mapstructure:"retry_max_attempts"`
178
179 // RetryIntervalRaw is the string retry interval. This interval
180 // controls how often we retry the join for RetryJoin. This defaults
181 // to 30 seconds.
182 RetryIntervalRaw string `mapstructure:"retry_interval"`
183 RetryInterval time.Duration `mapstructure:"-"`
184
185 // RejoinAfterLeave controls our interaction with the snapshot file.
186 // When set to false (default), a leave causes a Serf to not rejoin
187 // the cluster until an explicit join is received. If this is set to
188 // true, we ignore the leave, and rejoin the cluster on start. This
189 // only has an affect if the snapshot file is enabled.
190 RejoinAfterLeave bool `mapstructure:"rejoin_after_leave"`
191
192 // StatsiteAddr is the address of a statsite instance. If provided,
193 // metrics will be streamed to that instance.
194 StatsiteAddr string `mapstructure:"statsite_addr"`
195 }
196
197 // BindAddrParts returns the parts of the BindAddr that should be
198 // used to configure Serf.
199 func (c *Config) AddrParts(address string) (string, int, error) {
200 checkAddr := address
201
202 START:
203 _, _, err := net.SplitHostPort(checkAddr)
204 if ae, ok := err.(*net.AddrError); ok && ae.Err == "missing port in address" {
205 checkAddr = fmt.Sprintf("%s:%d", checkAddr, DefaultBindPort)
206 goto START
207 }
208 if err != nil {
209 return "", 0, err
210 }
211
212 // Get the address
213 addr, err := net.ResolveTCPAddr("tcp", checkAddr)
214 if err != nil {
215 return "", 0, err
216 }
217
218 return addr.IP.String(), addr.Port, nil
219 }
220
221 // EncryptBytes returns the encryption key configured.
222 func (c *Config) EncryptBytes() ([]byte, error) {
223 return base64.StdEncoding.DecodeString(c.EncryptKey)
224 }
225
226 // EventScripts returns the list of EventScripts associated with this
227 // configuration and specified by the "event_handlers" configuration.
228 func (c *Config) EventScripts() []EventScript {
229 result := make([]EventScript, 0, len(c.EventHandlers))
230 for _, v := range c.EventHandlers {
231 part := ParseEventScript(v)
232 result = append(result, part...)
233 }
234 return result
235 }
236
237 // Networkinterface is used to get the associated network
238 // interface from the configured value
239 func (c *Config) NetworkInterface() (*net.Interface, error) {
240 if c.Interface == "" {
241 return nil, nil
242 }
243 return net.InterfaceByName(c.Interface)
244 }
245
246 // DecodeConfig reads the configuration from the given reader in JSON
247 // format and decodes it into a proper Config structure.
248 func DecodeConfig(r io.Reader) (*Config, error) {
249 var raw interface{}
250 dec := json.NewDecoder(r)
251 if err := dec.Decode(&raw); err != nil {
252 return nil, err
253 }
254
255 // Decode
256 var md mapstructure.Metadata
257 var result Config
258 msdec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
259 Metadata: &md,
260 Result: &result,
261 ErrorUnused: true,
262 })
263 if err != nil {
264 return nil, err
265 }
266
267 if err := msdec.Decode(raw); err != nil {
268 return nil, err
269 }
270
271 // Decode the time values
272 if result.ReconnectIntervalRaw != "" {
273 dur, err := time.ParseDuration(result.ReconnectIntervalRaw)
274 if err != nil {
275 return nil, err
276 }
277 result.ReconnectInterval = dur
278 }
279
280 if result.ReconnectTimeoutRaw != "" {
281 dur, err := time.ParseDuration(result.ReconnectTimeoutRaw)
282 if err != nil {
283 return nil, err
284 }
285 result.ReconnectTimeout = dur
286 }
287
288 if result.TombstoneTimeoutRaw != "" {
289 dur, err := time.ParseDuration(result.TombstoneTimeoutRaw)
290 if err != nil {
291 return nil, err
292 }
293 result.TombstoneTimeout = dur
294 }
295
296 if result.RetryIntervalRaw != "" {
297 dur, err := time.ParseDuration(result.RetryIntervalRaw)
298 if err != nil {
299 return nil, err
300 }
301 result.RetryInterval = dur
302 }
303
304 return &result, nil
305 }
306
307 // containsKey is used to check if a slice of string keys contains
308 // another key
309 func containsKey(keys []string, key string) bool {
310 for _, k := range keys {
311 if k == key {
312 return true
313 }
314 }
315 return false
316 }
317
318 // MergeConfig merges two configurations together to make a single new
319 // configuration.
320 func MergeConfig(a, b *Config) *Config {
321 var result Config = *a
322
323 // Copy the strings if they're set
324 if b.NodeName != "" {
325 result.NodeName = b.NodeName
326 }
327 if b.Role != "" {
328 result.Role = b.Role
329 }
330 if b.Tags != nil {
331 if result.Tags == nil {
332 result.Tags = make(map[string]string)
333 }
334 for name, value := range b.Tags {
335 result.Tags[name] = value
336 }
337 }
338 if b.BindAddr != "" {
339 result.BindAddr = b.BindAddr
340 }
341 if b.AdvertiseAddr != "" {
342 result.AdvertiseAddr = b.AdvertiseAddr
343 }
344 if b.EncryptKey != "" {
345 result.EncryptKey = b.EncryptKey
346 }
347 if b.LogLevel != "" {
348 result.LogLevel = b.LogLevel
349 }
350 if b.Protocol > 0 {
351 result.Protocol = b.Protocol
352 }
353 if b.RPCAddr != "" {
354 result.RPCAddr = b.RPCAddr
355 }
356 if b.RPCAuthKey != "" {
357 result.RPCAuthKey = b.RPCAuthKey
358 }
359 if b.ReplayOnJoin != false {
360 result.ReplayOnJoin = b.ReplayOnJoin
361 }
362 if b.Profile != "" {
363 result.Profile = b.Profile
364 }
365 if b.SnapshotPath != "" {
366 result.SnapshotPath = b.SnapshotPath
367 }
368 if b.LeaveOnTerm == true {
369 result.LeaveOnTerm = true
370 }
371 if b.SkipLeaveOnInt == true {
372 result.SkipLeaveOnInt = true
373 }
374 if b.Discover != "" {
375 result.Discover = b.Discover
376 }
377 if b.Interface != "" {
378 result.Interface = b.Interface
379 }
380 if b.ReconnectInterval != 0 {
381 result.ReconnectInterval = b.ReconnectInterval
382 }
383 if b.ReconnectTimeout != 0 {
384 result.ReconnectTimeout = b.ReconnectTimeout
385 }
386 if b.TombstoneTimeout != 0 {
387 result.TombstoneTimeout = b.TombstoneTimeout
388 }
389 if b.DisableNameResolution {
390 result.DisableNameResolution = true
391 }
392 if b.TagsFile != "" {
393 result.TagsFile = b.TagsFile
394 }
395 if b.KeyringFile != "" {
396 result.KeyringFile = b.KeyringFile
397 }
398 if b.EnableSyslog {
399 result.EnableSyslog = true
400 }
401 if b.RetryMaxAttempts != 0 {
402 result.RetryMaxAttempts = b.RetryMaxAttempts
403 }
404 if b.RetryInterval != 0 {
405 result.RetryInterval = b.RetryInterval
406 }
407 if b.RejoinAfterLeave {
408 result.RejoinAfterLeave = true
409 }
410 if b.SyslogFacility != "" {
411 result.SyslogFacility = b.SyslogFacility
412 }
413 if b.StatsiteAddr != "" {
414 result.StatsiteAddr = b.StatsiteAddr
415 }
416
417 // Copy the event handlers
418 result.EventHandlers = make([]string, 0, len(a.EventHandlers)+len(b.EventHandlers))
419 result.EventHandlers = append(result.EventHandlers, a.EventHandlers...)
420 result.EventHandlers = append(result.EventHandlers, b.EventHandlers...)
421
422 // Copy the start join addresses
423 result.StartJoin = make([]string, 0, len(a.StartJoin)+len(b.StartJoin))
424 result.StartJoin = append(result.StartJoin, a.StartJoin...)
425 result.StartJoin = append(result.StartJoin, b.StartJoin...)
426
427 // Copy the retry join addresses
428 result.RetryJoin = make([]string, 0, len(a.RetryJoin)+len(b.RetryJoin))
429 result.RetryJoin = append(result.RetryJoin, a.RetryJoin...)
430 result.RetryJoin = append(result.RetryJoin, b.RetryJoin...)
431
432 return &result
433 }
434
435 // ReadConfigPaths reads the paths in the given order to load configurations.
436 // The paths can be to files or directories. If the path is a directory,
437 // we read one directory deep and read any files ending in ".json" as
438 // configuration files.
439 func ReadConfigPaths(paths []string) (*Config, error) {
440 result := new(Config)
441 for _, path := range paths {
442 f, err := os.Open(path)
443 if err != nil {
444 return nil, fmt.Errorf("Error reading '%s': %s", path, err)
445 }
446
447 fi, err := f.Stat()
448 if err != nil {
449 f.Close()
450 return nil, fmt.Errorf("Error reading '%s': %s", path, err)
451 }
452
453 if !fi.IsDir() {
454 config, err := DecodeConfig(f)
455 f.Close()
456
457 if err != nil {
458 return nil, fmt.Errorf("Error decoding '%s': %s", path, err)
459 }
460
461 result = MergeConfig(result, config)
462 continue
463 }
464
465 contents, err := f.Readdir(-1)
466 f.Close()
467 if err != nil {
468 return nil, fmt.Errorf("Error reading '%s': %s", path, err)
469 }
470
471 // Sort the contents, ensures lexical order
472 sort.Sort(dirEnts(contents))
473
474 for _, fi := range contents {
475 // Don't recursively read contents
476 if fi.IsDir() {
477 continue
478 }
479
480 // If it isn't a JSON file, ignore it
481 if !strings.HasSuffix(fi.Name(), ".json") {
482 continue
483 }
484
485 subpath := filepath.Join(path, fi.Name())
486 f, err := os.Open(subpath)
487 if err != nil {
488 return nil, fmt.Errorf("Error reading '%s': %s", subpath, err)
489 }
490
491 config, err := DecodeConfig(f)
492 f.Close()
493
494 if err != nil {
495 return nil, fmt.Errorf("Error decoding '%s': %s", subpath, err)
496 }
497
498 result = MergeConfig(result, config)
499 }
500 }
501
502 return result, nil
503 }
504
505 // Implement the sort interface for dirEnts
506 func (d dirEnts) Len() int {
507 return len(d)
508 }
509
510 func (d dirEnts) Less(i, j int) bool {
511 return d[i].Name() < d[j].Name()
512 }
513
514 func (d dirEnts) Swap(i, j int) {
515 d[i], d[j] = d[j], d[i]
516 }
0 package agent
1
2 import (
3 "bytes"
4 "encoding/base64"
5 "io/ioutil"
6 "os"
7 "path/filepath"
8 "reflect"
9 "testing"
10 "time"
11 )
12
13 func TestConfigBindAddrParts(t *testing.T) {
14 testCases := []struct {
15 Value string
16 IP string
17 Port int
18 Error bool
19 }{
20 {"0.0.0.0", "0.0.0.0", DefaultBindPort, false},
21 {"0.0.0.0:1234", "0.0.0.0", 1234, false},
22 }
23
24 for _, tc := range testCases {
25 c := &Config{BindAddr: tc.Value}
26 ip, port, err := c.AddrParts(c.BindAddr)
27 if tc.Error != (err != nil) {
28 t.Errorf("Bad error: %s", err)
29 continue
30 }
31
32 if tc.IP != ip {
33 t.Errorf("%s: Got IP %#v", tc.Value, ip)
34 continue
35 }
36
37 if tc.Port != port {
38 t.Errorf("%s: Got port %d", tc.Value, port)
39 continue
40 }
41 }
42 }
43
44 func TestConfigEncryptBytes(t *testing.T) {
45 // Test with some input
46 src := []byte("abc")
47 c := &Config{
48 EncryptKey: base64.StdEncoding.EncodeToString(src),
49 }
50
51 result, err := c.EncryptBytes()
52 if err != nil {
53 t.Fatalf("err: %s", err)
54 }
55
56 if !bytes.Equal(src, result) {
57 t.Fatalf("bad: %#v", result)
58 }
59
60 // Test with no input
61 c = &Config{}
62 result, err = c.EncryptBytes()
63 if err != nil {
64 t.Fatalf("err: %s", err)
65 }
66
67 if len(result) > 0 {
68 t.Fatalf("bad: %#v", result)
69 }
70 }
71
72 func TestConfigEventScripts(t *testing.T) {
73 c := &Config{
74 EventHandlers: []string{
75 "foo.sh",
76 "bar=blah.sh",
77 },
78 }
79
80 result := c.EventScripts()
81 if len(result) != 2 {
82 t.Fatalf("bad: %#v", result)
83 }
84
85 expected := []EventScript{
86 {EventFilter{"*", ""}, "foo.sh"},
87 {EventFilter{"bar", ""}, "blah.sh"},
88 }
89
90 if !reflect.DeepEqual(result, expected) {
91 t.Fatalf("bad: %#v", result)
92 }
93 }
94
95 func TestDecodeConfig(t *testing.T) {
96 // Without a protocol
97 input := `{"node_name": "foo"}`
98 config, err := DecodeConfig(bytes.NewReader([]byte(input)))
99 if err != nil {
100 t.Fatalf("err: %s", err)
101 }
102
103 if config.NodeName != "foo" {
104 t.Fatalf("bad: %#v", config)
105 }
106
107 if config.Protocol != 0 {
108 t.Fatalf("bad: %#v", config)
109 }
110
111 if config.SkipLeaveOnInt != DefaultConfig().SkipLeaveOnInt {
112 t.Fatalf("bad: %#v", config)
113 }
114
115 if config.LeaveOnTerm != DefaultConfig().LeaveOnTerm {
116 t.Fatalf("bad: %#v", config)
117 }
118
119 // With a protocol
120 input = `{"node_name": "foo", "protocol": 7}`
121 config, err = DecodeConfig(bytes.NewReader([]byte(input)))
122 if err != nil {
123 t.Fatalf("err: %s", err)
124 }
125
126 if config.NodeName != "foo" {
127 t.Fatalf("bad: %#v", config)
128 }
129
130 if config.Protocol != 7 {
131 t.Fatalf("bad: %#v", config)
132 }
133
134 // A bind addr
135 input = `{"bind": "127.0.0.2"}`
136 config, err = DecodeConfig(bytes.NewReader([]byte(input)))
137 if err != nil {
138 t.Fatalf("err: %s", err)
139 }
140
141 if config.BindAddr != "127.0.0.2" {
142 t.Fatalf("bad: %#v", config)
143 }
144
145 // replayOnJoin
146 input = `{"replay_on_join": true}`
147 config, err = DecodeConfig(bytes.NewReader([]byte(input)))
148 if err != nil {
149 t.Fatalf("err: %s", err)
150 }
151
152 if config.ReplayOnJoin != true {
153 t.Fatalf("bad: %#v", config)
154 }
155
156 // leave_on_terminate
157 input = `{"leave_on_terminate": true}`
158 config, err = DecodeConfig(bytes.NewReader([]byte(input)))
159 if err != nil {
160 t.Fatalf("err: %s", err)
161 }
162
163 if config.LeaveOnTerm != true {
164 t.Fatalf("bad: %#v", config)
165 }
166
167 // skip_leave_on_interrupt
168 input = `{"skip_leave_on_interrupt": true}`
169 config, err = DecodeConfig(bytes.NewReader([]byte(input)))
170 if err != nil {
171 t.Fatalf("err: %s", err)
172 }
173
174 if config.SkipLeaveOnInt != true {
175 t.Fatalf("bad: %#v", config)
176 }
177
178 // tags
179 input = `{"tags": {"foo": "bar", "role": "test"}}`
180 config, err = DecodeConfig(bytes.NewReader([]byte(input)))
181 if err != nil {
182 t.Fatalf("err: %s", err)
183 }
184
185 if config.Tags["foo"] != "bar" {
186 t.Fatalf("bad: %#v", config)
187 }
188 if config.Tags["role"] != "test" {
189 t.Fatalf("bad: %#v", config)
190 }
191
192 // tags file
193 input = `{"tags_file": "/some/path"}`
194 config, err = DecodeConfig(bytes.NewReader([]byte(input)))
195 if err != nil {
196 t.Fatalf("err: %s", err)
197 }
198
199 if config.TagsFile != "/some/path" {
200 t.Fatalf("bad: %#v", config)
201 }
202
203 // Discover
204 input = `{"discover": "foobar"}`
205 config, err = DecodeConfig(bytes.NewReader([]byte(input)))
206 if err != nil {
207 t.Fatalf("err: %s", err)
208 }
209
210 if config.Discover != "foobar" {
211 t.Fatalf("bad: %#v", config)
212 }
213
214 // Interface
215 input = `{"interface": "eth0"}`
216 config, err = DecodeConfig(bytes.NewReader([]byte(input)))
217 if err != nil {
218 t.Fatalf("err: %s", err)
219 }
220
221 if config.Interface != "eth0" {
222 t.Fatalf("bad: %#v", config)
223 }
224
225 // Reconnect intervals
226 input = `{"reconnect_interval": "15s", "reconnect_timeout": "48h"}`
227 config, err = DecodeConfig(bytes.NewReader([]byte(input)))
228 if err != nil {
229 t.Fatalf("err: %s", err)
230 }
231
232 if config.ReconnectInterval != 15*time.Second {
233 t.Fatalf("bad: %#v", config)
234 }
235
236 if config.ReconnectTimeout != 48*time.Hour {
237 t.Fatalf("bad: %#v", config)
238 }
239
240 // RPC Auth
241 input = `{"rpc_auth": "foobar"}`
242 config, err = DecodeConfig(bytes.NewReader([]byte(input)))
243 if err != nil {
244 t.Fatalf("err: %s", err)
245 }
246
247 if config.RPCAuthKey != "foobar" {
248 t.Fatalf("bad: %#v", config)
249 }
250
251 // DisableNameResolution
252 input = `{"disable_name_resolution": true}`
253 config, err = DecodeConfig(bytes.NewReader([]byte(input)))
254 if err != nil {
255 t.Fatalf("err: %s", err)
256 }
257
258 if !config.DisableNameResolution {
259 t.Fatalf("bad: %#v", config)
260 }
261
262 // Tombstone intervals
263 input = `{"tombstone_timeout": "48h"}`
264 config, err = DecodeConfig(bytes.NewReader([]byte(input)))
265 if err != nil {
266 t.Fatalf("err: %s", err)
267 }
268
269 if config.TombstoneTimeout != 48*time.Hour {
270 t.Fatalf("bad: %#v", config)
271 }
272
273 // Syslog
274 input = `{"enable_syslog": true, "syslog_facility": "LOCAL4"}`
275 config, err = DecodeConfig(bytes.NewReader([]byte(input)))
276 if err != nil {
277 t.Fatalf("err: %s", err)
278 }
279
280 if !config.EnableSyslog {
281 t.Fatalf("bad: %#v", config)
282 }
283 if config.SyslogFacility != "LOCAL4" {
284 t.Fatalf("bad: %#v", config)
285 }
286
287 // Retry configs
288 input = `{"retry_max_attempts": 5, "retry_interval": "60s"}`
289 config, err = DecodeConfig(bytes.NewReader([]byte(input)))
290 if err != nil {
291 t.Fatalf("err: %s", err)
292 }
293
294 if config.RetryMaxAttempts != 5 {
295 t.Fatalf("bad: %#v", config)
296 }
297
298 if config.RetryInterval != 60*time.Second {
299 t.Fatalf("bad: %#v", config)
300 }
301
302 // Retry configs
303 input = `{"retry_join": ["127.0.0.1", "127.0.0.2"]}`
304 config, err = DecodeConfig(bytes.NewReader([]byte(input)))
305 if err != nil {
306 t.Fatalf("err: %s", err)
307 }
308
309 if len(config.RetryJoin) != 2 {
310 t.Fatalf("bad: %#v", config)
311 }
312
313 if config.RetryJoin[0] != "127.0.0.1" {
314 t.Fatalf("bad: %#v", config)
315 }
316
317 if config.RetryJoin[1] != "127.0.0.2" {
318 t.Fatalf("bad: %#v", config)
319 }
320
321 // Rejoin configs
322 input = `{"rejoin_after_leave": true}`
323 config, err = DecodeConfig(bytes.NewReader([]byte(input)))
324 if err != nil {
325 t.Fatalf("err: %s", err)
326 }
327
328 if !config.RejoinAfterLeave {
329 t.Fatalf("bad: %#v", config)
330 }
331
332 // Rejoin configs
333 input = `{"statsite_addr": "127.0.0.1:8123"}`
334 config, err = DecodeConfig(bytes.NewReader([]byte(input)))
335 if err != nil {
336 t.Fatalf("err: %s", err)
337 }
338
339 if config.StatsiteAddr != "127.0.0.1:8123" {
340 t.Fatalf("bad: %#v", config)
341 }
342 }
343
344 func TestDecodeConfig_unknownDirective(t *testing.T) {
345 input := `{"unknown_directive": "titi"}`
346 _, err := DecodeConfig(bytes.NewReader([]byte(input)))
347 if err == nil {
348 t.Fatal("should have err")
349 }
350 }
351
352 func TestMergeConfig(t *testing.T) {
353 a := &Config{
354 NodeName: "foo",
355 Role: "bar",
356 Protocol: 7,
357 EventHandlers: []string{"foo"},
358 StartJoin: []string{"foo"},
359 ReplayOnJoin: true,
360 RetryJoin: []string{"zab"},
361 }
362
363 b := &Config{
364 NodeName: "bname",
365 Protocol: -1,
366 EncryptKey: "foo",
367 EventHandlers: []string{"bar"},
368 StartJoin: []string{"bar"},
369 LeaveOnTerm: true,
370 SkipLeaveOnInt: true,
371 Discover: "tubez",
372 Interface: "eth0",
373 ReconnectInterval: 15 * time.Second,
374 ReconnectTimeout: 48 * time.Hour,
375 RPCAuthKey: "foobar",
376 DisableNameResolution: true,
377 TombstoneTimeout: 36 * time.Hour,
378 EnableSyslog: true,
379 RetryJoin: []string{"zip"},
380 RetryMaxAttempts: 10,
381 RetryInterval: 120 * time.Second,
382 RejoinAfterLeave: true,
383 StatsiteAddr: "127.0.0.1:8125",
384 }
385
386 c := MergeConfig(a, b)
387
388 if c.NodeName != "bname" {
389 t.Fatalf("bad: %#v", c)
390 }
391
392 if c.Role != "bar" {
393 t.Fatalf("bad: %#v", c)
394 }
395
396 if c.Protocol != 7 {
397 t.Fatalf("bad: %#v", c)
398 }
399
400 if c.EncryptKey != "foo" {
401 t.Fatalf("bad: %#v", c.EncryptKey)
402 }
403
404 if c.ReplayOnJoin != true {
405 t.Fatalf("bad: %#v", c.ReplayOnJoin)
406 }
407
408 if !c.LeaveOnTerm {
409 t.Fatalf("bad: %#v", c.LeaveOnTerm)
410 }
411
412 if !c.SkipLeaveOnInt {
413 t.Fatalf("bad: %#v", c.SkipLeaveOnInt)
414 }
415
416 if c.Discover != "tubez" {
417 t.Fatalf("Bad: %v", c.Discover)
418 }
419
420 if c.Interface != "eth0" {
421 t.Fatalf("Bad: %v", c.Interface)
422 }
423
424 if c.ReconnectInterval != 15*time.Second {
425 t.Fatalf("bad: %#v", c)
426 }
427
428 if c.ReconnectTimeout != 48*time.Hour {
429 t.Fatalf("bad: %#v", c)
430 }
431
432 if c.TombstoneTimeout != 36*time.Hour {
433 t.Fatalf("bad: %#v", c)
434 }
435
436 if c.RPCAuthKey != "foobar" {
437 t.Fatalf("bad: %#v", c)
438 }
439
440 if !c.DisableNameResolution {
441 t.Fatalf("bad: %#v", c)
442 }
443
444 if !c.EnableSyslog {
445 t.Fatalf("bad: %#v", c)
446 }
447
448 if c.RetryMaxAttempts != 10 {
449 t.Fatalf("bad: %#v", c)
450 }
451
452 if c.RetryInterval != 120*time.Second {
453 t.Fatalf("bad: %#v", c)
454 }
455
456 if !c.RejoinAfterLeave {
457 t.Fatalf("bad: %#v", c)
458 }
459
460 if c.StatsiteAddr != "127.0.0.1:8125" {
461 t.Fatalf("bad: %#v", c)
462 }
463
464 expected := []string{"foo", "bar"}
465 if !reflect.DeepEqual(c.EventHandlers, expected) {
466 t.Fatalf("bad: %#v", c)
467 }
468
469 if !reflect.DeepEqual(c.StartJoin, expected) {
470 t.Fatalf("bad: %#v", c)
471 }
472
473 expected = []string{"zab", "zip"}
474 if !reflect.DeepEqual(c.RetryJoin, expected) {
475 t.Fatalf("bad: %#v", c)
476 }
477 }
478
479 func TestReadConfigPaths_badPath(t *testing.T) {
480 _, err := ReadConfigPaths([]string{"/i/shouldnt/exist/ever/rainbows"})
481 if err == nil {
482 t.Fatal("should have err")
483 }
484 }
485
486 func TestReadConfigPaths_file(t *testing.T) {
487 tf, err := ioutil.TempFile("", "serf")
488 if err != nil {
489 t.Fatalf("err: %s", err)
490 }
491 tf.Write([]byte(`{"node_name":"bar"}`))
492 tf.Close()
493 defer os.Remove(tf.Name())
494
495 config, err := ReadConfigPaths([]string{tf.Name()})
496 if err != nil {
497 t.Fatalf("err: %s", err)
498 }
499
500 if config.NodeName != "bar" {
501 t.Fatalf("bad: %#v", config)
502 }
503 }
504
505 func TestReadConfigPaths_dir(t *testing.T) {
506 td, err := ioutil.TempDir("", "serf")
507 if err != nil {
508 t.Fatalf("err: %s", err)
509 }
510 defer os.RemoveAll(td)
511
512 err = ioutil.WriteFile(filepath.Join(td, "a.json"),
513 []byte(`{"node_name": "bar"}`), 0644)
514 if err != nil {
515 t.Fatalf("err: %s", err)
516 }
517
518 err = ioutil.WriteFile(filepath.Join(td, "b.json"),
519 []byte(`{"node_name": "baz"}`), 0644)
520 if err != nil {
521 t.Fatalf("err: %s", err)
522 }
523
524 // A non-json file, shouldn't be read
525 err = ioutil.WriteFile(filepath.Join(td, "c"),
526 []byte(`{"node_name": "bad"}`), 0644)
527 if err != nil {
528 t.Fatalf("err: %s", err)
529 }
530
531 config, err := ReadConfigPaths([]string{td})
532 if err != nil {
533 t.Fatalf("err: %s", err)
534 }
535
536 if config.NodeName != "baz" {
537 t.Fatalf("bad: %#v", config)
538 }
539 }
0 package agent
1
2 import (
3 "fmt"
4 "github.com/hashicorp/serf/serf"
5 "log"
6 "os"
7 "strings"
8 "sync"
9 )
10
11 // EventHandler is a handler that does things when events happen.
12 type EventHandler interface {
13 HandleEvent(serf.Event)
14 }
15
16 // ScriptEventHandler invokes scripts for the events that it receives.
17 type ScriptEventHandler struct {
18 SelfFunc func() serf.Member
19 Scripts []EventScript
20 Logger *log.Logger
21
22 scriptLock sync.Mutex
23 newScripts []EventScript
24 }
25
26 func (h *ScriptEventHandler) HandleEvent(e serf.Event) {
27 // Swap in the new scripts if any
28 h.scriptLock.Lock()
29 if h.newScripts != nil {
30 h.Scripts = h.newScripts
31 h.newScripts = nil
32 }
33 h.scriptLock.Unlock()
34
35 if h.Logger == nil {
36 h.Logger = log.New(os.Stderr, "", log.LstdFlags)
37 }
38
39 self := h.SelfFunc()
40 for _, script := range h.Scripts {
41 if !script.Invoke(e) {
42 continue
43 }
44
45 err := invokeEventScript(h.Logger, script.Script, self, e)
46 if err != nil {
47 h.Logger.Printf("[ERR] agent: Error invoking script '%s': %s",
48 script.Script, err)
49 }
50 }
51 }
52
53 // UpdateScripts is used to safely update the scripts we invoke in
54 // a thread safe manner
55 func (h *ScriptEventHandler) UpdateScripts(scripts []EventScript) {
56 h.scriptLock.Lock()
57 defer h.scriptLock.Unlock()
58 h.newScripts = scripts
59 }
60
61 // EventFilter is used to filter which events are processed
62 type EventFilter struct {
63 Event string
64 Name string
65 }
66
67 // Invoke tests whether or not this event script should be invoked
68 // for the given Serf event.
69 func (s *EventFilter) Invoke(e serf.Event) bool {
70 if s.Event == "*" {
71 return true
72 }
73
74 if e.EventType().String() != s.Event {
75 return false
76 }
77
78 if s.Event == "user" && s.Name != "" {
79 userE, ok := e.(serf.UserEvent)
80 if !ok {
81 return false
82 }
83
84 if userE.Name != s.Name {
85 return false
86 }
87 }
88
89 if s.Event == "query" && s.Name != "" {
90 query, ok := e.(*serf.Query)
91 if !ok {
92 return false
93 }
94
95 if query.Name != s.Name {
96 return false
97 }
98 }
99
100 return true
101 }
102
103 // Valid checks if this is a valid agent event script.
104 func (s *EventFilter) Valid() bool {
105 switch s.Event {
106 case "member-join":
107 case "member-leave":
108 case "member-failed":
109 case "member-update":
110 case "member-reap":
111 case "user":
112 case "query":
113 case "*":
114 default:
115 return false
116 }
117 return true
118 }
119
120 // EventScript is a single event script that will be executed in the
121 // case of an event, and is configured from the command-line or from
122 // a configuration file.
123 type EventScript struct {
124 EventFilter
125 Script string
126 }
127
128 func (s *EventScript) String() string {
129 if s.Name != "" {
130 return fmt.Sprintf("Event '%s:%s' invoking '%s'", s.Event, s.Name, s.Script)
131 }
132 return fmt.Sprintf("Event '%s' invoking '%s'", s.Event, s.Script)
133 }
134
135 // ParseEventScript takes a string in the format of "type=script" and
136 // parses it into an EventScript struct, if it can.
137 func ParseEventScript(v string) []EventScript {
138 var filter, script string
139 parts := strings.SplitN(v, "=", 2)
140 if len(parts) == 1 {
141 script = parts[0]
142 } else {
143 filter = parts[0]
144 script = parts[1]
145 }
146
147 filters := ParseEventFilter(filter)
148 results := make([]EventScript, 0, len(filters))
149 for _, filt := range filters {
150 result := EventScript{
151 EventFilter: filt,
152 Script: script,
153 }
154 results = append(results, result)
155 }
156 return results
157 }
158
159 // ParseEventFilter a string with the event type filters and
160 // parses it into a series of EventFilters if it can.
161 func ParseEventFilter(v string) []EventFilter {
162 // No filter translates to stream all
163 if v == "" {
164 v = "*"
165 }
166
167 events := strings.Split(v, ",")
168 results := make([]EventFilter, 0, len(events))
169 for _, event := range events {
170 var result EventFilter
171 var name string
172
173 if strings.HasPrefix(event, "user:") {
174 name = event[len("user:"):]
175 event = "user"
176 } else if strings.HasPrefix(event, "query:") {
177 name = event[len("query:"):]
178 event = "query"
179 }
180
181 result.Event = event
182 result.Name = name
183 results = append(results, result)
184 }
185
186 return results
187 }
0 package agent
1
2 import (
3 "github.com/hashicorp/serf/serf"
4 "sync"
5 )
6
7 // MockEventHandler is an EventHandler implementation that can be used
8 // for tests.
9 type MockEventHandler struct {
10 Events []serf.Event
11 sync.Mutex
12 }
13
14 func (h *MockEventHandler) HandleEvent(e serf.Event) {
15 h.Lock()
16 defer h.Unlock()
17 h.Events = append(h.Events, e)
18 }
19
20 // MockQueryHandler is an EventHandler implementation used for tests,
21 // it always responds to a query with a given response
22 type MockQueryHandler struct {
23 Response []byte
24 Queries []*serf.Query
25 sync.Mutex
26 }
27
28 func (h *MockQueryHandler) HandleEvent(e serf.Event) {
29 query, ok := e.(*serf.Query)
30 if !ok {
31 return
32 }
33
34 h.Lock()
35 h.Queries = append(h.Queries, query)
36 h.Unlock()
37
38 query.Respond(h.Response)
39 }
0 package agent
1
2 import (
3 "fmt"
4 "github.com/hashicorp/serf/serf"
5 "io/ioutil"
6 "net"
7 "os"
8 "testing"
9 )
10
11 const eventScript = `#!/bin/sh
12 RESULT_FILE="%s"
13 echo $SERF_SELF_NAME $SERF_SELF_ROLE >>${RESULT_FILE}
14 echo $SERF_TAG_DC >> ${RESULT_FILE}
15 echo $SERF_TAG_BAD_TAG >> ${RESULT_FILE}
16 echo $SERF_EVENT $SERF_USER_EVENT "$@" >>${RESULT_FILE}
17 echo $os_env_var >> ${RESULT_FILE}
18 while read line; do
19 printf "${line}\n" >>${RESULT_FILE}
20 done
21 `
22
23 const userEventScript = `#!/bin/sh
24 RESULT_FILE="%s"
25 echo $SERF_SELF_NAME $SERF_SELF_ROLE >>${RESULT_FILE}
26 echo $SERF_TAG_DC >> ${RESULT_FILE}
27 echo $SERF_EVENT $SERF_USER_EVENT "$@" >>${RESULT_FILE}
28 echo $SERF_EVENT $SERF_USER_LTIME "$@" >>${RESULT_FILE}
29 while read line; do
30 printf "${line}\n" >>${RESULT_FILE}
31 done
32 `
33
34 const queryScript = `#!/bin/sh
35 RESULT_FILE="%s"
36 echo $SERF_SELF_NAME $SERF_SELF_ROLE >>${RESULT_FILE}
37 echo $SERF_TAG_DC >> ${RESULT_FILE}
38 echo $SERF_EVENT $SERF_QUERY_NAME "$@" >>${RESULT_FILE}
39 echo $SERF_EVENT $SERF_QUERY_LTIME "$@" >>${RESULT_FILE}
40 while read line; do
41 printf "${line}\n" >>${RESULT_FILE}
42 done
43 `
44
45 // testEventScript creates an event script that can be used with the
46 // agent. It returns the path to the event script itself and a path to
47 // the file that will contain the events that that script receives.
48 func testEventScript(t *testing.T, script string) (string, string) {
49 scriptFile, err := ioutil.TempFile("", "serf")
50 if err != nil {
51 t.Fatalf("err: %s", err)
52 }
53 defer scriptFile.Close()
54
55 if err := scriptFile.Chmod(0755); err != nil {
56 t.Fatalf("err: %s", err)
57 }
58
59 resultFile, err := ioutil.TempFile("", "serf-result")
60 if err != nil {
61 t.Fatalf("err: %s", err)
62 }
63 defer resultFile.Close()
64
65 _, err = scriptFile.Write([]byte(
66 fmt.Sprintf(script, resultFile.Name())))
67 if err != nil {
68 t.Fatalf("err: %s", err)
69 }
70
71 return scriptFile.Name(), resultFile.Name()
72 }
73
74 func TestScriptEventHandler(t *testing.T) {
75 os.Setenv("os_env_var", "os-env-foo")
76
77 script, results := testEventScript(t, eventScript)
78
79 h := &ScriptEventHandler{
80 SelfFunc: func() serf.Member {
81 return serf.Member{
82 Name: "ourname",
83 Tags: map[string]string{"role": "ourrole", "dc": "east-aws", "bad-tag": "bad"},
84 }
85 },
86 Scripts: []EventScript{
87 {
88 EventFilter: EventFilter{
89 Event: "*",
90 },
91 Script: script,
92 },
93 },
94 }
95
96 event := serf.MemberEvent{
97 Type: serf.EventMemberJoin,
98 Members: []serf.Member{
99 {
100 Name: "foo",
101 Addr: net.ParseIP("1.2.3.4"),
102 Tags: map[string]string{"role": "bar", "foo": "bar"},
103 },
104 },
105 }
106
107 h.HandleEvent(event)
108
109 result, err := ioutil.ReadFile(results)
110 if err != nil {
111 t.Fatalf("err: %s", err)
112 }
113
114 expected1 := "ourname ourrole\neast-aws\nbad\nmember-join\nos-env-foo\nfoo\t1.2.3.4\tbar\trole=bar,foo=bar\n"
115 expected2 := "ourname ourrole\neast-aws\nbad\nmember-join\nos-env-foo\nfoo\t1.2.3.4\tbar\tfoo=bar,role=bar\n"
116 if string(result) != expected1 && string(result) != expected2 {
117 t.Fatalf("bad: %#v. Expected: %#v or %v", string(result), expected1, expected2)
118 }
119 }
120
121 func TestScriptUserEventHandler(t *testing.T) {
122 script, results := testEventScript(t, userEventScript)
123
124 h := &ScriptEventHandler{
125 SelfFunc: func() serf.Member {
126 return serf.Member{
127 Name: "ourname",
128 Tags: map[string]string{"role": "ourrole", "dc": "east-aws"},
129 }
130 },
131 Scripts: []EventScript{
132 {
133 EventFilter: EventFilter{
134 Event: "*",
135 },
136 Script: script,
137 },
138 },
139 }
140
141 userEvent := serf.UserEvent{
142 LTime: 1,
143 Name: "baz",
144 Payload: []byte("foobar"),
145 Coalesce: true,
146 }
147
148 h.HandleEvent(userEvent)
149
150 result, err := ioutil.ReadFile(results)
151 if err != nil {
152 t.Fatalf("err: %s", err)
153 }
154
155 expected := "ourname ourrole\neast-aws\nuser baz\nuser 1\nfoobar\n"
156 if string(result) != expected {
157 t.Fatalf("bad: %#v. Expected: %#v", string(result), expected)
158 }
159 }
160
161 func TestScriptQueryEventHandler(t *testing.T) {
162 script, results := testEventScript(t, queryScript)
163
164 h := &ScriptEventHandler{
165 SelfFunc: func() serf.Member {
166 return serf.Member{
167 Name: "ourname",
168 Tags: map[string]string{"role": "ourrole", "dc": "east-aws"},
169 }
170 },
171 Scripts: []EventScript{
172 {
173 EventFilter: EventFilter{
174 Event: "*",
175 },
176 Script: script,
177 },
178 },
179 }
180
181 query := &serf.Query{
182 LTime: 42,
183 Name: "uptime",
184 Payload: []byte("load average"),
185 }
186
187 h.HandleEvent(query)
188
189 result, err := ioutil.ReadFile(results)
190 if err != nil {
191 t.Fatalf("err: %s", err)
192 }
193
194 expected := "ourname ourrole\neast-aws\nquery uptime\nquery 42\nload average\n"
195 if string(result) != expected {
196 t.Fatalf("bad: %#v. Expected: %#v", string(result), expected)
197 }
198 }
199
200 func TestEventScriptInvoke(t *testing.T) {
201 testCases := []struct {
202 script EventScript
203 event serf.Event
204 invoke bool
205 }{
206 {
207 EventScript{EventFilter{"*", ""}, "script.sh"},
208 serf.MemberEvent{},
209 true,
210 },
211 {
212 EventScript{EventFilter{"user", ""}, "script.sh"},
213 serf.MemberEvent{},
214 false,
215 },
216 {
217 EventScript{EventFilter{"user", "deploy"}, "script.sh"},
218 serf.UserEvent{Name: "deploy"},
219 true,
220 },
221 {
222 EventScript{EventFilter{"user", "deploy"}, "script.sh"},
223 serf.UserEvent{Name: "restart"},
224 false,
225 },
226 {
227 EventScript{EventFilter{"member-join", ""}, "script.sh"},
228 serf.MemberEvent{Type: serf.EventMemberJoin},
229 true,
230 },
231 {
232 EventScript{EventFilter{"member-join", ""}, "script.sh"},
233 serf.MemberEvent{Type: serf.EventMemberLeave},
234 false,
235 },
236 {
237 EventScript{EventFilter{"member-reap", ""}, "script.sh"},
238 serf.MemberEvent{Type: serf.EventMemberReap},
239 true,
240 },
241 {
242 EventScript{EventFilter{"query", "deploy"}, "script.sh"},
243 &serf.Query{Name: "deploy"},
244 true,
245 },
246 {
247 EventScript{EventFilter{"query", "uptime"}, "script.sh"},
248 &serf.Query{Name: "deploy"},
249 false,
250 },
251 {
252 EventScript{EventFilter{"query", ""}, "script.sh"},
253 &serf.Query{Name: "deploy"},
254 true,
255 },
256 }
257
258 for _, tc := range testCases {
259 result := tc.script.Invoke(tc.event)
260 if result != tc.invoke {
261 t.Errorf("bad: %#v", tc)
262 }
263 }
264 }
265
266 func TestEventScriptValid(t *testing.T) {
267 testCases := []struct {
268 Event string
269 Valid bool
270 }{
271 {"member-join", true},
272 {"member-leave", true},
273 {"member-failed", true},
274 {"member-update", true},
275 {"member-reap", true},
276 {"user", true},
277 {"User", false},
278 {"member", false},
279 {"query", true},
280 {"Query", false},
281 {"*", true},
282 }
283
284 for _, tc := range testCases {
285 script := EventScript{EventFilter: EventFilter{Event: tc.Event}}
286 if script.Valid() != tc.Valid {
287 t.Errorf("bad: %#v", tc)
288 }
289 }
290 }
291
292 func TestParseEventScript(t *testing.T) {
293 testCases := []struct {
294 v string
295 err bool
296 results []EventScript
297 }{
298 {
299 "script.sh",
300 false,
301 []EventScript{{EventFilter{"*", ""}, "script.sh"}},
302 },
303
304 {
305 "member-join=script.sh",
306 false,
307 []EventScript{{EventFilter{"member-join", ""}, "script.sh"}},
308 },
309
310 {
311 "foo,bar=script.sh",
312 false,
313 []EventScript{
314 {EventFilter{"foo", ""}, "script.sh"},
315 {EventFilter{"bar", ""}, "script.sh"},
316 },
317 },
318
319 {
320 "user:deploy=script.sh",
321 false,
322 []EventScript{{EventFilter{"user", "deploy"}, "script.sh"}},
323 },
324
325 {
326 "foo,user:blah,bar,query:tubez=script.sh",
327 false,
328 []EventScript{
329 {EventFilter{"foo", ""}, "script.sh"},
330 {EventFilter{"user", "blah"}, "script.sh"},
331 {EventFilter{"bar", ""}, "script.sh"},
332 {EventFilter{"query", "tubez"}, "script.sh"},
333 },
334 },
335
336 {
337 "query:load=script.sh",
338 false,
339 []EventScript{{EventFilter{"query", "load"}, "script.sh"}},
340 },
341
342 {
343 "query=script.sh",
344 false,
345 []EventScript{{EventFilter{"query", ""}, "script.sh"}},
346 },
347 }
348
349 for _, tc := range testCases {
350 results := ParseEventScript(tc.v)
351 if results == nil {
352 t.Errorf("result should not be nil")
353 continue
354 }
355
356 if len(results) != len(tc.results) {
357 t.Errorf("bad: %#v", results)
358 continue
359 }
360
361 for i, r := range results {
362 expected := tc.results[i]
363
364 if r.Event != expected.Event {
365 t.Errorf("Events not equal: %s %s", r.Event, expected.Event)
366 }
367
368 if r.Name != expected.Name {
369 t.Errorf("User events not equal: %s %s", r.Name, expected.Name)
370 }
371
372 if r.Script != expected.Script {
373 t.Errorf("Scripts not equal: %s %s", r.Script, expected.Script)
374 }
375 }
376 }
377 }
378
379 func TestParseEventFilter(t *testing.T) {
380 testCases := []struct {
381 v string
382 results []EventFilter
383 }{
384 {
385 "",
386 []EventFilter{EventFilter{"*", ""}},
387 },
388
389 {
390 "member-join",
391 []EventFilter{EventFilter{"member-join", ""}},
392 },
393
394 {
395 "member-reap",
396 []EventFilter{EventFilter{"member-reap", ""}},
397 },
398
399 {
400 "foo,bar",
401 []EventFilter{
402 EventFilter{"foo", ""},
403 EventFilter{"bar", ""},
404 },
405 },
406
407 {
408 "user:deploy",
409 []EventFilter{EventFilter{"user", "deploy"}},
410 },
411
412 {
413 "foo,user:blah,bar",
414 []EventFilter{
415 EventFilter{"foo", ""},
416 EventFilter{"user", "blah"},
417 EventFilter{"bar", ""},
418 },
419 },
420
421 {
422 "query:load",
423 []EventFilter{EventFilter{"query", "load"}},
424 },
425 }
426
427 for _, tc := range testCases {
428 results := ParseEventFilter(tc.v)
429 if results == nil {
430 t.Errorf("result should not be nil")
431 continue
432 }
433
434 if len(results) != len(tc.results) {
435 t.Errorf("bad: %#v", results)
436 continue
437 }
438
439 for i, r := range results {
440 expected := tc.results[i]
441
442 if r.Event != expected.Event {
443 t.Errorf("Events not equal: %s %s", r.Event, expected.Event)
444 }
445
446 if r.Name != expected.Name {
447 t.Errorf("User events not equal: %s %s", r.Name, expected.Name)
448 }
449 }
450 }
451 }
0 package agent
1
2 import "strings"
3
4 // AppendSliceValue implements the flag.Value interface and allows multiple
5 // calls to the same variable to append a list.
6 type AppendSliceValue []string
7
8 func (s *AppendSliceValue) String() string {
9 return strings.Join(*s, ",")
10 }
11
12 func (s *AppendSliceValue) Set(value string) error {
13 if *s == nil {
14 *s = make([]string, 0, 1)
15 }
16
17 *s = append(*s, value)
18 return nil
19 }
0 package agent
1
2 import (
3 "flag"
4 "reflect"
5 "testing"
6 )
7
8 func TestAppendSliceValue_implements(t *testing.T) {
9 var raw interface{}
10 raw = new(AppendSliceValue)
11 if _, ok := raw.(flag.Value); !ok {
12 t.Fatalf("AppendSliceValue should be a Value")
13 }
14 }
15
16 func TestAppendSliceValueSet(t *testing.T) {
17 sv := new(AppendSliceValue)
18 err := sv.Set("foo")
19 if err != nil {
20 t.Fatalf("err: %s", err)
21 }
22
23 err = sv.Set("bar")
24 if err != nil {
25 t.Fatalf("err: %s", err)
26 }
27
28 expected := []string{"foo", "bar"}
29 if !reflect.DeepEqual([]string(*sv), expected) {
30 t.Fatalf("Bad: %#v", sv)
31 }
32 }
0 package agent
1
2 import (
3 "io"
4 "sync"
5 )
6
7 // GatedWriter is an io.Writer implementation that buffers all of its
8 // data into an internal buffer until it is told to let data through.
9 type GatedWriter struct {
10 Writer io.Writer
11
12 buf [][]byte
13 flush bool
14 lock sync.RWMutex
15 }
16
17 // Flush tells the GatedWriter to flush any buffered data and to stop
18 // buffering.
19 func (w *GatedWriter) Flush() {
20 w.lock.Lock()
21 w.flush = true
22 w.lock.Unlock()
23
24 for _, p := range w.buf {
25 w.Write(p)
26 }
27 w.buf = nil
28 }
29
30 func (w *GatedWriter) Write(p []byte) (n int, err error) {
31 w.lock.RLock()
32 defer w.lock.RUnlock()
33
34 if w.flush {
35 return w.Writer.Write(p)
36 }
37
38 p2 := make([]byte, len(p))
39 copy(p2, p)
40 w.buf = append(w.buf, p2)
41 return len(p), nil
42 }
0 package agent
1
2 import (
3 "bytes"
4 "io"
5 "testing"
6 )
7
8 func TestGatedWriter_impl(t *testing.T) {
9 var _ io.Writer = new(GatedWriter)
10 }
11
12 func TestGatedWriter(t *testing.T) {
13 buf := new(bytes.Buffer)
14 w := &GatedWriter{Writer: buf}
15 w.Write([]byte("foo\n"))
16 w.Write([]byte("bar\n"))
17
18 if buf.String() != "" {
19 t.Fatalf("bad: %s", buf.String())
20 }
21
22 w.Flush()
23
24 if buf.String() != "foo\nbar\n" {
25 t.Fatalf("bad: %s", buf.String())
26 }
27
28 w.Write([]byte("baz\n"))
29
30 if buf.String() != "foo\nbar\nbaz\n" {
31 t.Fatalf("bad: %s", buf.String())
32 }
33 }
0 package agent
1
2 import (
3 "fmt"
4 "github.com/armon/circbuf"
5 "github.com/armon/go-metrics"
6 "github.com/hashicorp/serf/serf"
7 "io"
8 "log"
9 "os"
10 "os/exec"
11 "regexp"
12 "runtime"
13 "strings"
14 "time"
15 )
16
17 const (
18 windows = "windows"
19
20 // maxBufSize limits how much data we collect from a handler.
21 // This is to prevent Serf's memory from growing to an enormous
22 // amount due to a faulty handler.
23 maxBufSize = 8 * 1024
24
25 // warnSlow is used to warn about a slow handler invocation
26 warnSlow = time.Second
27 )
28
29 var sanitizeTagRegexp = regexp.MustCompile(`[^A-Z0-9_]`)
30
31 // invokeEventScript will execute the given event script with the given
32 // event. Depending on the event, the semantics of how data are passed
33 // are a bit different. For all events, the SERF_EVENT environmental
34 // variable is the type of the event. For user events, the SERF_USER_EVENT
35 // environmental variable is also set, containing the name of the user
36 // event that was fired.
37 //
38 // In all events, data is passed in via stdin to facilitate piping. See
39 // the various stdin functions below for more information.
40 func invokeEventScript(logger *log.Logger, script string, self serf.Member, event serf.Event) error {
41 defer metrics.MeasureSince([]string{"agent", "invoke", script}, time.Now())
42 output, _ := circbuf.NewBuffer(maxBufSize)
43
44 // Determine the shell invocation based on OS
45 var shell, flag string
46 if runtime.GOOS == windows {
47 shell = "cmd"
48 flag = "/C"
49 } else {
50 shell = "/bin/sh"
51 flag = "-c"
52 }
53
54 cmd := exec.Command(shell, flag, script)
55 cmd.Env = append(os.Environ(),
56 "SERF_EVENT="+event.EventType().String(),
57 "SERF_SELF_NAME="+self.Name,
58 "SERF_SELF_ROLE="+self.Tags["role"],
59 )
60 cmd.Stderr = output
61 cmd.Stdout = output
62
63 // Add all the tags
64 for name, val := range self.Tags {
65 //http://stackoverflow.com/questions/2821043/allowed-characters-in-linux-environment-variable-names
66 //(http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap08.html for the long version)
67 //says that env var names must be in [A-Z0-9_] and not start with [0-9].
68 //we only care about the first part, so convert all chars not in [A-Z0-9_] to _
69 sanitizedName := sanitizeTagRegexp.ReplaceAllString(strings.ToUpper(name), "_")
70 tag_env := fmt.Sprintf("SERF_TAG_%s=%s", sanitizedName, val)
71 cmd.Env = append(cmd.Env, tag_env)
72 }
73
74 stdin, err := cmd.StdinPipe()
75 if err != nil {
76 return err
77 }
78
79 switch e := event.(type) {
80 case serf.MemberEvent:
81 go memberEventStdin(logger, stdin, &e)
82 case serf.UserEvent:
83 cmd.Env = append(cmd.Env, "SERF_USER_EVENT="+e.Name)
84 cmd.Env = append(cmd.Env, fmt.Sprintf("SERF_USER_LTIME=%d", e.LTime))
85 go streamPayload(logger, stdin, e.Payload)
86 case *serf.Query:
87 cmd.Env = append(cmd.Env, "SERF_QUERY_NAME="+e.Name)
88 cmd.Env = append(cmd.Env, fmt.Sprintf("SERF_QUERY_LTIME=%d", e.LTime))
89 go streamPayload(logger, stdin, e.Payload)
90 default:
91 return fmt.Errorf("Unknown event type: %s", event.EventType().String())
92 }
93
94 // Start a timer to warn about slow handlers
95 slowTimer := time.AfterFunc(warnSlow, func() {
96 logger.Printf("[WARN] agent: Script '%s' slow, execution exceeding %v",
97 script, warnSlow)
98 })
99
100 if err := cmd.Start(); err != nil {
101 return err
102 }
103
104 // Warn if buffer is overritten
105 if output.TotalWritten() > output.Size() {
106 logger.Printf("[WARN] agent: Script '%s' generated %d bytes of output, truncated to %d",
107 script, output.TotalWritten(), output.Size())
108 }
109
110 err = cmd.Wait()
111 slowTimer.Stop()
112 logger.Printf("[DEBUG] agent: Event '%s' script output: %s",
113 event.EventType().String(), output.String())
114 if err != nil {
115 return err
116 }
117
118 // If this is a query and we have output, respond
119 if query, ok := event.(*serf.Query); ok && output.TotalWritten() > 0 {
120 if err := query.Respond(output.Bytes()); err != nil {
121 logger.Printf("[WARN] agent: Failed to respond to query '%s': %s",
122 event.String(), err)
123 }
124 }
125
126 return nil
127 }
128
129 // eventClean cleans a value to be a parameter in an event line.
130 func eventClean(v string) string {
131 v = strings.Replace(v, "\t", "\\t", -1)
132 v = strings.Replace(v, "\n", "\\n", -1)
133 return v
134 }
135
136 // Sends data on stdin for a member event.
137 //
138 // The format for the data is unix tool friendly, separated by whitespace
139 // and newlines. The structure of each line for any member event is:
140 // "NAME ADDRESS ROLE TAGS" where the whitespace is actually tabs.
141 // The name and role are cleaned so that newlines and tabs are replaced
142 // with "\n" and "\t" respectively.
143 func memberEventStdin(logger *log.Logger, stdin io.WriteCloser, e *serf.MemberEvent) {
144 defer stdin.Close()
145 for _, member := range e.Members {
146 // Format the tags as tag1=v1,tag2=v2,...
147 var tagPairs []string
148 for name, value := range member.Tags {
149 tagPairs = append(tagPairs, fmt.Sprintf("%s=%s", name, value))
150 }
151 tags := strings.Join(tagPairs, ",")
152
153 // Send the entire line
154 _, err := stdin.Write([]byte(fmt.Sprintf(
155 "%s\t%s\t%s\t%s\n",
156 eventClean(member.Name),
157 member.Addr.String(),
158 eventClean(member.Tags["role"]),
159 eventClean(tags))))
160 if err != nil {
161 return
162 }
163 }
164 }
165
166 // Sends data on stdin for an event. The stdin simply contains the
167 // payload (if any).
168 // Most shells read implementations need a newline, force it to be there
169 func streamPayload(logger *log.Logger, stdin io.WriteCloser, buf []byte) {
170 defer stdin.Close()
171
172 // Append a newline to payload if missing
173 payload := buf
174 if len(payload) > 0 && payload[len(payload)-1] != '\n' {
175 payload = append(payload, '\n')
176 }
177
178 if _, err := stdin.Write(payload); err != nil {
179 logger.Printf("[ERR] Error writing payload: %s", err)
180 return
181 }
182 }
0 package agent
1
2 /*
3 The agent exposes an IPC mechanism that is used for both controlling
4 Serf as well as providing a fast streaming mechanism for events. This
5 allows other applications to easily leverage Serf as the event layer.
6
7 We additionally make use of the IPC layer to also handle RPC calls from
8 the CLI to unify the code paths. This results in a split Request/Response
9 as well as streaming mode of operation.
10
11 The system is fairly simple, each client opens a TCP connection to the
12 agent. The connection is initialized with a handshake which establishes
13 the protocol version being used. This is to allow for future changes to
14 the protocol.
15
16 Once initialized, clients send commands and wait for responses. Certain
17 commands will cause the client to subscribe to events, and those will be
18 pushed down the socket as they are received. This provides a low-latency
19 mechanism for applications to send and receive events, while also providing
20 a flexible control mechanism for Serf.
21 */
22
23 import (
24 "bufio"
25 "fmt"
26 "github.com/armon/go-metrics"
27 "github.com/hashicorp/go-msgpack/codec"
28 "github.com/hashicorp/logutils"
29 "github.com/hashicorp/serf/serf"
30 "io"
31 "log"
32 "net"
33 "os"
34 "regexp"
35 "strings"
36 "sync"
37 "sync/atomic"
38 "time"
39 )
40
41 const (
42 MinIPCVersion = 1
43 MaxIPCVersion = 1
44 )
45
46 const (
47 handshakeCommand = "handshake"
48 eventCommand = "event"
49 forceLeaveCommand = "force-leave"
50 joinCommand = "join"
51 membersCommand = "members"
52 membersFilteredCommand = "members-filtered"
53 streamCommand = "stream"
54 stopCommand = "stop"
55 monitorCommand = "monitor"
56 leaveCommand = "leave"
57 installKeyCommand = "install-key"
58 useKeyCommand = "use-key"
59 removeKeyCommand = "remove-key"
60 listKeysCommand = "list-keys"
61 tagsCommand = "tags"
62 queryCommand = "query"
63 respondCommand = "respond"
64 authCommand = "auth"
65 statsCommand = "stats"
66 )
67
68 const (
69 unsupportedCommand = "Unsupported command"
70 unsupportedIPCVersion = "Unsupported IPC version"
71 duplicateHandshake = "Handshake already performed"
72 handshakeRequired = "Handshake required"
73 monitorExists = "Monitor already exists"
74 invalidFilter = "Invalid event filter"
75 streamExists = "Stream with given sequence exists"
76 invalidQueryID = "No pending queries matching ID"
77 authRequired = "Authentication required"
78 invalidAuthToken = "Invalid authentication token"
79 )
80
81 const (
82 queryRecordAck = "ack"
83 queryRecordResponse = "response"
84 queryRecordDone = "done"
85 )
86
87 // Request header is sent before each request
88 type requestHeader struct {
89 Command string
90 Seq uint64
91 }
92
93 // Response header is sent before each response
94 type responseHeader struct {
95 Seq uint64
96 Error string
97 }
98
99 type handshakeRequest struct {
100 Version int32
101 }
102
103 type authRequest struct {
104 AuthKey string
105 }
106
107 type eventRequest struct {
108 Name string
109 Payload []byte
110 Coalesce bool
111 }
112
113 type forceLeaveRequest struct {
114 Node string
115 }
116
117 type joinRequest struct {
118 Existing []string
119 Replay bool
120 }
121
122 type joinResponse struct {
123 Num int32
124 }
125
126 type membersFilteredRequest struct {
127 Tags map[string]string
128 Status string
129 Name string
130 }
131
132 type membersResponse struct {
133 Members []Member
134 }
135
136 type keyRequest struct {
137 Key string
138 }
139
140 type keyResponse struct {
141 Messages map[string]string
142 Keys map[string]int
143 NumNodes int
144 NumErr int
145 NumResp int
146 }
147
148 type monitorRequest struct {
149 LogLevel string
150 }
151
152 type streamRequest struct {
153 Type string
154 }
155
156 type stopRequest struct {
157 Stop uint64
158 }
159
160 type tagsRequest struct {
161 Tags map[string]string
162 DeleteTags []string
163 }
164
165 type queryRequest struct {
166 FilterNodes []string
167 FilterTags map[string]string
168 RequestAck bool
169 Timeout time.Duration
170 Name string
171 Payload []byte
172 }
173
174 type respondRequest struct {
175 ID uint64
176 Payload []byte
177 }
178
179 type queryRecord struct {
180 Type string
181 From string
182 Payload []byte
183 }
184
185 type logRecord struct {
186 Log string
187 }
188
189 type userEventRecord struct {
190 Event string
191 LTime serf.LamportTime
192 Name string
193 Payload []byte
194 Coalesce bool
195 }
196
197 type queryEventRecord struct {
198 Event string
199 ID uint64 // ID is opaque to client, used to respond
200 LTime serf.LamportTime
201 Name string
202 Payload []byte
203 }
204
205 type Member struct {
206 Name string
207 Addr net.IP
208 Port uint16
209 Tags map[string]string
210 Status string
211 ProtocolMin uint8
212 ProtocolMax uint8
213 ProtocolCur uint8
214 DelegateMin uint8
215 DelegateMax uint8
216 DelegateCur uint8
217 }
218
219 type memberEventRecord struct {
220 Event string
221 Members []Member
222 }
223
224 type AgentIPC struct {
225 sync.Mutex
226 agent *Agent
227 authKey string
228 clients map[string]*IPCClient
229 listener net.Listener
230 logger *log.Logger
231 logWriter *logWriter
232 stop bool
233 stopCh chan struct{}
234 }
235
236 type IPCClient struct {
237 queryID uint64 // Used to increment query IDs
238 name string
239 conn net.Conn
240 reader *bufio.Reader
241 writer *bufio.Writer
242 dec *codec.Decoder
243 enc *codec.Encoder
244 writeLock sync.Mutex
245 version int32 // From the handshake, 0 before
246 logStreamer *logStream
247 eventStreams map[uint64]*eventStream
248
249 pendingQueries map[uint64]*serf.Query
250 queryLock sync.Mutex
251
252 didAuth bool // Did we get an auth token yet?
253 }
254
255 // send is used to send an object using the MsgPack encoding. send
256 // is serialized to prevent write overlaps, while properly buffering.
257 func (c *IPCClient) Send(header *responseHeader, obj interface{}) error {
258 c.writeLock.Lock()
259 defer c.writeLock.Unlock()
260
261 if err := c.enc.Encode(header); err != nil {
262 return err
263 }
264
265 if obj != nil {
266 if err := c.enc.Encode(obj); err != nil {
267 return err
268 }
269 }
270
271 if err := c.writer.Flush(); err != nil {
272 return err
273 }
274
275 return nil
276 }
277
278 func (c *IPCClient) String() string {
279 return fmt.Sprintf("ipc.client: %v", c.conn.RemoteAddr())
280 }
281
282 // nextQueryID safely generates a new query ID
283 func (c *IPCClient) nextQueryID() uint64 {
284 return atomic.AddUint64(&c.queryID, 1)
285 }
286
287 // RegisterQuery is used to register a pending query that may
288 // get a response. The ID of the query is returned
289 func (c *IPCClient) RegisterQuery(q *serf.Query) uint64 {
290 // Generate a unique-per-client ID
291 id := c.nextQueryID()
292
293 // Ensure the query deadline is in the future
294 timeout := q.Deadline().Sub(time.Now())
295 if timeout < 0 {
296 return id
297 }
298
299 // Register the query
300 c.queryLock.Lock()
301 c.pendingQueries[id] = q
302 c.queryLock.Unlock()
303
304 // Setup a timer to deregister after the timeout
305 time.AfterFunc(timeout, func() {
306 c.queryLock.Lock()
307 delete(c.pendingQueries, id)
308 c.queryLock.Unlock()
309 })
310 return id
311 }
312
313 // NewAgentIPC is used to create a new Agent IPC handler
314 func NewAgentIPC(agent *Agent, authKey string, listener net.Listener,
315 logOutput io.Writer, logWriter *logWriter) *AgentIPC {
316 if logOutput == nil {
317 logOutput = os.Stderr
318 }
319 ipc := &AgentIPC{
320 agent: agent,
321 authKey: authKey,
322 clients: make(map[string]*IPCClient),
323 listener: listener,
324 logger: log.New(logOutput, "", log.LstdFlags),
325 logWriter: logWriter,
326 stopCh: make(chan struct{}),
327 }
328 go ipc.listen()
329 return ipc
330 }
331
332 // Shutdown is used to shutdown the IPC layer
333 func (i *AgentIPC) Shutdown() {
334 i.Lock()
335 defer i.Unlock()
336
337 if i.stop {
338 return
339 }
340
341 i.stop = true
342 close(i.stopCh)
343 i.listener.Close()
344
345 // Close the existing connections
346 for _, client := range i.clients {
347 client.conn.Close()
348 }
349 }
350
351 // listen is a long running routine that listens for new clients
352 func (i *AgentIPC) listen() {
353 for {
354 conn, err := i.listener.Accept()
355 if err != nil {
356 if i.stop {
357 return
358 }
359 i.logger.Printf("[ERR] agent.ipc: Failed to accept client: %v", err)
360 continue
361 }
362 i.logger.Printf("[INFO] agent.ipc: Accepted client: %v", conn.RemoteAddr())
363 metrics.IncrCounter([]string{"agent", "ipc", "accept"}, 1)
364
365 // Wrap the connection in a client
366 client := &IPCClient{
367 name: conn.RemoteAddr().String(),
368 conn: conn,
369 reader: bufio.NewReader(conn),
370 writer: bufio.NewWriter(conn),
371 eventStreams: make(map[uint64]*eventStream),
372 pendingQueries: make(map[uint64]*serf.Query),
373 }
374 client.dec = codec.NewDecoder(client.reader,
375 &codec.MsgpackHandle{RawToString: true, WriteExt: true})
376 client.enc = codec.NewEncoder(client.writer,
377 &codec.MsgpackHandle{RawToString: true, WriteExt: true})
378 if err != nil {
379 i.logger.Printf("[ERR] agent.ipc: Failed to create decoder: %v", err)
380 conn.Close()
381 continue
382 }
383
384 // Register the client
385 i.Lock()
386 if !i.stop {
387 i.clients[client.name] = client
388 go i.handleClient(client)
389 } else {
390 conn.Close()
391 }
392 i.Unlock()
393 }
394 }
395
396 // deregisterClient is called to cleanup after a client disconnects
397 func (i *AgentIPC) deregisterClient(client *IPCClient) {
398 // Close the socket
399 client.conn.Close()
400
401 // Remove from the clients list
402 i.Lock()
403 delete(i.clients, client.name)
404 i.Unlock()
405
406 // Remove from the log writer
407 if client.logStreamer != nil {
408 i.logWriter.DeregisterHandler(client.logStreamer)
409 client.logStreamer.Stop()
410 }
411
412 // Remove from event handlers
413 for _, es := range client.eventStreams {
414 i.agent.DeregisterEventHandler(es)
415 es.Stop()
416 }
417 }
418
419 // handleClient is a long running routine that handles a single client
420 func (i *AgentIPC) handleClient(client *IPCClient) {
421 defer i.deregisterClient(client)
422 var reqHeader requestHeader
423 for {
424 // Decode the header
425 if err := client.dec.Decode(&reqHeader); err != nil {
426 if !i.stop {
427 // The second part of this if is to block socket
428 // errors from Windows which appear to happen every
429 // time there is an EOF.
430 if err != io.EOF && !strings.Contains(err.Error(), "WSARecv") {
431 i.logger.Printf("[ERR] agent.ipc: failed to decode request header: %v", err)
432 }
433 }
434 return
435 }
436
437 // Evaluate the command
438 if err := i.handleRequest(client, &reqHeader); err != nil {
439 i.logger.Printf("[ERR] agent.ipc: Failed to evaluate request: %v", err)
440 return
441 }
442 }
443 }
444
445 // handleRequest is used to evaluate a single client command
446 func (i *AgentIPC) handleRequest(client *IPCClient, reqHeader *requestHeader) error {
447 // Look for a command field
448 command := reqHeader.Command
449 seq := reqHeader.Seq
450
451 // Ensure the handshake is performed before other commands
452 if command != handshakeCommand && client.version == 0 {
453 respHeader := responseHeader{Seq: seq, Error: handshakeRequired}
454 client.Send(&respHeader, nil)
455 return fmt.Errorf(handshakeRequired)
456 }
457 metrics.IncrCounter([]string{"agent", "ipc", "command"}, 1)
458
459 // Ensure the client has authenticated after the handshake if necessary
460 if i.authKey != "" && !client.didAuth && command != authCommand && command != handshakeCommand {
461 i.logger.Printf("[WARN] agent.ipc: Client sending commands before auth")
462 respHeader := responseHeader{Seq: seq, Error: authRequired}
463 client.Send(&respHeader, nil)
464 return nil
465 }
466
467 // Dispatch command specific handlers
468 switch command {
469 case handshakeCommand:
470 return i.handleHandshake(client, seq)
471
472 case authCommand:
473 return i.handleAuth(client, seq)
474
475 case eventCommand:
476 return i.handleEvent(client, seq)
477
478 case membersCommand, membersFilteredCommand:
479 return i.handleMembers(client, command, seq)
480
481 case streamCommand:
482 return i.handleStream(client, seq)
483
484 case monitorCommand:
485 return i.handleMonitor(client, seq)
486
487 case stopCommand:
488 return i.handleStop(client, seq)
489
490 case forceLeaveCommand:
491 return i.handleForceLeave(client, seq)
492
493 case joinCommand:
494 return i.handleJoin(client, seq)
495
496 case leaveCommand:
497 return i.handleLeave(client, seq)
498
499 case installKeyCommand:
500 return i.handleInstallKey(client, seq)
501
502 case useKeyCommand:
503 return i.handleUseKey(client, seq)
504
505 case removeKeyCommand:
506 return i.handleRemoveKey(client, seq)
507
508 case listKeysCommand:
509 return i.handleListKeys(client, seq)
510
511 case tagsCommand:
512 return i.handleTags(client, seq)
513
514 case queryCommand:
515 return i.handleQuery(client, seq)
516
517 case respondCommand:
518 return i.handleRespond(client, seq)
519
520 case statsCommand:
521 return i.handleStats(client, seq)
522
523 default:
524 respHeader := responseHeader{Seq: seq, Error: unsupportedCommand}
525 client.Send(&respHeader, nil)
526 return fmt.Errorf("command '%s' not recognized", command)
527 }
528 }
529
530 func (i *AgentIPC) handleHandshake(client *IPCClient, seq uint64) error {
531 var req handshakeRequest
532 if err := client.dec.Decode(&req); err != nil {
533 return fmt.Errorf("decode failed: %v", err)
534 }
535
536 resp := responseHeader{
537 Seq: seq,
538 Error: "",
539 }
540
541 // Check the version
542 if req.Version < MinIPCVersion || req.Version > MaxIPCVersion {
543 resp.Error = unsupportedIPCVersion
544 } else if client.version != 0 {
545 resp.Error = duplicateHandshake
546 } else {
547 client.version = req.Version
548 }
549 return client.Send(&resp, nil)
550 }
551
552 func (i *AgentIPC) handleAuth(client *IPCClient, seq uint64) error {
553 var req authRequest
554 if err := client.dec.Decode(&req); err != nil {
555 return fmt.Errorf("decode failed: %v", err)
556 }
557
558 resp := responseHeader{
559 Seq: seq,
560 Error: "",
561 }
562
563 // Check the token matches
564 if req.AuthKey == i.authKey {
565 client.didAuth = true
566 } else {
567 resp.Error = invalidAuthToken
568 }
569 return client.Send(&resp, nil)
570 }
571
572 func (i *AgentIPC) handleEvent(client *IPCClient, seq uint64) error {
573 var req eventRequest
574 if err := client.dec.Decode(&req); err != nil {
575 return fmt.Errorf("decode failed: %v", err)
576 }
577
578 // Attempt the send
579 err := i.agent.UserEvent(req.Name, req.Payload, req.Coalesce)
580
581 // Respond
582 resp := responseHeader{
583 Seq: seq,
584 Error: errToString(err),
585 }
586 return client.Send(&resp, nil)
587 }
588
589 func (i *AgentIPC) handleForceLeave(client *IPCClient, seq uint64) error {
590 var req forceLeaveRequest
591 if err := client.dec.Decode(&req); err != nil {
592 return fmt.Errorf("decode failed: %v", err)
593 }
594
595 // Attempt leave
596 err := i.agent.ForceLeave(req.Node)
597
598 // Respond
599 resp := responseHeader{
600 Seq: seq,
601 Error: errToString(err),
602 }
603 return client.Send(&resp, nil)
604 }
605
606 func (i *AgentIPC) handleJoin(client *IPCClient, seq uint64) error {
607 var req joinRequest
608 if err := client.dec.Decode(&req); err != nil {
609 return fmt.Errorf("decode failed: %v", err)
610 }
611
612 // Attempt the join
613 num, err := i.agent.Join(req.Existing, req.Replay)
614
615 // Respond
616 header := responseHeader{
617 Seq: seq,
618 Error: errToString(err),
619 }
620 resp := joinResponse{
621 Num: int32(num),
622 }
623 return client.Send(&header, &resp)
624 }
625
626 func (i *AgentIPC) handleMembers(client *IPCClient, command string, seq uint64) error {
627 serf := i.agent.Serf()
628 raw := serf.Members()
629 members := make([]Member, 0, len(raw))
630
631 if command == membersFilteredCommand {
632 var req membersFilteredRequest
633 err := client.dec.Decode(&req)
634 if err != nil {
635 return fmt.Errorf("decode failed: %v", err)
636 }
637 raw, err = i.filterMembers(raw, req.Tags, req.Status, req.Name)
638 if err != nil {
639 return err
640 }
641 }
642
643 for _, m := range raw {
644 sm := Member{
645 Name: m.Name,
646 Addr: m.Addr,
647 Port: m.Port,
648 Tags: m.Tags,
649 Status: m.Status.String(),
650 ProtocolMin: m.ProtocolMin,
651 ProtocolMax: m.ProtocolMax,
652 ProtocolCur: m.ProtocolCur,
653 DelegateMin: m.DelegateMin,
654 DelegateMax: m.DelegateMax,
655 DelegateCur: m.DelegateCur,
656 }
657 members = append(members, sm)
658 }
659
660 header := responseHeader{
661 Seq: seq,
662 Error: "",
663 }
664 resp := membersResponse{
665 Members: members,
666 }
667 return client.Send(&header, &resp)
668 }
669
670 func (i *AgentIPC) filterMembers(members []serf.Member, tags map[string]string,
671 status string, name string) ([]serf.Member, error) {
672
673 result := make([]serf.Member, 0, len(members))
674
675 // Pre-compile all the regular expressions
676 tagsRe := make(map[string]*regexp.Regexp)
677 for tag, expr := range tags {
678 re, err := regexp.Compile(fmt.Sprintf("^%s$", expr))
679 if err != nil {
680 return nil, fmt.Errorf("Failed to compile regex: %v", err)
681 }
682 tagsRe[tag] = re
683 }
684
685 statusRe, err := regexp.Compile(fmt.Sprintf("^%s$", status))
686 if err != nil {
687 return nil, fmt.Errorf("Failed to compile regex: %v", err)
688 }
689
690 nameRe, err := regexp.Compile(fmt.Sprintf("^%s$", name))
691 if err != nil {
692 return nil, fmt.Errorf("Failed to compile regex: %v", err)
693 }
694
695 OUTER:
696 for _, m := range members {
697 // Check if tags were passed, and if they match
698 for tag := range tags {
699 if !tagsRe[tag].MatchString(m.Tags[tag]) {
700 continue OUTER
701 }
702 }
703
704 // Check if status matches
705 if status != "" && !statusRe.MatchString(m.Status.String()) {
706 continue
707 }
708
709 // Check if node name matches
710 if name != "" && !nameRe.MatchString(m.Name) {
711 continue
712 }
713
714 // Made it past the filters!
715 result = append(result, m)
716 }
717
718 return result, nil
719 }
720
721 func (i *AgentIPC) handleInstallKey(client *IPCClient, seq uint64) error {
722 var req keyRequest
723 if err := client.dec.Decode(&req); err != nil {
724 return fmt.Errorf("decode failed: %v", err)
725 }
726
727 queryResp, err := i.agent.InstallKey(req.Key)
728
729 header := responseHeader{
730 Seq: seq,
731 Error: errToString(err),
732 }
733 resp := keyResponse{
734 Messages: queryResp.Messages,
735 NumNodes: queryResp.NumNodes,
736 NumErr: queryResp.NumErr,
737 NumResp: queryResp.NumResp,
738 }
739
740 return client.Send(&header, &resp)
741 }
742
743 func (i *AgentIPC) handleUseKey(client *IPCClient, seq uint64) error {
744 var req keyRequest
745 if err := client.dec.Decode(&req); err != nil {
746 return fmt.Errorf("decode failed: %v", err)
747 }
748
749 queryResp, err := i.agent.UseKey(req.Key)
750
751 header := responseHeader{
752 Seq: seq,
753 Error: errToString(err),
754 }
755 resp := keyResponse{
756 Messages: queryResp.Messages,
757 NumNodes: queryResp.NumNodes,
758 NumErr: queryResp.NumErr,
759 NumResp: queryResp.NumResp,
760 }
761
762 return client.Send(&header, &resp)
763 }
764
765 func (i *AgentIPC) handleRemoveKey(client *IPCClient, seq uint64) error {
766 var req keyRequest
767 if err := client.dec.Decode(&req); err != nil {
768 return fmt.Errorf("decode failed: %v", err)
769 }
770
771 queryResp, err := i.agent.RemoveKey(req.Key)
772
773 header := responseHeader{
774 Seq: seq,
775 Error: errToString(err),
776 }
777 resp := keyResponse{
778 Messages: queryResp.Messages,
779 NumNodes: queryResp.NumNodes,
780 NumErr: queryResp.NumErr,
781 NumResp: queryResp.NumResp,
782 }
783
784 return client.Send(&header, &resp)
785 }
786
787 func (i *AgentIPC) handleListKeys(client *IPCClient, seq uint64) error {
788 queryResp, err := i.agent.ListKeys()
789
790 header := responseHeader{
791 Seq: seq,
792 Error: errToString(err),
793 }
794 resp := keyResponse{
795 Messages: queryResp.Messages,
796 Keys: queryResp.Keys,
797 NumNodes: queryResp.NumNodes,
798 NumErr: queryResp.NumErr,
799 NumResp: queryResp.NumResp,
800 }
801
802 return client.Send(&header, &resp)
803 }
804
805 func (i *AgentIPC) handleStream(client *IPCClient, seq uint64) error {
806 var es *eventStream
807 var req streamRequest
808 if err := client.dec.Decode(&req); err != nil {
809 return fmt.Errorf("decode failed: %v", err)
810 }
811
812 resp := responseHeader{
813 Seq: seq,
814 Error: "",
815 }
816
817 // Create the event filters
818 filters := ParseEventFilter(req.Type)
819 for _, f := range filters {
820 if !f.Valid() {
821 resp.Error = invalidFilter
822 goto SEND
823 }
824 }
825
826 // Check if there is an existing stream
827 if _, ok := client.eventStreams[seq]; ok {
828 resp.Error = streamExists
829 goto SEND
830 }
831
832 // Create an event streamer
833 es = newEventStream(client, filters, seq, i.logger)
834 client.eventStreams[seq] = es
835
836 // Register with the agent. Defer so that we can respond before
837 // registration, avoids any possible race condition
838 defer i.agent.RegisterEventHandler(es)
839
840 SEND:
841 return client.Send(&resp, nil)
842 }
843
844 func (i *AgentIPC) handleMonitor(client *IPCClient, seq uint64) error {
845 var req monitorRequest
846 if err := client.dec.Decode(&req); err != nil {
847 return fmt.Errorf("decode failed: %v", err)
848 }
849
850 resp := responseHeader{
851 Seq: seq,
852 Error: "",
853 }
854
855 // Upper case the log level
856 req.LogLevel = strings.ToUpper(req.LogLevel)
857
858 // Create a level filter
859 filter := LevelFilter()
860 filter.MinLevel = logutils.LogLevel(req.LogLevel)
861 if !ValidateLevelFilter(filter.MinLevel, filter) {
862 resp.Error = fmt.Sprintf("Unknown log level: %s", filter.MinLevel)
863 goto SEND
864 }
865
866 // Check if there is an existing monitor
867 if client.logStreamer != nil {
868 resp.Error = monitorExists
869 goto SEND
870 }
871
872 // Create a log streamer
873 client.logStreamer = newLogStream(client, filter, seq, i.logger)
874
875 // Register with the log writer. Defer so that we can respond before
876 // registration, avoids any possible race condition
877 defer i.logWriter.RegisterHandler(client.logStreamer)
878
879 SEND:
880 return client.Send(&resp, nil)
881 }
882
883 func (i *AgentIPC) handleStop(client *IPCClient, seq uint64) error {
884 var req stopRequest
885 if err := client.dec.Decode(&req); err != nil {
886 return fmt.Errorf("decode failed: %v", err)
887 }
888
889 // Remove a log monitor if any
890 if client.logStreamer != nil && client.logStreamer.seq == req.Stop {
891 i.logWriter.DeregisterHandler(client.logStreamer)
892 client.logStreamer.Stop()
893 client.logStreamer = nil
894 }
895
896 // Remove an event stream if any
897 if es, ok := client.eventStreams[req.Stop]; ok {
898 i.agent.DeregisterEventHandler(es)
899 es.Stop()
900 delete(client.eventStreams, req.Stop)
901 }
902
903 // Always succeed
904 resp := responseHeader{Seq: seq, Error: ""}
905 return client.Send(&resp, nil)
906 }
907
908 func (i *AgentIPC) handleLeave(client *IPCClient, seq uint64) error {
909 i.logger.Printf("[INFO] agent.ipc: Graceful leave triggered")
910
911 // Do the leave
912 err := i.agent.Leave()
913 if err != nil {
914 i.logger.Printf("[ERR] agent.ipc: leave failed: %v", err)
915 }
916 resp := responseHeader{Seq: seq, Error: errToString(err)}
917
918 // Send and wait
919 err = client.Send(&resp, nil)
920
921 // Trigger a shutdown!
922 if err := i.agent.Shutdown(); err != nil {
923 i.logger.Printf("[ERR] agent.ipc: shutdown failed: %v", err)
924 }
925 return err
926 }
927
928 func (i *AgentIPC) handleTags(client *IPCClient, seq uint64) error {
929 var req tagsRequest
930 if err := client.dec.Decode(&req); err != nil {
931 return fmt.Errorf("decode failed: %v", err)
932 }
933
934 tags := make(map[string]string)
935
936 for key, val := range i.agent.SerfConfig().Tags {
937 var delTag bool
938 for _, delkey := range req.DeleteTags {
939 delTag = (delTag || delkey == key)
940 }
941 if !delTag {
942 tags[key] = val
943 }
944 }
945
946 for key, val := range req.Tags {
947 tags[key] = val
948 }
949
950 err := i.agent.SetTags(tags)
951
952 resp := responseHeader{Seq: seq, Error: errToString(err)}
953 return client.Send(&resp, nil)
954 }
955
956 func (i *AgentIPC) handleQuery(client *IPCClient, seq uint64) error {
957 var req queryRequest
958 if err := client.dec.Decode(&req); err != nil {
959 return fmt.Errorf("decode failed: %v", err)
960 }
961
962 // Setup the query
963 params := serf.QueryParam{
964 FilterNodes: req.FilterNodes,
965 FilterTags: req.FilterTags,
966 RequestAck: req.RequestAck,
967 Timeout: req.Timeout,
968 }
969
970 // Start the query
971 queryResp, err := i.agent.Query(req.Name, req.Payload, &params)
972
973 // Stream the query responses
974 if err == nil {
975 qs := newQueryResponseStream(client, seq, i.logger)
976 defer func() {
977 go qs.Stream(queryResp)
978 }()
979 }
980
981 // Respond
982 resp := responseHeader{
983 Seq: seq,
984 Error: errToString(err),
985 }
986 return client.Send(&resp, nil)
987 }
988
989 func (i *AgentIPC) handleRespond(client *IPCClient, seq uint64) error {
990 var req respondRequest
991 if err := client.dec.Decode(&req); err != nil {
992 return fmt.Errorf("decode failed: %v", err)
993 }
994
995 // Lookup the query
996 client.queryLock.Lock()
997 query, ok := client.pendingQueries[req.ID]
998 client.queryLock.Unlock()
999
1000 // Respond if we have a pending query
1001 var err error
1002 if ok {
1003 err = query.Respond(req.Payload)
1004 } else {
1005 err = fmt.Errorf(invalidQueryID)
1006 }
1007
1008 // Respond
1009 resp := responseHeader{
1010 Seq: seq,
1011 Error: errToString(err),
1012 }
1013 return client.Send(&resp, nil)
1014 }
1015
1016 // handleStats is used to get various statistics
1017 func (i *AgentIPC) handleStats(client *IPCClient, seq uint64) error {
1018 header := responseHeader{
1019 Seq: seq,
1020 Error: "",
1021 }
1022 resp := i.agent.Stats()
1023 return client.Send(&header, resp)
1024 }
1025
1026 // Used to convert an error to a string representation
1027 func errToString(err error) string {
1028 if err == nil {
1029 return ""
1030 }
1031 return err.Error()
1032 }
0 package agent
1
2 import (
3 "fmt"
4 "github.com/hashicorp/serf/serf"
5 "log"
6 )
7
8 type streamClient interface {
9 Send(*responseHeader, interface{}) error
10 RegisterQuery(*serf.Query) uint64
11 }
12
13 // eventStream is used to stream events to a client over IPC
14 type eventStream struct {
15 client streamClient
16 eventCh chan serf.Event
17 filters []EventFilter
18 logger *log.Logger
19 seq uint64
20 }
21
22 func newEventStream(client streamClient, filters []EventFilter, seq uint64, logger *log.Logger) *eventStream {
23 es := &eventStream{
24 client: client,
25 eventCh: make(chan serf.Event, 512),
26 filters: filters,
27 logger: logger,
28 seq: seq,
29 }
30 go es.stream()
31 return es
32 }
33
34 func (es *eventStream) HandleEvent(e serf.Event) {
35 // Check the event
36 for _, f := range es.filters {
37 if f.Invoke(e) {
38 goto HANDLE
39 }
40 }
41 return
42
43 // Do a non-blocking send
44 HANDLE:
45 select {
46 case es.eventCh <- e:
47 default:
48 es.logger.Printf("[WARN] agent.ipc: Dropping event to %v", es.client)
49 }
50 }
51
52 func (es *eventStream) Stop() {
53 close(es.eventCh)
54 }
55
56 func (es *eventStream) stream() {
57 var err error
58 for event := range es.eventCh {
59 switch e := event.(type) {
60 case serf.MemberEvent:
61 err = es.sendMemberEvent(e)
62 case serf.UserEvent:
63 err = es.sendUserEvent(e)
64 case *serf.Query:
65 err = es.sendQuery(e)
66 default:
67 err = fmt.Errorf("Unknown event type: %s", event.EventType().String())
68 }
69 if err != nil {
70 es.logger.Printf("[ERR] agent.ipc: Failed to stream event to %v: %v",
71 es.client, err)
72 return
73 }
74 }
75 }
76
77 // sendMemberEvent is used to send a single member event
78 func (es *eventStream) sendMemberEvent(me serf.MemberEvent) error {
79 members := make([]Member, 0, len(me.Members))
80 for _, m := range me.Members {
81 sm := Member{
82 Name: m.Name,
83 Addr: m.Addr,
84 Port: m.Port,
85 Tags: m.Tags,
86 Status: m.Status.String(),
87 ProtocolMin: m.ProtocolMin,
88 ProtocolMax: m.ProtocolMax,
89 ProtocolCur: m.ProtocolCur,
90 DelegateMin: m.DelegateMin,
91 DelegateMax: m.DelegateMax,
92 DelegateCur: m.DelegateCur,
93 }
94 members = append(members, sm)
95 }
96
97 header := responseHeader{
98 Seq: es.seq,
99 Error: "",
100 }
101 rec := memberEventRecord{
102 Event: me.String(),
103 Members: members,
104 }
105 return es.client.Send(&header, &rec)
106 }
107
108 // sendUserEvent is used to send a single user event
109 func (es *eventStream) sendUserEvent(ue serf.UserEvent) error {
110 header := responseHeader{
111 Seq: es.seq,
112 Error: "",
113 }
114 rec := userEventRecord{
115 Event: ue.EventType().String(),
116 LTime: ue.LTime,
117 Name: ue.Name,
118 Payload: ue.Payload,
119 Coalesce: ue.Coalesce,
120 }
121 return es.client.Send(&header, &rec)
122 }
123
124 // sendQuery is used to send a single query event
125 func (es *eventStream) sendQuery(q *serf.Query) error {
126 id := es.client.RegisterQuery(q)
127
128 header := responseHeader{
129 Seq: es.seq,
130 Error: "",
131 }
132 rec := queryEventRecord{
133 Event: q.EventType().String(),
134 ID: id,
135 LTime: q.LTime,
136 Name: q.Name,
137 Payload: q.Payload,
138 }
139 return es.client.Send(&header, &rec)
140 }
0 package agent
1
2 import (
3 "bytes"
4 "github.com/hashicorp/serf/serf"
5 "log"
6 "net"
7 "os"
8 "testing"
9 "time"
10 )
11
12 type MockStreamClient struct {
13 headers []*responseHeader
14 objs []interface{}
15 err error
16 }
17
18 func (m *MockStreamClient) Send(h *responseHeader, o interface{}) error {
19 m.headers = append(m.headers, h)
20 m.objs = append(m.objs, o)
21 return m.err
22 }
23
24 func (m *MockStreamClient) RegisterQuery(q *serf.Query) uint64 {
25 return 42
26 }
27
28 func TestIPCEventStream(t *testing.T) {
29 sc := &MockStreamClient{}
30 filters := ParseEventFilter("user:foobar,member-join,query:deploy")
31 es := newEventStream(sc, filters, 42, log.New(os.Stderr, "", log.LstdFlags))
32 defer es.Stop()
33
34 es.HandleEvent(serf.UserEvent{
35 LTime: 123,
36 Name: "foobar",
37 Payload: []byte("test"),
38 Coalesce: true,
39 })
40 es.HandleEvent(serf.UserEvent{
41 LTime: 124,
42 Name: "ignore",
43 Payload: []byte("test"),
44 Coalesce: true,
45 })
46 es.HandleEvent(serf.MemberEvent{
47 Type: serf.EventMemberJoin,
48 Members: []serf.Member{
49 serf.Member{
50 Name: "TestNode",
51 Addr: net.IP([]byte{127, 0, 0, 1}),
52 Port: 12345,
53 Tags: map[string]string{"role": "node"},
54 Status: serf.StatusAlive,
55 ProtocolMin: 0,
56 ProtocolMax: 0,
57 ProtocolCur: 0,
58 DelegateMin: 0,
59 DelegateMax: 0,
60 DelegateCur: 0,
61 },
62 },
63 })
64 es.HandleEvent(&serf.Query{
65 LTime: 125,
66 Name: "deploy",
67 Payload: []byte("test"),
68 })
69
70 time.Sleep(5 * time.Millisecond)
71
72 if len(sc.headers) != 3 {
73 t.Fatalf("expected 2 messages!")
74 }
75 for _, h := range sc.headers {
76 if h.Seq != 42 {
77 t.Fatalf("bad seq")
78 }
79 if h.Error != "" {
80 t.Fatalf("bad err")
81 }
82 }
83
84 obj1 := sc.objs[0].(*userEventRecord)
85 if obj1.Event != "user" {
86 t.Fatalf("bad event: %#v", obj1)
87 }
88 if obj1.LTime != 123 {
89 t.Fatalf("bad event: %#v", obj1)
90 }
91 if obj1.Name != "foobar" {
92 t.Fatalf("bad event: %#v", obj1)
93 }
94 if bytes.Compare(obj1.Payload, []byte("test")) != 0 {
95 t.Fatalf("bad event: %#v", obj1)
96 }
97 if !obj1.Coalesce {
98 t.Fatalf("bad event: %#v", obj1)
99 }
100
101 obj2 := sc.objs[1].(*memberEventRecord)
102 if obj2.Event != "member-join" {
103 t.Fatalf("bad event: %#v", obj2)
104 }
105 mem1 := obj2.Members[0]
106 if mem1.Name != "TestNode" {
107 t.Fatalf("bad member: %#v", mem1)
108 }
109 if bytes.Compare(mem1.Addr, []byte{127, 0, 0, 1}) != 0 {
110 t.Fatalf("bad member: %#v", mem1)
111 }
112 if mem1.Port != 12345 {
113 t.Fatalf("bad member: %#v", mem1)
114 }
115 if mem1.Status != "alive" {
116 t.Fatalf("bad member: %#v", mem1)
117 }
118 if mem1.ProtocolMin != 0 {
119 t.Fatalf("bad member: %#v", mem1)
120 }
121 if mem1.ProtocolMax != 0 {
122 t.Fatalf("bad member: %#v", mem1)
123 }
124 if mem1.ProtocolCur != 0 {
125 t.Fatalf("bad member: %#v", mem1)
126 }
127 if mem1.DelegateMin != 0 {
128 t.Fatalf("bad member: %#v", mem1)
129 }
130 if mem1.DelegateMax != 0 {
131 t.Fatalf("bad member: %#v", mem1)
132 }
133 if mem1.DelegateCur != 0 {
134 t.Fatalf("bad member: %#v", mem1)
135 }
136
137 obj3 := sc.objs[2].(*queryEventRecord)
138 if obj3.Event != "query" {
139 t.Fatalf("bad query: %#v", obj3)
140 }
141 if obj3.ID != 42 {
142 t.Fatalf("bad query: %#v", obj3)
143 }
144 if obj3.LTime != 125 {
145 t.Fatalf("bad query: %#v", obj3)
146 }
147 if obj3.Name != "deploy" {
148 t.Fatalf("bad query: %#v", obj3)
149 }
150 if bytes.Compare(obj3.Payload, []byte("test")) != 0 {
151 t.Fatalf("bad query: %#v", obj3)
152 }
153
154 }
0 package agent
1
2 import (
3 "github.com/hashicorp/logutils"
4 "log"
5 )
6
7 // logStream is used to stream logs to a client over IPC
8 type logStream struct {
9 client streamClient
10 filter *logutils.LevelFilter
11 logCh chan string
12 logger *log.Logger
13 seq uint64
14 }
15
16 func newLogStream(client streamClient, filter *logutils.LevelFilter,
17 seq uint64, logger *log.Logger) *logStream {
18 ls := &logStream{
19 client: client,
20 filter: filter,
21 logCh: make(chan string, 512),
22 logger: logger,
23 seq: seq,
24 }
25 go ls.stream()
26 return ls
27 }
28
29 func (ls *logStream) HandleLog(l string) {
30 // Check the log level
31 if !ls.filter.Check([]byte(l)) {
32 return
33 }
34
35 // Do a non-blocking send
36 select {
37 case ls.logCh <- l:
38 default:
39 // We can't log syncronously, since we are already being invoked
40 // from the logWriter, and a log will need to invoke Write() which
41 // already holds the lock. We must therefor do the log async, so
42 // as to not deadlock
43 go ls.logger.Printf("[WARN] agent.ipc: Dropping logs to %v", ls.client)
44 }
45 }
46
47 func (ls *logStream) Stop() {
48 close(ls.logCh)
49 }
50
51 func (ls *logStream) stream() {
52 header := responseHeader{Seq: ls.seq, Error: ""}
53 rec := logRecord{Log: ""}
54
55 for line := range ls.logCh {
56 rec.Log = line
57 if err := ls.client.Send(&header, &rec); err != nil {
58 ls.logger.Printf("[ERR] agent.ipc: Failed to stream log to %v: %v",
59 ls.client, err)
60 return
61 }
62 }
63 }
0 package agent
1
2 import (
3 "github.com/hashicorp/logutils"
4 "log"
5 "os"
6 "testing"
7 "time"
8 )
9
10 func TestIPCLogStream(t *testing.T) {
11 sc := &MockStreamClient{}
12 filter := LevelFilter()
13 filter.MinLevel = logutils.LogLevel("INFO")
14
15 ls := newLogStream(sc, filter, 42, log.New(os.Stderr, "", log.LstdFlags))
16 defer ls.Stop()
17
18 log := "[DEBUG] this is a test log"
19 log2 := "[INFO] This should pass"
20 ls.HandleLog(log)
21 ls.HandleLog(log2)
22
23 time.Sleep(5 * time.Millisecond)
24
25 if len(sc.headers) != 1 {
26 t.Fatalf("expected 1 messages!")
27 }
28 for _, h := range sc.headers {
29 if h.Seq != 42 {
30 t.Fatalf("bad seq")
31 }
32 if h.Error != "" {
33 t.Fatalf("bad err")
34 }
35 }
36
37 obj1 := sc.objs[0].(*logRecord)
38 if obj1.Log != log2 {
39 t.Fatalf("bad event %#v", obj1)
40 }
41 }
0 package agent
1
2 import (
3 "github.com/hashicorp/serf/serf"
4 "log"
5 "time"
6 )
7
8 // queryResponseStream is used to stream the query results back to a client
9 type queryResponseStream struct {
10 client streamClient
11 logger *log.Logger
12 seq uint64
13 }
14
15 func newQueryResponseStream(client streamClient, seq uint64, logger *log.Logger) *queryResponseStream {
16 qs := &queryResponseStream{
17 client: client,
18 logger: logger,
19 seq: seq,
20 }
21 return qs
22 }
23
24 // Stream is a long running routine used to stream the results of a query back to a client
25 func (qs *queryResponseStream) Stream(resp *serf.QueryResponse) {
26 // Setup a timer for the query ending
27 remaining := resp.Deadline().Sub(time.Now())
28 done := time.After(remaining)
29
30 ackCh := resp.AckCh()
31 respCh := resp.ResponseCh()
32 for {
33 select {
34 case a := <-ackCh:
35 if err := qs.sendAck(a); err != nil {
36 qs.logger.Printf("[ERR] agent.ipc: Failed to stream ack to %v: %v", qs.client, err)
37 return
38 }
39 case r := <-respCh:
40 if err := qs.sendResponse(r.From, r.Payload); err != nil {
41 qs.logger.Printf("[ERR] agent.ipc: Failed to stream response to %v: %v", qs.client, err)
42 return
43 }
44 case <-done:
45 if err := qs.sendDone(); err != nil {
46 qs.logger.Printf("[ERR] agent.ipc: Failed to stream query end to %v: %v", qs.client, err)
47 }
48 return
49 }
50 }
51 }
52
53 // sendAck is used to send a single ack
54 func (qs *queryResponseStream) sendAck(from string) error {
55 header := responseHeader{
56 Seq: qs.seq,
57 Error: "",
58 }
59 rec := queryRecord{
60 Type: queryRecordAck,
61 From: from,
62 }
63 return qs.client.Send(&header, &rec)
64 }
65
66 // sendResponse is used to send a single response
67 func (qs *queryResponseStream) sendResponse(from string, payload []byte) error {
68 header := responseHeader{
69 Seq: qs.seq,
70 Error: "",
71 }
72 rec := queryRecord{
73 Type: queryRecordResponse,
74 From: from,
75 Payload: payload,
76 }
77 return qs.client.Send(&header, &rec)
78 }
79
80 // sendDone is used to signal the end
81 func (qs *queryResponseStream) sendDone() error {
82 header := responseHeader{
83 Seq: qs.seq,
84 Error: "",
85 }
86 rec := queryRecord{
87 Type: queryRecordDone,
88 }
89 return qs.client.Send(&header, &rec)
90 }
0 package agent
1
2 import (
3 "github.com/hashicorp/logutils"
4 "io/ioutil"
5 )
6
7 // LevelFilter returns a LevelFilter that is configured with the log
8 // levels that we use.
9 func LevelFilter() *logutils.LevelFilter {
10 return &logutils.LevelFilter{
11 Levels: []logutils.LogLevel{"TRACE", "DEBUG", "INFO", "WARN", "ERR"},
12 MinLevel: "INFO",
13 Writer: ioutil.Discard,
14 }
15 }
16
17 // ValidateLevelFilter verifies that the log levels within the filter
18 // are valid.
19 func ValidateLevelFilter(minLevel logutils.LogLevel, filter *logutils.LevelFilter) bool {
20 for _, level := range filter.Levels {
21 if level == minLevel {
22 return true
23 }
24 }
25 return false
26 }
0 package agent
1
2 import (
3 "sync"
4 )
5
6 // LogHandler interface is used for clients that want to subscribe
7 // to logs, for example to stream them over an IPC mechanism
8 type LogHandler interface {
9 HandleLog(string)
10 }
11
12 // logWriter implements io.Writer so it can be used as a log sink.
13 // It maintains a circular buffer of logs, and a set of handlers to
14 // which it can stream the logs to.
15 type logWriter struct {
16 sync.Mutex
17 logs []string
18 index int
19 handlers map[LogHandler]struct{}
20 }
21
22 // NewLogWriter creates a logWriter with the given buffer capacity
23 func NewLogWriter(buf int) *logWriter {
24 return &logWriter{
25 logs: make([]string, buf),
26 index: 0,
27 handlers: make(map[LogHandler]struct{}),
28 }
29 }
30
31 // RegisterHandler adds a log handler to receive logs, and sends
32 // the last buffered logs to the handler
33 func (l *logWriter) RegisterHandler(lh LogHandler) {
34 l.Lock()
35 defer l.Unlock()
36
37 // Do nothing if already registered
38 if _, ok := l.handlers[lh]; ok {
39 return
40 }
41
42 // Register
43 l.handlers[lh] = struct{}{}
44
45 // Send the old logs
46 if l.logs[l.index] != "" {
47 for i := l.index; i < len(l.logs); i++ {
48 lh.HandleLog(l.logs[i])
49 }
50 }
51 for i := 0; i < l.index; i++ {
52 lh.HandleLog(l.logs[i])
53 }
54 }
55
56 // DeregisterHandler removes a LogHandler and prevents more invocations
57 func (l *logWriter) DeregisterHandler(lh LogHandler) {
58 l.Lock()
59 defer l.Unlock()
60 delete(l.handlers, lh)
61 }
62
63 // Write is used to accumulate new logs
64 func (l *logWriter) Write(p []byte) (n int, err error) {
65 l.Lock()
66 defer l.Unlock()
67
68 // Strip off newlines at the end if there are any since we store
69 // individual log lines in the agent.
70 n = len(p)
71 if p[n-1] == '\n' {
72 p = p[:n-1]
73 }
74
75 l.logs[l.index] = string(p)
76 l.index = (l.index + 1) % len(l.logs)
77
78 for lh, _ := range l.handlers {
79 lh.HandleLog(string(p))
80 }
81 return
82 }
0 package agent
1
2 import (
3 "testing"
4 )
5
6 type MockLogHandler struct {
7 logs []string
8 }
9
10 func (m *MockLogHandler) HandleLog(l string) {
11 m.logs = append(m.logs, l)
12 }
13
14 func TestLogWriter(t *testing.T) {
15 h := &MockLogHandler{}
16 w := NewLogWriter(4)
17
18 // Write some logs
19 w.Write([]byte("one")) // Gets dropped!
20 w.Write([]byte("two"))
21 w.Write([]byte("three"))
22 w.Write([]byte("four"))
23 w.Write([]byte("five"))
24
25 // Register a handler, sends old!
26 w.RegisterHandler(h)
27
28 w.Write([]byte("six"))
29 w.Write([]byte("seven"))
30
31 // Deregister
32 w.DeregisterHandler(h)
33
34 w.Write([]byte("eight"))
35 w.Write([]byte("nine"))
36
37 out := []string{
38 "two",
39 "three",
40 "four",
41 "five",
42 "six",
43 "seven",
44 }
45 for idx := range out {
46 if out[idx] != h.logs[idx] {
47 t.Fatalf("mismatch %v", h.logs)
48 }
49 }
50 }
0 package agent
1
2 import (
3 "fmt"
4 "io"
5 "log"
6 "net"
7 "time"
8
9 "github.com/hashicorp/mdns"
10 )
11
12 const (
13 mdnsPollInterval = 60 * time.Second
14 mdnsQuietInterval = 100 * time.Millisecond
15 )
16
17 // AgentMDNS is used to advertise ourself using mDNS and to
18 // attempt to join peers periodically using mDNS queries.
19 type AgentMDNS struct {
20 agent *Agent
21 discover string
22 logger *log.Logger
23 seen map[string]struct{}
24 server *mdns.Server
25 replay bool
26 iface *net.Interface
27 }
28
29 // NewAgentMDNS is used to create a new AgentMDNS
30 func NewAgentMDNS(agent *Agent, logOutput io.Writer, replay bool,
31 node, discover string, iface *net.Interface, bind net.IP, port int) (*AgentMDNS, error) {
32 // Create the service
33 service, err := mdns.NewMDNSService(
34 node,
35 mdnsName(discover),
36 "",
37 "",
38 port,
39 []net.IP{bind},
40 []string{fmt.Sprintf("Serf '%s' cluster", discover)})
41 if err != nil {
42 return nil, err
43 }
44
45 // Configure mdns server
46 conf := &mdns.Config{
47 Zone: service,
48 Iface: iface,
49 }
50
51 // Create the server
52 server, err := mdns.NewServer(conf)
53 if err != nil {
54 return nil, err
55 }
56
57 // Initialize the AgentMDNS
58 m := &AgentMDNS{
59 agent: agent,
60 discover: discover,
61 logger: log.New(logOutput, "", log.LstdFlags),
62 seen: make(map[string]struct{}),
63 server: server,
64 replay: replay,
65 iface: iface,
66 }
67
68 // Start the background workers
69 go m.run()
70 return m, nil
71 }
72
73 // run is a long running goroutine that scans for new hosts periodically
74 func (m *AgentMDNS) run() {
75 hosts := make(chan *mdns.ServiceEntry, 32)
76 poll := time.After(0)
77 var quiet <-chan time.Time
78 var join []string
79
80 for {
81 select {
82 case h := <-hosts:
83 // Format the host address
84 addr := net.TCPAddr{IP: h.Addr, Port: h.Port}
85 addrS := addr.String()
86
87 // Skip if we've handled this host already
88 if _, ok := m.seen[addrS]; ok {
89 continue
90 }
91
92 // Queue for handling
93 join = append(join, addrS)
94 quiet = time.After(mdnsQuietInterval)
95
96 case <-quiet:
97 // Attempt the join
98 n, err := m.agent.Join(join, m.replay)
99 if err != nil {
100 m.logger.Printf("[ERR] agent.mdns: Failed to join: %v", err)
101 }
102 if n > 0 {
103 m.logger.Printf("[INFO] agent.mdns: Joined %d hosts", n)
104 }
105
106 // Mark all as seen
107 for _, n := range join {
108 m.seen[n] = struct{}{}
109 }
110 join = nil
111
112 case <-poll:
113 poll = time.After(mdnsPollInterval)
114 go m.poll(hosts)
115 }
116 }
117 }
118
119 // poll is invoked periodically to check for new hosts
120 func (m *AgentMDNS) poll(hosts chan *mdns.ServiceEntry) {
121 params := mdns.QueryParam{
122 Service: mdnsName(m.discover),
123 Interface: m.iface,
124 Entries: hosts,
125 }
126 if err := mdns.Query(&params); err != nil {
127 m.logger.Printf("[ERR] agent.mdns: Failed to poll for new hosts: %v", err)
128 }
129 }
130
131 // mdnsName returns the service name to register and to lookup
132 func mdnsName(discover string) string {
133 return fmt.Sprintf("_serf_%s._tcp", discover)
134 }
0 package agent
1
2 import (
3 "bytes"
4 "encoding/base64"
5 "github.com/hashicorp/serf/client"
6 "github.com/hashicorp/serf/serf"
7 "github.com/hashicorp/serf/testutil"
8 "io"
9 "net"
10 "os"
11 "strings"
12 "testing"
13 "time"
14 )
15
16 func testRPCClient(t *testing.T) (*client.RPCClient, *Agent, *AgentIPC) {
17 agentConf := DefaultConfig()
18 serfConf := serf.DefaultConfig()
19
20 return testRPCClientWithConfig(t, agentConf, serfConf)
21 }
22
23 // testRPCClient returns an RPCClient connected to an RPC server that
24 // serves only this connection.
25 func testRPCClientWithConfig(t *testing.T, agentConf *Config,
26 serfConf *serf.Config) (*client.RPCClient, *Agent, *AgentIPC) {
27
28 l, err := net.Listen("tcp", "127.0.0.1:0")
29 if err != nil {
30 t.Fatalf("err: %s", err)
31 }
32
33 lw := NewLogWriter(512)
34 mult := io.MultiWriter(os.Stderr, lw)
35
36 agent := testAgentWithConfig(agentConf, serfConf, mult)
37 ipc := NewAgentIPC(agent, "", l, mult, lw)
38
39 rpcClient, err := client.NewRPCClient(l.Addr().String())
40 if err != nil {
41 t.Fatalf("err: %s", err)
42 }
43
44 return rpcClient, agent, ipc
45 }
46
47 func findMember(t *testing.T, members []serf.Member, name string) serf.Member {
48 for _, m := range members {
49 if m.Name == name {
50 return m
51 }
52 }
53 t.Fatalf("%s not found", name)
54 return serf.Member{}
55 }
56
57 func TestRPCClientForceLeave(t *testing.T) {
58 client, a1, ipc := testRPCClient(t)
59 a2 := testAgent(nil)
60 defer ipc.Shutdown()
61 defer client.Close()
62 defer a1.Shutdown()
63 defer a2.Shutdown()
64
65 if err := a1.Start(); err != nil {
66 t.Fatalf("err: %s", err)
67 }
68
69 if err := a2.Start(); err != nil {
70 t.Fatalf("err: %s", err)
71 }
72
73 testutil.Yield()
74
75 s2Addr := a2.conf.MemberlistConfig.BindAddr
76 if _, err := a1.Join([]string{s2Addr}, false); err != nil {
77 t.Fatalf("err: %s", err)
78 }
79
80 testutil.Yield()
81
82 if err := a2.Shutdown(); err != nil {
83 t.Fatalf("err: %s", err)
84 }
85
86 start := time.Now()
87 WAIT:
88 time.Sleep(a1.conf.MemberlistConfig.ProbeInterval * 3)
89 m := a1.Serf().Members()
90 if len(m) != 2 {
91 t.Fatalf("should have 2 members: %#v", a1.Serf().Members())
92 }
93 if findMember(t, m, a2.conf.NodeName).Status != serf.StatusFailed && time.Now().Sub(start) < 3*time.Second {
94 goto WAIT
95 }
96
97 if err := client.ForceLeave(a2.conf.NodeName); err != nil {
98 t.Fatalf("err: %s", err)
99 }
100
101 testutil.Yield()
102
103 m = a1.Serf().Members()
104 if len(m) != 2 {
105 t.Fatalf("should have 2 members: %#v", a1.Serf().Members())
106 }
107
108 if findMember(t, m, a2.conf.NodeName).Status != serf.StatusLeft {
109 t.Fatalf("should be left: %#v", m[1])
110 }
111 }
112
113 func TestRPCClientJoin(t *testing.T) {
114 client, a1, ipc := testRPCClient(t)
115 a2 := testAgent(nil)
116 defer ipc.Shutdown()
117 defer client.Close()
118 defer a1.Shutdown()
119 defer a2.Shutdown()
120
121 if err := a1.Start(); err != nil {
122 t.Fatalf("err: %s", err)
123 }
124
125 if err := a2.Start(); err != nil {
126 t.Fatalf("err: %s", err)
127 }
128
129 testutil.Yield()
130
131 n, err := client.Join([]string{a2.conf.MemberlistConfig.BindAddr}, false)
132 if err != nil {
133 t.Fatalf("err: %s", err)
134 }
135
136 if n != 1 {
137 t.Fatalf("n != 1: %d", n)
138 }
139 }
140
141 func TestRPCClientMembers(t *testing.T) {
142 client, a1, ipc := testRPCClient(t)
143 a2 := testAgent(nil)
144 defer ipc.Shutdown()
145 defer client.Close()
146 defer a1.Shutdown()
147 defer a2.Shutdown()
148
149 if err := a1.Start(); err != nil {
150 t.Fatalf("err: %s", err)
151 }
152
153 if err := a2.Start(); err != nil {
154 t.Fatalf("err: %s", err)
155 }
156
157 testutil.Yield()
158
159 mem, err := client.Members()
160 if err != nil {
161 t.Fatalf("err: %s", err)
162 }
163
164 if len(mem) != 1 {
165 t.Fatalf("bad: %#v", mem)
166 }
167
168 _, err = client.Join([]string{a2.conf.MemberlistConfig.BindAddr}, false)
169 if err != nil {
170 t.Fatalf("err: %s", err)
171 }
172
173 testutil.Yield()
174
175 mem, err = client.Members()
176 if err != nil {
177 t.Fatalf("err: %s", err)
178 }
179
180 if len(mem) != 2 {
181 t.Fatalf("bad: %#v", mem)
182 }
183 }
184
185 func TestRPCClientMembersFiltered(t *testing.T) {
186 client, a1, ipc := testRPCClient(t)
187 a2 := testAgent(nil)
188 defer ipc.Shutdown()
189 defer client.Close()
190 defer a1.Shutdown()
191 defer a2.Shutdown()
192
193 if err := a1.Start(); err != nil {
194 t.Fatalf("err: %s", err)
195 }
196
197 if err := a2.Start(); err != nil {
198 t.Fatalf("err: %s", err)
199 }
200
201 testutil.Yield()
202
203 _, err := client.Join([]string{a2.conf.MemberlistConfig.BindAddr}, false)
204 if err != nil {
205 t.Fatalf("err: %s", err)
206 }
207
208 err = client.UpdateTags(map[string]string{
209 "tag1": "val1",
210 "tag2": "val2",
211 }, []string{})
212
213 if err != nil {
214 t.Fatalf("bad: %s", err)
215 }
216
217 testutil.Yield()
218
219 // Make sure that filters work on member names
220 mem, err := client.MembersFiltered(map[string]string{}, "", ".*")
221 if err != nil {
222 t.Fatalf("bad: %s", err)
223 }
224
225 if len(mem) == 0 {
226 t.Fatalf("should have matched more than 0 members")
227 }
228
229 mem, err = client.MembersFiltered(map[string]string{}, "", "bad")
230 if err != nil {
231 t.Fatalf("bad: %s", err)
232 }
233
234 if len(mem) != 0 {
235 t.Fatalf("should have matched 0 members: %#v", mem)
236 }
237
238 // Make sure that filters work on member tags
239 mem, err = client.MembersFiltered(map[string]string{"tag1": "val.*"}, "", "")
240 if err != nil {
241 t.Fatalf("bad: %s", err)
242 }
243
244 if len(mem) != 1 {
245 t.Fatalf("should have matched 1 member: %#v", mem)
246 }
247
248 // Make sure tag filters work on multiple tags
249 mem, err = client.MembersFiltered(map[string]string{
250 "tag1": "val.*",
251 "tag2": "val2",
252 }, "", "")
253
254 if err != nil {
255 t.Fatalf("bad: %s", err)
256 }
257
258 if len(mem) != 1 {
259 t.Fatalf("should have matched one member: %#v", mem)
260 }
261
262 // Make sure all tags match when multiple tags are passed
263 mem, err = client.MembersFiltered(map[string]string{
264 "tag1": "val1",
265 "tag2": "bad",
266 }, "", "")
267
268 if err != nil {
269 t.Fatalf("bad: %s", err)
270 }
271
272 if len(mem) != 0 {
273 t.Fatalf("should have matched 0 members: %#v", mem)
274 }
275
276 // Make sure that filters work on member status
277 if err := client.ForceLeave(a2.conf.NodeName); err != nil {
278 t.Fatalf("bad: %s", err)
279 }
280
281 mem, err = client.MembersFiltered(map[string]string{}, "alive", "")
282 if err != nil {
283 t.Fatalf("err: %s", err)
284 }
285
286 if len(mem) != 1 {
287 t.Fatalf("should have matched 1 member: %#v", mem)
288 }
289
290 mem, err = client.MembersFiltered(map[string]string{}, "leaving", "")
291 if err != nil {
292 t.Fatalf("err: %s", err)
293 }
294
295 if len(mem) != 1 {
296 t.Fatalf("should have matched 1 member: %#v", mem)
297 }
298 }
299
300 func TestRPCClientUserEvent(t *testing.T) {
301 client, a1, ipc := testRPCClient(t)
302 defer ipc.Shutdown()
303 defer client.Close()
304 defer a1.Shutdown()
305
306 handler := new(MockEventHandler)
307 a1.RegisterEventHandler(handler)
308
309 if err := a1.Start(); err != nil {
310 t.Fatalf("err: %s", err)
311 }
312
313 testutil.Yield()
314
315 if err := client.UserEvent("deploy", []byte("foo"), false); err != nil {
316 t.Fatalf("err: %s", err)
317 }
318
319 testutil.Yield()
320
321 handler.Lock()
322 defer handler.Unlock()
323
324 if len(handler.Events) == 0 {
325 t.Fatal("no events")
326 }
327
328 serfEvent, ok := handler.Events[len(handler.Events)-1].(serf.UserEvent)
329 if !ok {
330 t.Fatalf("bad: %#v", serfEvent)
331 }
332
333 if serfEvent.Name != "deploy" {
334 t.Fatalf("bad: %#v", serfEvent)
335 }
336
337 if string(serfEvent.Payload) != "foo" {
338 t.Fatalf("bad: %#v", serfEvent)
339 }
340 }
341
342 func TestRPCClientLeave(t *testing.T) {
343 client, a1, ipc := testRPCClient(t)
344 defer ipc.Shutdown()
345 defer client.Close()
346 defer a1.Shutdown()
347
348 testutil.Yield()
349
350 if err := client.Leave(); err != nil {
351 t.Fatalf("err: %s", err)
352 }
353
354 testutil.Yield()
355
356 select {
357 case <-a1.ShutdownCh():
358 default:
359 t.Fatalf("agent should be shutdown!")
360 }
361 }
362
363 func TestRPCClientMonitor(t *testing.T) {
364 client, a1, ipc := testRPCClient(t)
365 defer ipc.Shutdown()
366 defer client.Close()
367 defer a1.Shutdown()
368
369 if err := a1.Start(); err != nil {
370 t.Fatalf("err: %s", err)
371 }
372
373 eventCh := make(chan string, 64)
374 if handle, err := client.Monitor("debug", eventCh); err != nil {
375 t.Fatalf("err: %s", err)
376 } else {
377 defer client.Stop(handle)
378 }
379
380 testutil.Yield()
381
382 select {
383 case e := <-eventCh:
384 if !strings.Contains(e, "Accepted client") {
385 t.Fatalf("bad: %s", e)
386 }
387 default:
388 t.Fatalf("should have backlog")
389 }
390
391 // Drain the rest of the messages as we know it
392 drainEventCh(eventCh)
393
394 // Join a bad thing to generate more events
395 a1.Join(nil, false)
396
397 testutil.Yield()
398
399 select {
400 case e := <-eventCh:
401 if !strings.Contains(e, "joining") {
402 t.Fatalf("bad: %s", e)
403 }
404 default:
405 t.Fatalf("should have message")
406 }
407 }
408
409 func TestRPCClientStream_User(t *testing.T) {
410 client, a1, ipc := testRPCClient(t)
411 defer ipc.Shutdown()
412 defer client.Close()
413 defer a1.Shutdown()
414
415 if err := a1.Start(); err != nil {
416 t.Fatalf("err: %s", err)
417 }
418
419 eventCh := make(chan map[string]interface{}, 64)
420 if handle, err := client.Stream("user", eventCh); err != nil {
421 t.Fatalf("err: %s", err)
422 } else {
423 defer client.Stop(handle)
424 }
425
426 testutil.Yield()
427
428 if err := client.UserEvent("deploy", []byte("foo"), false); err != nil {
429 t.Fatalf("err: %s", err)
430 }
431
432 testutil.Yield()
433
434 select {
435 case e := <-eventCh:
436 if e["Event"].(string) != "user" {
437 t.Fatalf("bad event: %#v", e)
438 }
439 if e["LTime"].(int64) != 1 {
440 t.Fatalf("bad event: %#v", e)
441 }
442 if e["Name"].(string) != "deploy" {
443 t.Fatalf("bad event: %#v", e)
444 }
445 if bytes.Compare(e["Payload"].([]byte), []byte("foo")) != 0 {
446 t.Fatalf("bad event: %#v", e)
447 }
448 if e["Coalesce"].(bool) != false {
449 t.Fatalf("bad event: %#v", e)
450 }
451
452 default:
453 t.Fatalf("should have event")
454 }
455 }
456
457 func TestRPCClientStream_Member(t *testing.T) {
458 client, a1, ipc := testRPCClient(t)
459 defer ipc.Shutdown()
460 defer client.Close()
461 defer a1.Shutdown()
462 a2 := testAgent(nil)
463 defer a2.Shutdown()
464
465 if err := a1.Start(); err != nil {
466 t.Fatalf("err: %s", err)
467 }
468
469 if err := a2.Start(); err != nil {
470 t.Fatalf("err: %s", err)
471 }
472
473 testutil.Yield()
474
475 eventCh := make(chan map[string]interface{}, 64)
476 if handle, err := client.Stream("*", eventCh); err != nil {
477 t.Fatalf("err: %s", err)
478 } else {
479 defer client.Stop(handle)
480 }
481
482 testutil.Yield()
483
484 s2Addr := a2.conf.MemberlistConfig.BindAddr
485 if _, err := a1.Join([]string{s2Addr}, false); err != nil {
486 t.Fatalf("err: %s", err)
487 }
488
489 testutil.Yield()
490
491 select {
492 case e := <-eventCh:
493 if e["Event"].(string) != "member-join" {
494 t.Fatalf("bad event: %#v", e)
495 }
496
497 members := e["Members"].([]interface{})
498 if len(members) != 1 {
499 t.Fatalf("should have 1 member")
500 }
501 member := members[0].(map[interface{}]interface{})
502
503 if _, ok := member["Name"].(string); !ok {
504 t.Fatalf("bad event: %#v", e)
505 }
506 if _, ok := member["Addr"].([]uint8); !ok {
507 t.Fatalf("bad event: %#v", e)
508 }
509 if _, ok := member["Port"].(uint64); !ok {
510 t.Fatalf("bad event: %#v", e)
511 }
512 if _, ok := member["Tags"].(map[interface{}]interface{}); !ok {
513 t.Fatalf("bad event: %#v", e)
514 }
515 if stat, _ := member["Status"].(string); stat != "alive" {
516 t.Fatalf("bad event: %#v", e)
517 }
518 if _, ok := member["ProtocolMin"].(int64); !ok {
519 t.Fatalf("bad event: %#v", e)
520 }
521 if _, ok := member["ProtocolMax"].(int64); !ok {
522 t.Fatalf("bad event: %#v", e)
523 }
524 if _, ok := member["ProtocolCur"].(int64); !ok {
525 t.Fatalf("bad event: %#v", e)
526 }
527 if _, ok := member["DelegateMin"].(int64); !ok {
528 t.Fatalf("bad event: %#v", e)
529 }
530 if _, ok := member["DelegateMax"].(int64); !ok {
531 t.Fatalf("bad event: %#v", e)
532 }
533 if _, ok := member["DelegateCur"].(int64); !ok {
534 t.Fatalf("bad event: %#v", e)
535 }
536
537 default:
538 t.Fatalf("should have event")
539 }
540 }
541
542 func TestRPCClientUpdateTags(t *testing.T) {
543 client, a1, ipc := testRPCClient(t)
544 defer ipc.Shutdown()
545 defer client.Close()
546 defer a1.Shutdown()
547
548 if err := a1.Start(); err != nil {
549 t.Fatalf("err: %s", err)
550 }
551
552 testutil.Yield()
553
554 mem, err := client.Members()
555 if err != nil {
556 t.Fatalf("err: %s", err)
557 }
558
559 if len(mem) != 1 {
560 t.Fatalf("bad: %#v", mem)
561 }
562
563 m0 := mem[0]
564 if _, ok := m0.Tags["testing"]; ok {
565 t.Fatalf("have testing tag")
566 }
567
568 if err := client.UpdateTags(map[string]string{"testing": "1"}, nil); err != nil {
569 t.Fatalf("err: %s", err)
570 }
571
572 mem, err = client.Members()
573 if err != nil {
574 t.Fatalf("err: %s", err)
575 }
576
577 if len(mem) != 1 {
578 t.Fatalf("bad: %#v", mem)
579 }
580
581 m0 = mem[0]
582 if _, ok := m0.Tags["testing"]; !ok {
583 t.Fatalf("missing testing tag")
584 }
585 }
586
587 func TestRPCClientQuery(t *testing.T) {
588 cl, a1, ipc := testRPCClient(t)
589 defer ipc.Shutdown()
590 defer cl.Close()
591 defer a1.Shutdown()
592
593 handler := new(MockQueryHandler)
594 handler.Response = []byte("ok")
595 a1.RegisterEventHandler(handler)
596
597 if err := a1.Start(); err != nil {
598 t.Fatalf("err: %s", err)
599 }
600
601 testutil.Yield()
602
603 ackCh := make(chan string, 1)
604 respCh := make(chan client.NodeResponse, 1)
605 params := client.QueryParam{
606 RequestAck: true,
607 Timeout: 200 * time.Millisecond,
608 Name: "deploy",
609 Payload: []byte("foo"),
610 AckCh: ackCh,
611 RespCh: respCh,
612 }
613 if err := cl.Query(&params); err != nil {
614 t.Fatalf("err: %s", err)
615 }
616
617 testutil.Yield()
618
619 handler.Lock()
620 defer handler.Unlock()
621
622 if len(handler.Queries) == 0 {
623 t.Fatal("no queries")
624 }
625
626 query := handler.Queries[0]
627 if query.Name != "deploy" {
628 t.Fatalf("bad: %#v", query)
629 }
630
631 if string(query.Payload) != "foo" {
632 t.Fatalf("bad: %#v", query)
633 }
634
635 select {
636 case a := <-ackCh:
637 if a != a1.conf.NodeName {
638 t.Fatalf("Bad ack from: %v", a)
639 }
640 default:
641 t.Fatalf("missing ack")
642 }
643
644 select {
645 case r := <-respCh:
646 if r.From != a1.conf.NodeName {
647 t.Fatalf("Bad resp from: %v", r)
648 }
649 if string(r.Payload) != "ok" {
650 t.Fatalf("Bad resp from: %v", r)
651 }
652 default:
653 t.Fatalf("missing response")
654 }
655 }
656
657 func TestRPCClientStream_Query(t *testing.T) {
658 cl, a1, ipc := testRPCClient(t)
659 defer ipc.Shutdown()
660 defer cl.Close()
661 defer a1.Shutdown()
662
663 if err := a1.Start(); err != nil {
664 t.Fatalf("err: %s", err)
665 }
666
667 eventCh := make(chan map[string]interface{}, 64)
668 if handle, err := cl.Stream("query", eventCh); err != nil {
669 t.Fatalf("err: %s", err)
670 } else {
671 defer cl.Stop(handle)
672 }
673
674 testutil.Yield()
675
676 params := client.QueryParam{
677 Timeout: 200 * time.Millisecond,
678 Name: "deploy",
679 Payload: []byte("foo"),
680 }
681 if err := cl.Query(&params); err != nil {
682 t.Fatalf("err: %s", err)
683 }
684
685 testutil.Yield()
686
687 select {
688 case e := <-eventCh:
689 if e["Event"].(string) != "query" {
690 t.Fatalf("bad query: %#v", e)
691 }
692 if e["ID"].(int64) != 1 {
693 t.Fatalf("bad query: %#v", e)
694 }
695 if e["LTime"].(int64) != 1 {
696 t.Fatalf("bad query: %#v", e)
697 }
698 if e["Name"].(string) != "deploy" {
699 t.Fatalf("bad query: %#v", e)
700 }
701 if bytes.Compare(e["Payload"].([]byte), []byte("foo")) != 0 {
702 t.Fatalf("bad query: %#v", e)
703 }
704
705 default:
706 t.Fatalf("should have query")
707 }
708 }
709
710 func TestRPCClientStream_Query_Respond(t *testing.T) {
711 cl, a1, ipc := testRPCClient(t)
712 defer ipc.Shutdown()
713 defer cl.Close()
714 defer a1.Shutdown()
715
716 if err := a1.Start(); err != nil {
717 t.Fatalf("err: %s", err)
718 }
719
720 eventCh := make(chan map[string]interface{}, 64)
721 if handle, err := cl.Stream("query", eventCh); err != nil {
722 t.Fatalf("err: %s", err)
723 } else {
724 defer cl.Stop(handle)
725 }
726
727 testutil.Yield()
728
729 ackCh := make(chan string, 1)
730 respCh := make(chan client.NodeResponse, 1)
731 params := client.QueryParam{
732 RequestAck: true,
733 Timeout: 500 * time.Millisecond,
734 Name: "ping",
735 AckCh: ackCh,
736 RespCh: respCh,
737 }
738 if err := cl.Query(&params); err != nil {
739 t.Fatalf("err: %s", err)
740 }
741
742 testutil.Yield()
743
744 select {
745 case e := <-eventCh:
746 if e["Event"].(string) != "query" {
747 t.Fatalf("bad query: %#v", e)
748 }
749 if e["Name"].(string) != "ping" {
750 t.Fatalf("bad query: %#v", e)
751 }
752
753 // Send a response
754 id := e["ID"].(int64)
755 if err := cl.Respond(uint64(id), []byte("pong")); err != nil {
756 t.Fatalf("err: %v", err)
757 }
758
759 default:
760 t.Fatalf("should have query")
761 }
762
763 testutil.Yield()
764
765 // Should have ack
766 select {
767 case a := <-ackCh:
768 if a != a1.conf.NodeName {
769 t.Fatalf("Bad ack from: %v", a)
770 }
771 default:
772 t.Fatalf("missing ack")
773 }
774
775 // Should have response
776 select {
777 case r := <-respCh:
778 if r.From != a1.conf.NodeName {
779 t.Fatalf("Bad resp from: %v", r)
780 }
781 if string(r.Payload) != "pong" {
782 t.Fatalf("Bad resp from: %v", r)
783 }
784 default:
785 t.Fatalf("missing response")
786 }
787 }
788
789 func TestRPCClientAuth(t *testing.T) {
790 cl, a1, ipc := testRPCClient(t)
791 defer ipc.Shutdown()
792 defer cl.Close()
793 defer a1.Shutdown()
794
795 // Setup an auth key
796 ipc.authKey = "foobar"
797
798 if err := a1.Start(); err != nil {
799 t.Fatalf("err: %s", err)
800 }
801 testutil.Yield()
802
803 if err := cl.UserEvent("deploy", nil, false); err.Error() != authRequired {
804 t.Fatalf("err: %s", err)
805 }
806 testutil.Yield()
807
808 config := client.Config{Addr: ipc.listener.Addr().String(), AuthKey: "foobar"}
809 rpcClient, err := client.ClientFromConfig(&config)
810 if err != nil {
811 t.Fatalf("err: %s", err)
812 }
813 defer rpcClient.Close()
814
815 if err := rpcClient.UserEvent("deploy", nil, false); err != nil {
816 t.Fatalf("err: %s", err)
817 }
818 }
819
820 func TestRPCClient_Keys_EncryptionDisabledError(t *testing.T) {
821 client, a1, ipc := testRPCClient(t)
822 defer ipc.Shutdown()
823 defer client.Close()
824 defer a1.Shutdown()
825
826 if err := a1.Start(); err != nil {
827 t.Fatalf("err: %s", err)
828 }
829
830 // Failed installing key
831 failures, err := client.InstallKey("El/H8lEqX2WiUa36SxcpZw==")
832 if err == nil {
833 t.Fatalf("expected encryption disabled error")
834 }
835 if _, ok := failures[a1.conf.NodeName]; !ok {
836 t.Fatalf("expected error from node %s", a1.conf.NodeName)
837 }
838
839 // Failed using key
840 failures, err = client.UseKey("El/H8lEqX2WiUa36SxcpZw==")
841 if err == nil {
842 t.Fatalf("expected encryption disabled error")
843 }
844 if _, ok := failures[a1.conf.NodeName]; !ok {
845 t.Fatalf("expected error from node %s", a1.conf.NodeName)
846 }
847
848 // Failed removing key
849 failures, err = client.RemoveKey("El/H8lEqX2WiUa36SxcpZw==")
850 if err == nil {
851 t.Fatalf("expected encryption disabled error")
852 }
853 if _, ok := failures[a1.conf.NodeName]; !ok {
854 t.Fatalf("expected error from node %s", a1.conf.NodeName)
855 }
856
857 // Failed listing keys
858 _, _, failures, err = client.ListKeys()
859 if err == nil {
860 t.Fatalf("expected encryption disabled error")
861 }
862 if _, ok := failures[a1.conf.NodeName]; !ok {
863 t.Fatalf("expected error from node %s", a1.conf.NodeName)
864 }
865 }
866
867 func TestRPCClient_Keys(t *testing.T) {
868 newKey := "El/H8lEqX2WiUa36SxcpZw=="
869 existing := "A2xzjs0eq9PxSV2+dPi3sg=="
870 existingBytes, err := base64.StdEncoding.DecodeString(existing)
871 if err != nil {
872 t.Fatalf("err: %s", err)
873 }
874
875 agentConf := DefaultConfig()
876 serfConf := serf.DefaultConfig()
877 serfConf.MemberlistConfig.SecretKey = existingBytes
878
879 client, a1, ipc := testRPCClientWithConfig(t, agentConf, serfConf)
880 defer ipc.Shutdown()
881 defer client.Close()
882 defer a1.Shutdown()
883
884 if err := a1.Start(); err != nil {
885 t.Fatalf("err: %s", err)
886 }
887
888 testutil.Yield()
889
890 keys, num, _, err := client.ListKeys()
891 if err != nil {
892 t.Fatalf("err: %s", err)
893 }
894 if _, ok := keys[newKey]; ok {
895 t.Fatalf("have new key: %s", newKey)
896 }
897
898 // Trying to use a key that doesn't exist errors
899 if _, err := client.UseKey(newKey); err == nil {
900 t.Fatalf("expected use-key error: %s", newKey)
901 }
902
903 // Keyring should not contain new key at this point
904 keys, _, _, err = client.ListKeys()
905 if err != nil {
906 t.Fatalf("err: %s", err)
907 }
908 if _, ok := keys[newKey]; ok {
909 t.Fatalf("have new key: %s", newKey)
910 }
911
912 // Invalid key installation throws an error
913 if _, err := client.InstallKey("badkey"); err == nil {
914 t.Fatalf("expected bad key error")
915 }
916
917 // InstallKey should succeed
918 if _, err := client.InstallKey(newKey); err != nil {
919 t.Fatalf("err: %s", err)
920 }
921
922 // InstallKey is idempotent
923 if _, err := client.InstallKey(newKey); err != nil {
924 t.Fatalf("err: %s", err)
925 }
926
927 // New key should now appear in the list of keys
928 keys, num, _, err = client.ListKeys()
929 if err != nil {
930 t.Fatalf("err: %s", err)
931 }
932 if num != 1 {
933 t.Fatalf("expected 1 member total, got %d", num)
934 }
935 if _, ok := keys[newKey]; !ok {
936 t.Fatalf("key not found: %s", newKey)
937 }
938
939 // Counter of installed copies of new key should be 1
940 if keys[newKey] != 1 {
941 t.Fatalf("expected 1 member with key %s, have %d", newKey, keys[newKey])
942 }
943
944 // Deleting primary key should return error
945 if _, err := client.RemoveKey(existing); err == nil {
946 t.Fatalf("expected error deleting primary key: %s", newKey)
947 }
948
949 // UseKey succeeds when given a key that exists
950 if _, err := client.UseKey(newKey); err != nil {
951 t.Fatalf("err: %s", err)
952 }
953
954 // UseKey is idempotent
955 if _, err := client.UseKey(newKey); err != nil {
956 t.Fatalf("err: %s", err)
957 }
958
959 // Removing a non-primary key should succeed
960 if _, err := client.RemoveKey(newKey); err == nil {
961 t.Fatalf("expected error deleting primary key: %s", newKey)
962 }
963
964 // RemoveKey is idempotent
965 if _, err := client.RemoveKey(existing); err != nil {
966 t.Fatalf("err: %s", err)
967 }
968 }
969
970 func TestRPCClientStats(t *testing.T) {
971 client, a1, ipc := testRPCClient(t)
972 defer ipc.Shutdown()
973 defer client.Close()
974 defer a1.Shutdown()
975
976 if err := a1.Start(); err != nil {
977 t.Fatalf("err: %s", err)
978 }
979
980 testutil.Yield()
981
982 stats, err := client.Stats()
983 if err != nil {
984 t.Fatalf("err: %v", err)
985 }
986
987 if stats["agent"]["name"] != a1.conf.NodeName {
988 t.Fatalf("bad: %v", stats)
989 }
990 }
0 package agent
1
2 import (
3 "bytes"
4 "github.com/hashicorp/go-syslog"
5 )
6
7 // levelPriority is used to map a log level to a
8 // syslog priority level
9 var levelPriority = map[string]gsyslog.Priority{
10 "TRACE": gsyslog.LOG_DEBUG,
11 "DEBUG": gsyslog.LOG_INFO,
12 "INFO": gsyslog.LOG_NOTICE,
13 "WARN": gsyslog.LOG_WARNING,
14 "ERR": gsyslog.LOG_ERR,
15 "CRIT": gsyslog.LOG_CRIT,
16 }
17
18 // SyslogWrapper is used to cleaup log messages before
19 // writing them to a Syslogger. Implements the io.Writer
20 // interface.
21 type SyslogWrapper struct {
22 l gsyslog.Syslogger
23 }
24
25 // Write is used to implement io.Writer
26 func (s *SyslogWrapper) Write(p []byte) (int, error) {
27 // Extract log level
28 var level string
29 afterLevel := p
30 x := bytes.IndexByte(p, '[')
31 if x >= 0 {
32 y := bytes.IndexByte(p[x:], ']')
33 if y >= 0 {
34 level = string(p[x+1 : x+y])
35 afterLevel = p[x+y+2:]
36 }
37 }
38
39 // Each log level will be handled by a specific syslog priority
40 priority, ok := levelPriority[level]
41 if !ok {
42 priority = gsyslog.LOG_NOTICE
43 }
44
45 // Attempt the write
46 err := s.l.WriteLevel(priority, afterLevel)
47 return len(p), err
48 }
0 package agent
1
2 import (
3 "runtime"
4 "strconv"
5 )
6
7 // runtimeStats is used to return various runtime information
8 func runtimeStats() map[string]string {
9 return map[string]string{
10 "os": runtime.GOOS,
11 "arch": runtime.GOARCH,
12 "version": runtime.Version(),
13 "max_procs": strconv.FormatInt(int64(runtime.GOMAXPROCS(0)), 10),
14 "goroutines": strconv.FormatInt(int64(runtime.NumGoroutine()), 10),
15 "cpu_count": strconv.FormatInt(int64(runtime.NumCPU()), 10),
16 }
17 }
0 package agent
1
2 import (
3 "fmt"
4 "github.com/hashicorp/serf/serf"
5 "github.com/hashicorp/serf/testutil"
6 "io"
7 "math/rand"
8 "net"
9 "os"
10 "time"
11 )
12
13 func init() {
14 // Seed the random number generator
15 rand.Seed(time.Now().UnixNano())
16 }
17
18 func drainEventCh(ch <-chan string) {
19 for {
20 select {
21 case <-ch:
22 default:
23 return
24 }
25 }
26 }
27
28 func getRPCAddr() string {
29 for i := 0; i < 500; i++ {
30 l, err := net.Listen("tcp", fmt.Sprintf(":%d", rand.Int31n(25000)+1024))
31 if err == nil {
32 l.Close()
33 return l.Addr().String()
34 }
35 }
36
37 panic("no listener")
38 }
39
40 func testAgent(logOutput io.Writer) *Agent {
41 return testAgentWithConfig(DefaultConfig(), serf.DefaultConfig(), logOutput)
42 }
43
44 func testAgentWithConfig(agentConfig *Config, serfConfig *serf.Config,
45 logOutput io.Writer) *Agent {
46
47 if logOutput == nil {
48 logOutput = os.Stderr
49 }
50 serfConfig.MemberlistConfig.ProbeInterval = 100 * time.Millisecond
51 serfConfig.MemberlistConfig.BindAddr = testutil.GetBindAddr().String()
52 serfConfig.NodeName = serfConfig.MemberlistConfig.BindAddr
53
54 agent, err := Create(agentConfig, serfConfig, logOutput)
55 if err != nil {
56 panic(err)
57 }
58 return agent
59 }
0 package command
1
2 import (
3 "flag"
4 "fmt"
5 "github.com/mitchellh/cli"
6 "strings"
7 )
8
9 // EventCommand is a Command implementation that queries a running
10 // Serf agent what members are part of the cluster currently.
11 type EventCommand struct {
12 Ui cli.Ui
13 }
14
15 func (c *EventCommand) Help() string {
16 helpText := `
17 Usage: serf event [options] name payload
18
19 Dispatches a custom event across the Serf cluster.
20
21 Options:
22
23 -coalesce=true/false Whether this event can be coalesced. This means
24 that repeated events of the same name within a
25 short period of time are ignored, except the last
26 one received. Default is true.
27 -rpc-addr=127.0.0.1:7373 RPC address of the Serf agent.
28 -rpc-auth="" RPC auth token of the Serf agent.
29 `
30 return strings.TrimSpace(helpText)
31 }
32
33 func (c *EventCommand) Run(args []string) int {
34 var coalesce bool
35
36 cmdFlags := flag.NewFlagSet("event", flag.ContinueOnError)
37 cmdFlags.Usage = func() { c.Ui.Output(c.Help()) }
38 cmdFlags.BoolVar(&coalesce, "coalesce", true, "coalesce")
39 rpcAddr := RPCAddrFlag(cmdFlags)
40 rpcAuth := RPCAuthFlag(cmdFlags)
41 if err := cmdFlags.Parse(args); err != nil {
42 return 1
43 }
44
45 args = cmdFlags.Args()
46 if len(args) < 1 {
47 c.Ui.Error("An event name must be specified.")
48 c.Ui.Error("")
49 c.Ui.Error(c.Help())
50 return 1
51 } else if len(args) > 2 {
52 c.Ui.Error("Too many command line arguments. Only a name and payload must be specified.")
53 c.Ui.Error("")
54 c.Ui.Error(c.Help())
55 return 1
56 }
57
58 event := args[0]
59 var payload []byte
60 if len(args) == 2 {
61 payload = []byte(args[1])
62 }
63
64 client, err := RPCClient(*rpcAddr, *rpcAuth)
65 if err != nil {
66 c.Ui.Error(fmt.Sprintf("Error connecting to Serf agent: %s", err))
67 return 1
68 }
69 defer client.Close()
70
71 if err := client.UserEvent(event, payload, coalesce); err != nil {
72 c.Ui.Error(fmt.Sprintf("Error sending event: %s", err))
73 return 1
74 }
75
76 c.Ui.Output(fmt.Sprintf("Event '%s' dispatched! Coalescing enabled: %#v",
77 event, coalesce))
78 return 0
79 }
80
81 func (c *EventCommand) Synopsis() string {
82 return "Send a custom event through the Serf cluster"
83 }
0 package command
1
2 import (
3 "github.com/mitchellh/cli"
4 "strings"
5 "testing"
6 )
7
8 func TestEventCommand_implements(t *testing.T) {
9 var _ cli.Command = &EventCommand{}
10 }
11
12 func TestEventCommandRun_noEvent(t *testing.T) {
13 ui := new(cli.MockUi)
14 c := &EventCommand{Ui: ui}
15 args := []string{"-rpc-addr=foo"}
16
17 code := c.Run(args)
18 if code != 1 {
19 t.Fatalf("bad: %d", code)
20 }
21
22 if !strings.Contains(ui.ErrorWriter.String(), "event name") {
23 t.Fatalf("bad: %#v", ui.ErrorWriter.String())
24 }
25 }
26
27 func TestEventCommandRun_tooMany(t *testing.T) {
28 ui := new(cli.MockUi)
29 c := &EventCommand{Ui: ui}
30 args := []string{"-rpc-addr=foo", "foo", "bar", "baz"}
31
32 code := c.Run(args)
33 if code != 1 {
34 t.Fatalf("bad: %d", code)
35 }
36
37 if !strings.Contains(ui.ErrorWriter.String(), "Too many") {
38 t.Fatalf("bad: %#v", ui.ErrorWriter.String())
39 }
40 }
0 package command
1
2 import (
3 "flag"
4 "fmt"
5 "github.com/mitchellh/cli"
6 "strings"
7 )
8
9 // ForceLeaveCommand is a Command implementation that tells a running Serf
10 // to force a member to enter the "left" state.
11 type ForceLeaveCommand struct {
12 Ui cli.Ui
13 }
14
15 func (c *ForceLeaveCommand) Run(args []string) int {
16 cmdFlags := flag.NewFlagSet("join", flag.ContinueOnError)
17 cmdFlags.Usage = func() { c.Ui.Output(c.Help()) }
18 rpcAddr := RPCAddrFlag(cmdFlags)
19 rpcAuth := RPCAuthFlag(cmdFlags)
20 if err := cmdFlags.Parse(args); err != nil {
21 return 1
22 }
23
24 nodes := cmdFlags.Args()
25 if len(nodes) != 1 {
26 c.Ui.Error("A node name must be specified to force leave.")
27 c.Ui.Error("")
28 c.Ui.Error(c.Help())
29 return 1
30 }
31
32 client, err := RPCClient(*rpcAddr, *rpcAuth)
33 if err != nil {
34 c.Ui.Error(fmt.Sprintf("Error connecting to Serf agent: %s", err))
35 return 1
36 }
37 defer client.Close()
38
39 err = client.ForceLeave(nodes[0])
40 if err != nil {
41 c.Ui.Error(fmt.Sprintf("Error force leaving: %s", err))
42 return 1
43 }
44
45 return 0
46 }
47
48 func (c *ForceLeaveCommand) Synopsis() string {
49 return "Forces a member of the cluster to enter the \"left\" state"
50 }
51
52 func (c *ForceLeaveCommand) Help() string {
53 helpText := `
54 Usage: serf force-leave [options] name
55
56 Forces a member of a Serf cluster to enter the "left" state. Note
57 that if the member is still actually alive, it will eventually rejoin
58 the cluster. This command is most useful for cleaning out "failed" nodes
59 that are never coming back. If you do not force leave a failed node,
60 Serf will attempt to reconnect to those failed nodes for some period of
61 time before eventually reaping them.
62
63 Options:
64
65 -rpc-addr=127.0.0.1:7373 RPC address of the Serf agent.
66 -rpc-auth="" RPC auth token of the Serf agent.
67 `
68 return strings.TrimSpace(helpText)
69 }
0 package command
1
2 import (
3 "github.com/hashicorp/serf/serf"
4 "github.com/hashicorp/serf/testutil"
5 "github.com/mitchellh/cli"
6 "strings"
7 "testing"
8 "time"
9 )
10
11 func TestForceLeaveCommand_implements(t *testing.T) {
12 var _ cli.Command = &ForceLeaveCommand{}
13 }
14
15 func TestForceLeaveCommandRun(t *testing.T) {
16 a1 := testAgent(t)
17 a2 := testAgent(t)
18 defer a1.Shutdown()
19 defer a2.Shutdown()
20 rpcAddr, ipc := testIPC(t, a1)
21 defer ipc.Shutdown()
22
23 _, err := a1.Join([]string{a2.SerfConfig().MemberlistConfig.BindAddr}, false)
24 if err != nil {
25 t.Fatalf("err: %s", err)
26 }
27
28 testutil.Yield()
29
30 // Forcibly shutdown a2 so that it appears "failed" in a1
31 if err := a2.Serf().Shutdown(); err != nil {
32 t.Fatalf("err: %s", err)
33 }
34
35 start := time.Now()
36 WAIT:
37 time.Sleep(a2.SerfConfig().MemberlistConfig.ProbeInterval * 3)
38 m := a1.Serf().Members()
39 if len(m) != 2 {
40 t.Fatalf("should have 2 members: %#v", a1.Serf().Members())
41 }
42
43 if m[1].Status != serf.StatusFailed && time.Now().Sub(start) < 3*time.Second {
44 goto WAIT
45 }
46
47 ui := new(cli.MockUi)
48 c := &ForceLeaveCommand{Ui: ui}
49 args := []string{
50 "-rpc-addr=" + rpcAddr,
51 a2.SerfConfig().NodeName,
52 }
53
54 code := c.Run(args)
55 if code != 0 {
56 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
57 }
58
59 m = a1.Serf().Members()
60 if len(m) != 2 {
61 t.Fatalf("should have 2 members: %#v", a1.Serf().Members())
62 }
63
64 if m[1].Status != serf.StatusLeft {
65 t.Fatalf("should be left: %#v", m[1])
66 }
67 }
68
69 func TestForceLeaveCommandRun_noAddrs(t *testing.T) {
70 ui := new(cli.MockUi)
71 c := &ForceLeaveCommand{Ui: ui}
72 args := []string{"-rpc-addr=foo"}
73
74 code := c.Run(args)
75 if code != 1 {
76 t.Fatalf("bad: %d", code)
77 }
78
79 if !strings.Contains(ui.ErrorWriter.String(), "node name") {
80 t.Fatalf("bad: %#v", ui.ErrorWriter.String())
81 }
82 }
0 package command
1
2 import (
3 "bytes"
4 "flag"
5 "fmt"
6 "github.com/mitchellh/cli"
7 "sort"
8 "strings"
9 )
10
11 // InfoCommand is a Command implementation that queries a running
12 // Serf agent for various debugging statistics for operators
13 type InfoCommand struct {
14 Ui cli.Ui
15 }
16
17 func (i *InfoCommand) Help() string {
18 helpText := `
19 Usage: serf info [options]
20
21 Provides debugging information for operators
22
23 Options:
24
25 -format If provided, output is returned in the specified
26 format. Valid formats are 'json', and 'text' (default)
27
28 -rpc-addr=127.0.0.1:7373 RPC address of the Serf agent.
29
30 -rpc-auth="" RPC auth token of the Serf agent.
31 `
32 return strings.TrimSpace(helpText)
33 }
34
35 func (i *InfoCommand) Run(args []string) int {
36 var format string
37 cmdFlags := flag.NewFlagSet("info", flag.ContinueOnError)
38 cmdFlags.Usage = func() { i.Ui.Output(i.Help()) }
39 cmdFlags.StringVar(&format, "format", "text", "output format")
40 rpcAddr := RPCAddrFlag(cmdFlags)
41 rpcAuth := RPCAuthFlag(cmdFlags)
42 if err := cmdFlags.Parse(args); err != nil {
43 return 1
44 }
45
46 client, err := RPCClient(*rpcAddr, *rpcAuth)
47 if err != nil {
48 i.Ui.Error(fmt.Sprintf("Error connecting to Serf agent: %s", err))
49 return 1
50 }
51 defer client.Close()
52
53 stats, err := client.Stats()
54 if err != nil {
55 i.Ui.Error(fmt.Sprintf("Error querying agent: %s", err))
56 return 1
57 }
58
59 output, err := formatOutput(StatsContainer(stats), format)
60 if err != nil {
61 i.Ui.Error(fmt.Sprintf("Encoding error: %s", err))
62 return 1
63 }
64
65 i.Ui.Output(string(output))
66 return 0
67 }
68
69 func (i *InfoCommand) Synopsis() string {
70 return "Provides debugging information for operators"
71 }
72
73 type StatsContainer map[string]map[string]string
74
75 func (s StatsContainer) String() string {
76 var buf bytes.Buffer
77
78 // Get the keys in sorted order
79 keys := make([]string, 0, len(s))
80 for key := range s {
81 keys = append(keys, key)
82 }
83 sort.Strings(keys)
84
85 // Iterate over each top-level key
86 for _, key := range keys {
87 buf.WriteString(fmt.Sprintf(key + ":\n"))
88
89 // Sort the sub-keys
90 subvals := s[key]
91 subkeys := make([]string, 0, len(subvals))
92 for k := range subvals {
93 subkeys = append(subkeys, k)
94 }
95 sort.Strings(subkeys)
96
97 // Iterate over the subkeys
98 for _, subkey := range subkeys {
99 val := subvals[subkey]
100 buf.WriteString(fmt.Sprintf("\t%s = %s\n", subkey, val))
101 }
102 }
103 return buf.String()
104 }
0 package command
1
2 import (
3 "github.com/mitchellh/cli"
4 "strings"
5 "testing"
6 )
7
8 func TestInfoCommand_implements(t *testing.T) {
9 var _ cli.Command = &InfoCommand{}
10 }
11
12 func TestInfoCommandRun(t *testing.T) {
13 a1 := testAgent(t)
14 defer a1.Shutdown()
15 rpcAddr, ipc := testIPC(t, a1)
16 defer ipc.Shutdown()
17
18 ui := new(cli.MockUi)
19 c := &InfoCommand{Ui: ui}
20 args := []string{"-rpc-addr=" + rpcAddr}
21
22 code := c.Run(args)
23 if code != 0 {
24 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
25 }
26
27 if !strings.Contains(ui.OutputWriter.String(), "runtime") {
28 t.Fatalf("bad: %#v", ui.OutputWriter.String())
29 }
30 }
0 package command
1
2 import (
3 "flag"
4 "fmt"
5 "github.com/mitchellh/cli"
6 "strings"
7 )
8
9 // JoinCommand is a Command implementation that tells a running Serf
10 // agent to join another.
11 type JoinCommand struct {
12 Ui cli.Ui
13 }
14
15 func (c *JoinCommand) Help() string {
16 helpText := `
17 Usage: serf join [options] address ...
18
19 Tells a running Serf agent (with "serf agent") to join the cluster
20 by specifying at least one existing member.
21
22 Options:
23
24 -replay Replay past user events.
25 -rpc-addr=127.0.0.1:7373 RPC address of the Serf agent.
26 -rpc-auth="" RPC auth token of the Serf agent.
27 `
28 return strings.TrimSpace(helpText)
29 }
30
31 func (c *JoinCommand) Run(args []string) int {
32 var replayEvents bool
33
34 cmdFlags := flag.NewFlagSet("join", flag.ContinueOnError)
35 cmdFlags.Usage = func() { c.Ui.Output(c.Help()) }
36 cmdFlags.BoolVar(&replayEvents, "replay", false, "replay")
37 rpcAddr := RPCAddrFlag(cmdFlags)
38 rpcAuth := RPCAuthFlag(cmdFlags)
39 if err := cmdFlags.Parse(args); err != nil {
40 return 1
41 }
42
43 addrs := cmdFlags.Args()
44 if len(addrs) == 0 {
45 c.Ui.Error("At least one address to join must be specified.")
46 c.Ui.Error("")
47 c.Ui.Error(c.Help())
48 return 1
49 }
50
51 client, err := RPCClient(*rpcAddr, *rpcAuth)
52 if err != nil {
53 c.Ui.Error(fmt.Sprintf("Error connecting to Serf agent: %s", err))
54 return 1
55 }
56 defer client.Close()
57
58 n, err := client.Join(addrs, replayEvents)
59 if err != nil {
60 c.Ui.Error(fmt.Sprintf("Error joining the cluster: %s", err))
61 return 1
62 }
63
64 c.Ui.Output(fmt.Sprintf(
65 "Successfully joined cluster by contacting %d nodes.", n))
66 return 0
67 }
68
69 func (c *JoinCommand) Synopsis() string {
70 return "Tell Serf agent to join cluster"
71 }
0 package command
1
2 import (
3 "github.com/mitchellh/cli"
4 "strings"
5 "testing"
6 )
7
8 func TestJoinCommand_implements(t *testing.T) {
9 var _ cli.Command = &JoinCommand{}
10 }
11
12 func TestJoinCommandRun(t *testing.T) {
13 a1 := testAgent(t)
14 a2 := testAgent(t)
15 defer a1.Shutdown()
16 defer a2.Shutdown()
17 rpcAddr, ipc := testIPC(t, a1)
18 defer ipc.Shutdown()
19
20 ui := new(cli.MockUi)
21 c := &JoinCommand{Ui: ui}
22 args := []string{
23 "-rpc-addr=" + rpcAddr,
24 a2.SerfConfig().MemberlistConfig.BindAddr,
25 }
26
27 code := c.Run(args)
28 if code != 0 {
29 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
30 }
31
32 if len(a1.Serf().Members()) != 2 {
33 t.Fatalf("bad: %#v", a1.Serf().Members())
34 }
35 }
36
37 func TestJoinCommandRun_noAddrs(t *testing.T) {
38 ui := new(cli.MockUi)
39 c := &JoinCommand{Ui: ui}
40 args := []string{"-rpc-addr=foo"}
41
42 code := c.Run(args)
43 if code != 1 {
44 t.Fatalf("bad: %d", code)
45 }
46
47 if !strings.Contains(ui.ErrorWriter.String(), "one address") {
48 t.Fatalf("bad: %#v", ui.ErrorWriter.String())
49 }
50 }
0 package command
1
2 import (
3 "crypto/rand"
4 "encoding/base64"
5 "fmt"
6 "github.com/mitchellh/cli"
7 "strings"
8 )
9
10 // KeygenCommand is a Command implementation that generates an encryption
11 // key for use in `serf agent`.
12 type KeygenCommand struct {
13 Ui cli.Ui
14 }
15
16 func (c *KeygenCommand) Run(_ []string) int {
17 key := make([]byte, 16)
18 n, err := rand.Reader.Read(key)
19 if err != nil {
20 c.Ui.Error(fmt.Sprintf("Error reading random data: %s", err))
21 return 1
22 }
23 if n != 16 {
24 c.Ui.Error(fmt.Sprintf("Couldn't read enough entropy. Generate more entropy!"))
25 return 1
26 }
27
28 c.Ui.Output(base64.StdEncoding.EncodeToString(key))
29 return 0
30 }
31
32 func (c *KeygenCommand) Synopsis() string {
33 return "Generates a new encryption key"
34 }
35
36 func (c *KeygenCommand) Help() string {
37 helpText := `
38 Usage: serf keygen
39
40 Generates a new encryption key that can be used to configure the
41 agent to encrypt traffic. The output of this command is already
42 in the proper format that the agent expects.
43 `
44 return strings.TrimSpace(helpText)
45 }
0 package command
1
2 import (
3 "encoding/base64"
4 "github.com/mitchellh/cli"
5 "testing"
6 )
7
8 func TestKeygenCommand_implements(t *testing.T) {
9 var _ cli.Command = &KeygenCommand{}
10 }
11
12 func TestKeygenCommand(t *testing.T) {
13 ui := new(cli.MockUi)
14 c := &KeygenCommand{Ui: ui}
15 code := c.Run(nil)
16 if code != 0 {
17 t.Fatalf("bad: %d", code)
18 }
19
20 output := ui.OutputWriter.String()
21 result, err := base64.StdEncoding.DecodeString(output)
22 if err != nil {
23 t.Fatalf("err: %s", err)
24 }
25
26 if len(result) != 16 {
27 t.Fatalf("bad: %#v", result)
28 }
29 }
0 package command
1
2 import (
3 "flag"
4 "fmt"
5 "github.com/mitchellh/cli"
6 "github.com/ryanuber/columnize"
7 "strings"
8 )
9
10 type KeysCommand struct {
11 Ui cli.Ui
12 }
13
14 func (c *KeysCommand) Help() string {
15 helpText := `
16 Usage: serf keys [options]...
17
18 Manage the internal encryption keyring used by Serf. Modifications made by
19 this command will be broadcasted to all members in the cluster and applied
20 locally on each member. Operations of this command are idempotent.
21
22 To facilitate key rotation, Serf allows for multiple encryption keys to be in
23 use simultaneously. Only one key, the "primary" key, will be used for
24 encrypting messages. All other keys are used for decryption only.
25
26 All variations of this command will return 0 if all nodes reply and report
27 no errors. If any node fails to respond or reports failure, we return 1.
28
29 WARNING: Running with multiple encryption keys enabled is recommended as a
30 transition state only. Performance may be impacted by using multiple keys.
31
32 Options:
33
34 -install=<key> Install a new key onto Serf's internal keyring. This
35 will enable the key for decryption. The key will not
36 be used to encrypt messages until the primary key is
37 changed.
38 -use=<key> Change the primary key used for encrypting messages.
39 All nodes in the cluster must already have this key
40 installed if they are to continue communicating with
41 eachother.
42 -remove=<key> Remove a key from Serf's internal keyring. The key
43 being removed may not be the current primary key.
44 -list List all currently known keys in the cluster. This
45 will ask all nodes in the cluster for a list of keys
46 and dump a summary containing each key and the
47 number of members it is installed on to the console.
48 -rpc-addr=127.0.0.1:7373 RPC address of the Serf agent.
49 -rpc-auth="" RPC auth token of the Serf agent.
50 `
51 return strings.TrimSpace(helpText)
52 }
53
54 func (c *KeysCommand) Run(args []string) int {
55 var installKey, useKey, removeKey string
56 var lines []string
57 var listKeys bool
58
59 cmdFlags := flag.NewFlagSet("key", flag.ContinueOnError)
60 cmdFlags.Usage = func() { c.Ui.Output(c.Help()) }
61 cmdFlags.StringVar(&installKey, "install", "", "install a new key")
62 cmdFlags.StringVar(&useKey, "use", "", "change primary encryption key")
63 cmdFlags.StringVar(&removeKey, "remove", "", "remove a key")
64 cmdFlags.BoolVar(&listKeys, "list", false, "list cluster keys")
65 rpcAddr := RPCAddrFlag(cmdFlags)
66 rpcAuth := RPCAuthFlag(cmdFlags)
67 if err := cmdFlags.Parse(args); err != nil {
68 return 1
69 }
70
71 c.Ui = &cli.PrefixedUi{
72 OutputPrefix: "",
73 InfoPrefix: "==> ",
74 ErrorPrefix: "",
75 Ui: c.Ui,
76 }
77
78 // Make sure that we only have one actionable argument to avoid ambiguity
79 found := listKeys
80 for _, arg := range []string{installKey, useKey, removeKey} {
81 if found && len(arg) > 0 {
82 c.Ui.Error("Only one of -install, -use, -remove, or -list allowed")
83 return 1
84 }
85 found = found || len(arg) > 0
86 }
87
88 // Fail fast if no actionable args were passed
89 if !found {
90 c.Ui.Error(c.Help())
91 return 1
92 }
93
94 client, err := RPCClient(*rpcAddr, *rpcAuth)
95 if err != nil {
96 c.Ui.Error(fmt.Sprintf("Error connecting to Serf agent: %s", err))
97 return 1
98 }
99 defer client.Close()
100
101 if listKeys {
102 c.Ui.Info("Asking all members for installed keys...")
103 keys, total, failures, err := client.ListKeys()
104
105 if err != nil {
106 if len(failures) > 0 {
107 for node, message := range failures {
108 lines = append(lines, fmt.Sprintf("failed: | %s | %s", node, message))
109 }
110 out := columnize.SimpleFormat(lines)
111 c.Ui.Error(out)
112 }
113
114 c.Ui.Error("")
115 c.Ui.Error(fmt.Sprintf("Failed to gather member keys: %s", err))
116 return 1
117 }
118
119 c.Ui.Info("Keys gathered, listing cluster keys...")
120 c.Ui.Output("")
121
122 for key, num := range keys {
123 lines = append(lines, fmt.Sprintf("%s | [%d/%d]", key, num, total))
124 }
125 out := columnize.SimpleFormat(lines)
126 c.Ui.Output(out)
127
128 return 0
129 }
130
131 if installKey != "" {
132 c.Ui.Info("Installing key on all members...")
133 if failures, err := client.InstallKey(installKey); err != nil {
134 if len(failures) > 0 {
135 for node, message := range failures {
136 lines = append(lines, fmt.Sprintf("failed: | %s | %s", node, message))
137 }
138 out := columnize.SimpleFormat(lines)
139 c.Ui.Error(out)
140 }
141 c.Ui.Error("")
142 c.Ui.Error(fmt.Sprintf("Error installing key: %s", err))
143 return 1
144 }
145 c.Ui.Info("Successfully installed key!")
146 return 0
147 }
148
149 if useKey != "" {
150 c.Ui.Info("Changing primary key on all members...")
151 if failures, err := client.UseKey(useKey); err != nil {
152 if len(failures) > 0 {
153 for node, message := range failures {
154 lines = append(lines, fmt.Sprintf("failed: | %s | %s", node, message))
155 }
156 out := columnize.SimpleFormat(lines)
157 c.Ui.Error(out)
158 }
159 c.Ui.Error("")
160 c.Ui.Error(fmt.Sprintf("Error changing primary key: %s", err))
161 return 1
162 }
163 c.Ui.Info("Successfully changed primary key!")
164 return 0
165 }
166
167 if removeKey != "" {
168 c.Ui.Info("Removing key on all members...")
169 if failures, err := client.RemoveKey(removeKey); err != nil {
170 if len(failures) > 0 {
171 for node, message := range failures {
172 lines = append(lines, fmt.Sprintf("failed: | %s | %s", node, message))
173 }
174 out := columnize.SimpleFormat(lines)
175 c.Ui.Error(out)
176 }
177 c.Ui.Error("")
178 c.Ui.Error(fmt.Sprintf("Error removing key: %s", err))
179 return 1
180 }
181 c.Ui.Info("Successfully removed key!")
182 return 0
183 }
184
185 // Should never reach this point
186 return 0
187 }
188
189 func (c *KeysCommand) Synopsis() string {
190 return "Manipulate the internal encryption keyring used by Serf"
191 }
0 package command
1
2 import (
3 "encoding/base64"
4 "github.com/hashicorp/memberlist"
5 "github.com/hashicorp/serf/client"
6 "github.com/hashicorp/serf/command/agent"
7 "github.com/hashicorp/serf/serf"
8 "github.com/mitchellh/cli"
9 "strings"
10 "testing"
11 )
12
13 func testKeysCommandAgent(t *testing.T) *agent.Agent {
14 key1, err := base64.StdEncoding.DecodeString("SNCg1bQSoCdGVlEx+TgfBw==")
15 if err != nil {
16 t.Fatalf("err: %s", err)
17 }
18 key2, err := base64.StdEncoding.DecodeString("vbitCcJNwNP4aEWHgofjMg==")
19 if err != nil {
20 t.Fatalf("err: %s", err)
21 }
22 keyring, err := memberlist.NewKeyring([][]byte{key1, key2}, key1)
23 if err != nil {
24 t.Fatalf("err: %s", err)
25 }
26
27 agentConf := agent.DefaultConfig()
28 serfConf := serf.DefaultConfig()
29 serfConf.MemberlistConfig.Keyring = keyring
30
31 a1 := testAgentWithConfig(t, agentConf, serfConf)
32 return a1
33 }
34
35 func TestKeysCommand_implements(t *testing.T) {
36 var _ cli.Command = &KeysCommand{}
37 }
38
39 func TestKeysCommandRun_InstallKey(t *testing.T) {
40 a1 := testKeysCommandAgent(t)
41 defer a1.Shutdown()
42 rpcAddr, ipc := testIPC(t, a1)
43 defer ipc.Shutdown()
44
45 ui := new(cli.MockUi)
46 c := &KeysCommand{Ui: ui}
47
48 rpcClient, err := client.NewRPCClient(rpcAddr)
49 if err != nil {
50 t.Fatalf("err: %s", err)
51 }
52
53 keys, _, _, err := rpcClient.ListKeys()
54 if err != nil {
55 t.Fatalf("err: %s", err)
56 }
57 if _, ok := keys["jbuQMI4gMUeh1PPmKOtiBg=="]; ok {
58 t.Fatalf("have test key")
59 }
60
61 args := []string{
62 "-rpc-addr=" + rpcAddr,
63 "-install", "jbuQMI4gMUeh1PPmKOtiBg==",
64 }
65
66 code := c.Run(args)
67 if code != 0 {
68 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
69 }
70
71 if !strings.Contains(ui.OutputWriter.String(), "Successfully installed key") {
72 t.Fatalf("bad: %#v", ui.OutputWriter.String())
73 }
74
75 keys, _, _, err = rpcClient.ListKeys()
76 if err != nil {
77 t.Fatalf("err: %s", err)
78 }
79 if _, ok := keys["jbuQMI4gMUeh1PPmKOtiBg=="]; !ok {
80 t.Fatalf("new key not found")
81 }
82 }
83
84 func TestKeysCommandRun_InstallKeyFailure(t *testing.T) {
85 a1 := testAgent(t)
86 defer a1.Shutdown()
87 rpcAddr, ipc := testIPC(t, a1)
88 defer ipc.Shutdown()
89
90 ui := new(cli.MockUi)
91 c := &KeysCommand{Ui: ui}
92
93 // Trying to install with encryption disabled returns 1
94 args := []string{
95 "-rpc-addr=" + rpcAddr,
96 "-install", "jbuQMI4gMUeh1PPmKOtiBg==",
97 }
98
99 code := c.Run(args)
100 if code != 1 {
101 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
102 }
103
104 // Node errors appear in stderr
105 if !strings.Contains(ui.ErrorWriter.String(), "not enabled") {
106 t.Fatalf("expected empty keyring error")
107 }
108 }
109
110 func TestKeysCommandRun_UseKey(t *testing.T) {
111 a1 := testKeysCommandAgent(t)
112 defer a1.Shutdown()
113 rpcAddr, ipc := testIPC(t, a1)
114 defer ipc.Shutdown()
115
116 ui := new(cli.MockUi)
117 c := &KeysCommand{Ui: ui}
118
119 // Trying to use a non-existent key returns 1
120 args := []string{
121 "-rpc-addr=" + rpcAddr,
122 "-use", "eodFZZjm7pPwIZ0Miy7boQ==",
123 }
124
125 code := c.Run(args)
126 if code != 1 {
127 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
128 }
129
130 // Using an existing key returns 0
131 args = []string{
132 "-rpc-addr=" + rpcAddr,
133 "-use", "vbitCcJNwNP4aEWHgofjMg==",
134 }
135
136 code = c.Run(args)
137 if code != 0 {
138 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
139 }
140 }
141
142 func TestKeysCommandRun_UseKeyFailure(t *testing.T) {
143 a1 := testKeysCommandAgent(t)
144 defer a1.Shutdown()
145 rpcAddr, ipc := testIPC(t, a1)
146 defer ipc.Shutdown()
147
148 ui := new(cli.MockUi)
149 c := &KeysCommand{Ui: ui}
150
151 // Trying to use a key that doesn't exist returns 1
152 args := []string{
153 "-rpc-addr=" + rpcAddr,
154 "-use", "jbuQMI4gMUeh1PPmKOtiBg==",
155 }
156
157 code := c.Run(args)
158 if code != 1 {
159 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
160 }
161
162 // Node errors appear in stderr
163 if !strings.Contains(ui.ErrorWriter.String(), "not in the keyring") {
164 t.Fatalf("expected absent key error")
165 }
166 }
167
168 func TestKeysCommandRun_RemoveKey(t *testing.T) {
169 a1 := testKeysCommandAgent(t)
170 defer a1.Shutdown()
171 rpcAddr, ipc := testIPC(t, a1)
172 defer ipc.Shutdown()
173
174 ui := new(cli.MockUi)
175 c := &KeysCommand{Ui: ui}
176
177 rpcClient, err := client.NewRPCClient(rpcAddr)
178 if err != nil {
179 t.Fatalf("err: %s", err)
180 }
181
182 keys, _, _, err := rpcClient.ListKeys()
183 if err != nil {
184 t.Fatalf("err: %s", err)
185 }
186 if len(keys) != 2 {
187 t.Fatalf("expected 2 keys: %v", keys)
188 }
189
190 // Removing non-existing key still returns 0 (noop)
191 args := []string{
192 "-rpc-addr=" + rpcAddr,
193 "-remove", "eodFZZjm7pPwIZ0Miy7boQ==",
194 }
195
196 code := c.Run(args)
197 if code != 0 {
198 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
199 }
200
201 // Number of keys unchanged after noop command
202 keys, _, _, err = rpcClient.ListKeys()
203 if err != nil {
204 t.Fatalf("err: %s", err)
205 }
206 if len(keys) != 2 {
207 t.Fatalf("expected 2 keys: %v", keys)
208 }
209
210 // Removing a primary key returns 1
211 args = []string{
212 "-rpc-addr=" + rpcAddr,
213 "-remove", "SNCg1bQSoCdGVlEx+TgfBw==",
214 }
215
216 ui.ErrorWriter.Reset()
217 code = c.Run(args)
218 if code != 1 {
219 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
220 }
221
222 if !strings.Contains(ui.ErrorWriter.String(), "Error removing key") {
223 t.Fatalf("bad: %#v", ui.OutputWriter.String())
224 }
225
226 // Removing a non-primary, existing key returns 0
227 args = []string{
228 "-rpc-addr=" + rpcAddr,
229 "-remove", "vbitCcJNwNP4aEWHgofjMg==",
230 }
231
232 code = c.Run(args)
233 if code != 0 {
234 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
235 }
236
237 // Key removed after successful -remove command
238 keys, _, _, err = rpcClient.ListKeys()
239 if err != nil {
240 t.Fatalf("err: %s", err)
241 }
242 if len(keys) != 1 {
243 t.Fatalf("expected 2 keys: %v", keys)
244 }
245 }
246
247 func TestKeysCommandRun_RemoveKeyFailure(t *testing.T) {
248 a1 := testKeysCommandAgent(t)
249 defer a1.Shutdown()
250 rpcAddr, ipc := testIPC(t, a1)
251 defer ipc.Shutdown()
252
253 ui := new(cli.MockUi)
254 c := &KeysCommand{Ui: ui}
255
256 // Trying to remove the primary key returns 1
257 args := []string{
258 "-rpc-addr=" + rpcAddr,
259 "-remove", "SNCg1bQSoCdGVlEx+TgfBw==",
260 }
261
262 code := c.Run(args)
263 if code != 1 {
264 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
265 }
266
267 // Node errors appear in stderr
268 if !strings.Contains(ui.ErrorWriter.String(), "not allowed") {
269 t.Fatalf("expected primary key removal error")
270 }
271 }
272
273 func TestKeysCommandRun_ListKeys(t *testing.T) {
274 a1 := testKeysCommandAgent(t)
275 defer a1.Shutdown()
276 rpcAddr, ipc := testIPC(t, a1)
277 defer ipc.Shutdown()
278
279 ui := new(cli.MockUi)
280 c := &KeysCommand{Ui: ui}
281
282 args := []string{
283 "-rpc-addr=" + rpcAddr,
284 "-list",
285 }
286
287 code := c.Run(args)
288 if code == 1 {
289 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
290 }
291
292 if !strings.Contains(ui.OutputWriter.String(), "SNCg1bQSoCdGVlEx+TgfBw==") {
293 t.Fatalf("missing expected key")
294 }
295
296 if !strings.Contains(ui.OutputWriter.String(), "vbitCcJNwNP4aEWHgofjMg==") {
297 t.Fatalf("missing expected key")
298 }
299 }
300
301 func TestKeysCommandRun_ListKeysFailure(t *testing.T) {
302 a1 := testAgent(t)
303 defer a1.Shutdown()
304 rpcAddr, ipc := testIPC(t, a1)
305 defer ipc.Shutdown()
306
307 ui := new(cli.MockUi)
308 c := &KeysCommand{Ui: ui}
309
310 // Trying to list keys with encryption disabled returns 1
311 args := []string{
312 "-rpc-addr=" + rpcAddr,
313 "-list",
314 }
315
316 code := c.Run(args)
317 if code != 1 {
318 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
319 }
320
321 if !strings.Contains(ui.ErrorWriter.String(), "not enabled") {
322 t.Fatalf("expected empty keyring error")
323 }
324 }
325
326 func TestKeysCommandRun_BadOptions(t *testing.T) {
327 a1 := testAgent(t)
328 defer a1.Shutdown()
329 rpcAddr, ipc := testIPC(t, a1)
330 defer ipc.Shutdown()
331
332 ui := new(cli.MockUi)
333 c := &KeysCommand{Ui: ui}
334
335 args := []string{
336 "-rpc-addr=" + rpcAddr,
337 "-install", "vbitCcJNwNP4aEWHgofjMg==",
338 "-use", "vbitCcJNwNP4aEWHgofjMg==",
339 }
340
341 code := c.Run(args)
342 if code != 1 {
343 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
344 }
345
346 args = []string{
347 "-rpc-addr=" + rpcAddr,
348 "-list",
349 "-remove", "SNCg1bQSoCdGVlEx+TgfBw==",
350 }
351
352 code = c.Run(args)
353 if code != 1 {
354 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
355 }
356 }
0 package command
1
2 import (
3 "flag"
4 "fmt"
5 "github.com/mitchellh/cli"
6 "strings"
7 )
8
9 // LeaveCommand is a Command implementation that instructs
10 // the Serf agent to gracefully leave the cluster
11 type LeaveCommand struct {
12 Ui cli.Ui
13 }
14
15 func (c *LeaveCommand) Help() string {
16 helpText := `
17 Usage: serf leave
18
19 Causes the agent to gracefully leave the Serf cluster and shutdown.
20
21 Options:
22
23 -rpc-addr=127.0.0.1:7373 RPC address of the Serf agent.
24 -rpc-auth="" RPC auth token of the Serf agent.
25 `
26 return strings.TrimSpace(helpText)
27 }
28
29 func (c *LeaveCommand) Run(args []string) int {
30 cmdFlags := flag.NewFlagSet("leave", flag.ContinueOnError)
31 cmdFlags.Usage = func() { c.Ui.Output(c.Help()) }
32 rpcAddr := RPCAddrFlag(cmdFlags)
33 rpcAuth := RPCAuthFlag(cmdFlags)
34 if err := cmdFlags.Parse(args); err != nil {
35 return 1
36 }
37
38 client, err := RPCClient(*rpcAddr, *rpcAuth)
39 if err != nil {
40 c.Ui.Error(fmt.Sprintf("Error connecting to Serf agent: %s", err))
41 return 1
42 }
43 defer client.Close()
44
45 if err := client.Leave(); err != nil {
46 c.Ui.Error(fmt.Sprintf("Error leaving: %s", err))
47 return 1
48 }
49
50 c.Ui.Output("Graceful leave complete")
51 return 0
52 }
53
54 func (c *LeaveCommand) Synopsis() string {
55 return "Gracefully leaves the Serf cluster and shuts down"
56 }
0 package command
1
2 import (
3 "github.com/mitchellh/cli"
4 "strings"
5 "testing"
6 )
7
8 func TestLeaveCommand_implements(t *testing.T) {
9 var _ cli.Command = &LeaveCommand{}
10 }
11
12 func TestLeaveCommandRun(t *testing.T) {
13 a1 := testAgent(t)
14 defer a1.Shutdown()
15 rpcAddr, ipc := testIPC(t, a1)
16 defer ipc.Shutdown()
17
18 ui := new(cli.MockUi)
19 c := &LeaveCommand{Ui: ui}
20 args := []string{"-rpc-addr=" + rpcAddr}
21
22 code := c.Run(args)
23 if code != 0 {
24 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
25 }
26
27 if !strings.Contains(ui.OutputWriter.String(), "leave complete") {
28 t.Fatalf("bad: %#v", ui.OutputWriter.String())
29 }
30 }
0 package command
1
2 import (
3 "flag"
4 "fmt"
5 "github.com/hashicorp/serf/command/agent"
6 "github.com/mitchellh/cli"
7 "github.com/ryanuber/columnize"
8 "net"
9 "strings"
10 )
11
12 // MembersCommand is a Command implementation that queries a running
13 // Serf agent what members are part of the cluster currently.
14 type MembersCommand struct {
15 Ui cli.Ui
16 }
17
18 // A container of member details. Maintaining a command-specific struct here
19 // makes sense so that the agent.Member struct can evolve without changing the
20 // keys in the output interface.
21 type Member struct {
22 detail bool
23 Name string `json:"name"`
24 Addr string `json:"addr"`
25 Port uint16 `json:"port"`
26 Tags map[string]string `json:"tags"`
27 Status string `json:"status"`
28 Proto map[string]uint8 `json:"protocol"`
29 }
30
31 type MemberContainer struct {
32 Members []Member `json:"members"`
33 }
34
35 func (c MemberContainer) String() string {
36 var result []string
37 for _, member := range c.Members {
38 tags := strings.Join(agent.MarshalTags(member.Tags), ",")
39 line := fmt.Sprintf("%s|%s|%s|%s",
40 member.Name, member.Addr, member.Status, tags)
41 if member.detail {
42 line += fmt.Sprintf(
43 "|Protocol Version: %d|Available Protocol Range: [%d, %d]",
44 member.Proto["version"], member.Proto["min"], member.Proto["max"])
45 }
46 result = append(result, line)
47 }
48 return columnize.SimpleFormat(result)
49 }
50
51 func (c *MembersCommand) Help() string {
52 helpText := `
53 Usage: serf members [options]
54
55 Outputs the members of a running Serf agent.
56
57 Options:
58
59 -detailed Additional information such as protocol verions
60 will be shown (only affects text output format).
61
62 -format If provided, output is returned in the specified
63 format. Valid formats are 'json', and 'text' (default)
64
65 -name=<regexp> If provided, only members matching the regexp are
66 returned. The regexp is anchored at the start and end,
67 and must be a full match.
68
69 -role=<regexp> If provided, output is filtered to only nodes matching
70 the regular expression for role
71 '-role' is deprecated in favor of '-tag role=foo'.
72 The regexp is anchored at the start and end, and must be
73 a full match.
74
75 -status=<regexp> If provided, output is filtered to only nodes matching
76 the regular expression for status
77
78 -tag <key>=<regexp> If provided, output is filtered to only nodes with the
79 tag <key> with value matching the regular expression.
80 tag can be specified multiple times to filter on
81 multiple keys. The regexp is anchored at the start and end,
82 and must be a full match.
83
84 -rpc-addr=127.0.0.1:7373 RPC address of the Serf agent.
85
86 -rpc-auth="" RPC auth token of the Serf agent.
87 `
88 return strings.TrimSpace(helpText)
89 }
90
91 func (c *MembersCommand) Run(args []string) int {
92 var detailed bool
93 var roleFilter, statusFilter, nameFilter, format string
94 var tags []string
95 cmdFlags := flag.NewFlagSet("members", flag.ContinueOnError)
96 cmdFlags.Usage = func() { c.Ui.Output(c.Help()) }
97 cmdFlags.BoolVar(&detailed, "detailed", false, "detailed output")
98 cmdFlags.StringVar(&roleFilter, "role", "", "role filter")
99 cmdFlags.StringVar(&statusFilter, "status", "", "status filter")
100 cmdFlags.StringVar(&format, "format", "text", "output format")
101 cmdFlags.Var((*agent.AppendSliceValue)(&tags), "tag", "tag filter")
102 cmdFlags.StringVar(&nameFilter, "name", "", "name filter")
103 rpcAddr := RPCAddrFlag(cmdFlags)
104 rpcAuth := RPCAuthFlag(cmdFlags)
105 if err := cmdFlags.Parse(args); err != nil {
106 return 1
107 }
108
109 // Deprecation warning for role
110 if roleFilter != "" {
111 c.Ui.Output("Deprecation warning: 'Role' has been replaced with 'Tags'")
112 tags = append(tags, fmt.Sprintf("role=%s", roleFilter))
113 }
114
115 reqtags, err := agent.UnmarshalTags(tags)
116 if err != nil {
117 c.Ui.Error(fmt.Sprintf("Error: %s", err))
118 return 1
119 }
120
121 client, err := RPCClient(*rpcAddr, *rpcAuth)
122 if err != nil {
123 c.Ui.Error(fmt.Sprintf("Error connecting to Serf agent: %s", err))
124 return 1
125 }
126 defer client.Close()
127
128 members, err := client.MembersFiltered(reqtags, statusFilter, nameFilter)
129 if err != nil {
130 c.Ui.Error(fmt.Sprintf("Error retrieving members: %s", err))
131 return 1
132 }
133
134 result := MemberContainer{}
135
136 for _, member := range members {
137 addr := net.TCPAddr{IP: member.Addr, Port: int(member.Port)}
138
139 result.Members = append(result.Members, Member{
140 detail: detailed,
141 Name: member.Name,
142 Addr: addr.String(),
143 Port: member.Port,
144 Tags: member.Tags,
145 Status: member.Status,
146 Proto: map[string]uint8{
147 "min": member.DelegateMin,
148 "max": member.DelegateMax,
149 "version": member.DelegateCur,
150 },
151 })
152 }
153
154 output, err := formatOutput(result, format)
155 if err != nil {
156 c.Ui.Error(fmt.Sprintf("Encoding error: %s", err))
157 return 1
158 }
159
160 c.Ui.Output(string(output))
161 return 0
162 }
163
164 func (c *MembersCommand) Synopsis() string {
165 return "Lists the members of a Serf cluster"
166 }
0 package command
1
2 import (
3 "github.com/mitchellh/cli"
4 "strings"
5 "testing"
6 )
7
8 func TestMembersCommand_implements(t *testing.T) {
9 var _ cli.Command = &MembersCommand{}
10 }
11
12 func TestMembersCommandRun(t *testing.T) {
13 a1 := testAgent(t)
14 defer a1.Shutdown()
15 rpcAddr, ipc := testIPC(t, a1)
16 defer ipc.Shutdown()
17
18 ui := new(cli.MockUi)
19 c := &MembersCommand{Ui: ui}
20 args := []string{"-rpc-addr=" + rpcAddr}
21
22 code := c.Run(args)
23 if code != 0 {
24 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
25 }
26
27 if !strings.Contains(ui.OutputWriter.String(), a1.SerfConfig().NodeName) {
28 t.Fatalf("bad: %#v", ui.OutputWriter.String())
29 }
30 }
31
32 func TestMembersCommandRun_statusFilter(t *testing.T) {
33 a1 := testAgent(t)
34 defer a1.Shutdown()
35 rpcAddr, ipc := testIPC(t, a1)
36 defer ipc.Shutdown()
37
38 ui := new(cli.MockUi)
39 c := &MembersCommand{Ui: ui}
40 args := []string{
41 "-rpc-addr=" + rpcAddr,
42 "-status=alive",
43 }
44
45 code := c.Run(args)
46 if code != 0 {
47 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
48 }
49
50 if !strings.Contains(ui.OutputWriter.String(), a1.SerfConfig().NodeName) {
51 t.Fatalf("bad: %#v", ui.OutputWriter.String())
52 }
53 }
54
55 func TestMembersCommandRun_statusFilter_failed(t *testing.T) {
56 a1 := testAgent(t)
57 defer a1.Shutdown()
58 rpcAddr, ipc := testIPC(t, a1)
59 defer ipc.Shutdown()
60
61 ui := new(cli.MockUi)
62 c := &MembersCommand{Ui: ui}
63 args := []string{
64 "-rpc-addr=" + rpcAddr,
65 "-status=(failed|left)",
66 }
67
68 code := c.Run(args)
69 if code != 0 {
70 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
71 }
72
73 if strings.Contains(ui.OutputWriter.String(), a1.SerfConfig().NodeName) {
74 t.Fatalf("bad: %#v", ui.OutputWriter.String())
75 }
76 }
77
78 func TestMembersCommandRun_roleFilter(t *testing.T) {
79 a1 := testAgent(t)
80 defer a1.Shutdown()
81 rpcAddr, ipc := testIPC(t, a1)
82 defer ipc.Shutdown()
83
84 ui := new(cli.MockUi)
85 c := &MembersCommand{Ui: ui}
86 args := []string{
87 "-rpc-addr=" + rpcAddr,
88 "-role=test",
89 }
90
91 code := c.Run(args)
92 if code != 0 {
93 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
94 }
95
96 if !strings.Contains(ui.OutputWriter.String(), a1.SerfConfig().NodeName) {
97 t.Fatalf("bad: %#v", ui.OutputWriter.String())
98 }
99 }
100
101 func TestMembersCommandRun_roleFilter_failed(t *testing.T) {
102 a1 := testAgent(t)
103 defer a1.Shutdown()
104 rpcAddr, ipc := testIPC(t, a1)
105 defer ipc.Shutdown()
106
107 ui := new(cli.MockUi)
108 c := &MembersCommand{Ui: ui}
109 args := []string{
110 "-rpc-addr=" + rpcAddr,
111 "-role=primary",
112 }
113
114 code := c.Run(args)
115 if code != 0 {
116 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
117 }
118
119 if strings.Contains(ui.OutputWriter.String(), a1.SerfConfig().NodeName) {
120 t.Fatalf("bad: %#v", ui.OutputWriter.String())
121 }
122 }
123
124 func TestMembersCommandRun_tagFilter(t *testing.T) {
125 a1 := testAgent(t)
126 defer a1.Shutdown()
127 rpcAddr, ipc := testIPC(t, a1)
128 defer ipc.Shutdown()
129
130 ui := new(cli.MockUi)
131 c := &MembersCommand{Ui: ui}
132 args := []string{
133 "-rpc-addr=" + rpcAddr,
134 "-tag=tag1=foo",
135 }
136
137 code := c.Run(args)
138 if code != 0 {
139 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
140 }
141
142 if !strings.Contains(ui.OutputWriter.String(), a1.SerfConfig().NodeName) {
143 t.Fatalf("bad: %#v", ui.OutputWriter.String())
144 }
145 }
146
147 func TestMembersCommandRun_tagFilter_failed(t *testing.T) {
148 a1 := testAgent(t)
149 defer a1.Shutdown()
150 rpcAddr, ipc := testIPC(t, a1)
151 defer ipc.Shutdown()
152
153 ui := new(cli.MockUi)
154 c := &MembersCommand{Ui: ui}
155 args := []string{
156 "-rpc-addr=" + rpcAddr,
157 "-tag=tag1=nomatch",
158 }
159
160 code := c.Run(args)
161 if code != 0 {
162 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
163 }
164
165 if strings.Contains(ui.OutputWriter.String(), a1.SerfConfig().NodeName) {
166 t.Fatalf("bad: %#v", ui.OutputWriter.String())
167 }
168 }
169 func TestMembersCommandRun_mutliTagFilter(t *testing.T) {
170 a1 := testAgent(t)
171 defer a1.Shutdown()
172 rpcAddr, ipc := testIPC(t, a1)
173 defer ipc.Shutdown()
174
175 ui := new(cli.MockUi)
176 c := &MembersCommand{Ui: ui}
177 args := []string{
178 "-rpc-addr=" + rpcAddr,
179 "-tag=tag1=foo",
180 "-tag=tag2=bar",
181 }
182
183 code := c.Run(args)
184 if code != 0 {
185 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
186 }
187
188 if !strings.Contains(ui.OutputWriter.String(), a1.SerfConfig().NodeName) {
189 t.Fatalf("bad: %#v", ui.OutputWriter.String())
190 }
191 }
192
193 func TestMembersCommandRun_multiTagFilter_failed(t *testing.T) {
194 a1 := testAgent(t)
195 defer a1.Shutdown()
196 rpcAddr, ipc := testIPC(t, a1)
197 defer ipc.Shutdown()
198
199 ui := new(cli.MockUi)
200 c := &MembersCommand{Ui: ui}
201 args := []string{
202 "-rpc-addr=" + rpcAddr,
203 "-tag=tag1=foo",
204 "-tag=tag2=nomatch",
205 }
206
207 code := c.Run(args)
208 if code != 0 {
209 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
210 }
211
212 if strings.Contains(ui.OutputWriter.String(), a1.SerfConfig().NodeName) {
213 t.Fatalf("bad: %#v", ui.OutputWriter.String())
214 }
215 }
0 package command
1
2 import (
3 "flag"
4 "fmt"
5 "github.com/hashicorp/logutils"
6 "github.com/mitchellh/cli"
7 "strings"
8 "sync"
9 )
10
11 // MonitorCommand is a Command implementation that queries a running
12 // Serf agent what members are part of the cluster currently.
13 type MonitorCommand struct {
14 ShutdownCh <-chan struct{}
15 Ui cli.Ui
16
17 lock sync.Mutex
18 quitting bool
19 }
20
21 func (c *MonitorCommand) Help() string {
22 helpText := `
23 Usage: serf monitor [options]
24
25 Shows recent log messages of a Serf agent, and attaches to the agent,
26 outputting log messages as they occur in real time. The monitor lets you
27 listen for log levels that may be filtered out of the Serf agent. For
28 example your agent may only be logging at INFO level, but with the monitor
29 you can see the DEBUG level logs.
30
31 Options:
32
33 -log-level=info Log level of the agent.
34 -rpc-addr=127.0.0.1:7373 RPC address of the Serf agent.
35 -rpc-auth="" RPC auth token of the Serf agent.
36 `
37 return strings.TrimSpace(helpText)
38 }
39
40 func (c *MonitorCommand) Run(args []string) int {
41 var logLevel string
42 cmdFlags := flag.NewFlagSet("monitor", flag.ContinueOnError)
43 cmdFlags.Usage = func() { c.Ui.Output(c.Help()) }
44 cmdFlags.StringVar(&logLevel, "log-level", "INFO", "log level")
45 rpcAddr := RPCAddrFlag(cmdFlags)
46 rpcAuth := RPCAuthFlag(cmdFlags)
47 if err := cmdFlags.Parse(args); err != nil {
48 return 1
49 }
50
51 client, err := RPCClient(*rpcAddr, *rpcAuth)
52 if err != nil {
53 c.Ui.Error(fmt.Sprintf("Error connecting to Serf agent: %s", err))
54 return 1
55 }
56 defer client.Close()
57
58 eventCh := make(chan map[string]interface{}, 1024)
59 streamHandle, err := client.Stream("*", eventCh)
60 if err != nil {
61 c.Ui.Error(fmt.Sprintf("Error starting stream: %s", err))
62 return 1
63 }
64 defer client.Stop(streamHandle)
65
66 logCh := make(chan string, 1024)
67 monHandle, err := client.Monitor(logutils.LogLevel(logLevel), logCh)
68 if err != nil {
69 c.Ui.Error(fmt.Sprintf("Error starting monitor: %s", err))
70 return 1
71 }
72 defer client.Stop(monHandle)
73
74 eventDoneCh := make(chan struct{})
75 go func() {
76 defer close(eventDoneCh)
77 OUTER:
78 for {
79 select {
80 case log := <-logCh:
81 if log == "" {
82 break OUTER
83 }
84 c.Ui.Info(log)
85 case event := <-eventCh:
86 if event == nil {
87 break OUTER
88 }
89 c.Ui.Info("Event Info:")
90 for key, val := range event {
91 c.Ui.Info(fmt.Sprintf("\t%s: %#v", key, val))
92 }
93 }
94 }
95
96 c.lock.Lock()
97 defer c.lock.Unlock()
98 if !c.quitting {
99 c.Ui.Info("")
100 c.Ui.Output("Remote side ended the monitor! This usually means that the\n" +
101 "remote side has exited or crashed.")
102 }
103 }()
104
105 select {
106 case <-eventDoneCh:
107 return 1
108 case <-c.ShutdownCh:
109 c.lock.Lock()
110 c.quitting = true
111 c.lock.Unlock()
112 }
113
114 return 0
115 }
116
117 func (c *MonitorCommand) Synopsis() string {
118 return "Stream logs from a Serf agent"
119 }
0 package command
1
2 import (
3 "encoding/json"
4 "fmt"
5 "strings"
6 )
7
8 // Format some raw data for output. For better or worse, this currently forces
9 // the passed data object to implement fmt.Stringer, since it's pretty hard to
10 // implement a canonical *-to-string function.
11 func formatOutput(data interface{}, format string) ([]byte, error) {
12 var out string
13
14 switch format {
15
16 case "json":
17 jsonout, err := json.MarshalIndent(data, "", " ")
18 if err != nil {
19 return nil, err
20 }
21 out = string(jsonout)
22
23 case "text":
24 out = data.(fmt.Stringer).String()
25
26 default:
27 return nil, fmt.Errorf("Invalid output format \"%s\"", format)
28
29 }
30 return []byte(prepareOutput(out)), nil
31 }
32
33 // Apply some final formatting to make sure we don't end up with extra newlines
34 func prepareOutput(in string) string {
35 return strings.TrimSpace(string(in))
36 }
0 package command
1
2 import (
3 "fmt"
4 "testing"
5 )
6
7 type OutputTest struct {
8 XMLName string `json:"-"`
9 TestString string `json:"test_string"`
10 TestInt int `json:"test_int"`
11 TestNil []byte `json:"test_nil"`
12 TestNested OutputTestNested `json:"nested"`
13 }
14
15 type OutputTestNested struct {
16 NestKey string `json:"nest_key"`
17 }
18
19 func (o OutputTest) String() string {
20 return fmt.Sprintf("%s %d %s", o.TestString, o.TestInt, o.TestNil)
21 }
22
23 func TestCommandOutput(t *testing.T) {
24 var formatted []byte
25 result := OutputTest{
26 TestString: "woooo a string",
27 TestInt: 77,
28 TestNil: nil,
29 TestNested: OutputTestNested{
30 NestKey: "nest_value",
31 },
32 }
33
34 json_expected := `{
35 "test_string": "woooo a string",
36 "test_int": 77,
37 "test_nil": null,
38 "nested": {
39 "nest_key": "nest_value"
40 }
41 }`
42 formatted, _ = formatOutput(result, "json")
43 if string(formatted) != json_expected {
44 t.Fatalf("bad json:\n%s\n\nexpected:\n%s", formatted, json_expected)
45 }
46
47 text_expected := "woooo a string 77"
48 formatted, _ = formatOutput(result, "text")
49 if string(formatted) != text_expected {
50 t.Fatalf("bad output:\n\"%s\"\n\nexpected:\n\"%s\"", formatted, text_expected)
51 }
52
53 error_expected := `Invalid output format "boo"`
54 _, err := formatOutput(result, "boo")
55 if err.Error() != error_expected {
56 t.Fatalf("bad output:\n\"%s\"\n\nexpected:\n\"%s\"", err.Error(), error_expected)
57 }
58 }
0 package command
1
2 import (
3 "flag"
4 "fmt"
5 "github.com/hashicorp/serf/client"
6 "github.com/hashicorp/serf/command/agent"
7 "github.com/mitchellh/cli"
8 "strings"
9 "time"
10 )
11
12 // QueryCommand is a Command implementation that is used to trigger a new
13 // query and wait for responses and acks
14 type QueryCommand struct {
15 ShutdownCh <-chan struct{}
16 Ui cli.Ui
17 }
18
19 func (c *QueryCommand) Help() string {
20 helpText := `
21 Usage: serf query [options] name payload
22
23 Dispatches a query to the Serf cluster.
24
25 Options:
26
27 -format If provided, output is returned in the specified
28 format. Valid formats are 'json', and 'text' (default)
29
30 -node=NAME This flag can be provided multiple times to filter
31 responses to only named nodes.
32
33 -tag key=regexp This flag can be provided multiple times to filter
34 responses to only nodes matching the tags
35
36 -timeout="15s" Providing a timeout overrides the default timeout
37
38 -no-ack Setting this prevents nodes from sending an acknowledgement
39 of the query
40
41 -rpc-addr=127.0.0.1:7373 RPC address of the Serf agent.
42
43 -rpc-auth="" RPC auth token of the Serf agent.
44 `
45 return strings.TrimSpace(helpText)
46 }
47
48 func (c *QueryCommand) Run(args []string) int {
49 var noAck bool
50 var nodes []string
51 var tags []string
52 var timeout time.Duration
53 var format string
54 cmdFlags := flag.NewFlagSet("event", flag.ContinueOnError)
55 cmdFlags.Usage = func() { c.Ui.Output(c.Help()) }
56 cmdFlags.Var((*agent.AppendSliceValue)(&nodes), "node", "node filter")
57 cmdFlags.Var((*agent.AppendSliceValue)(&tags), "tag", "tag filter")
58 cmdFlags.DurationVar(&timeout, "timeout", 0, "query timeout")
59 cmdFlags.BoolVar(&noAck, "no-ack", false, "no-ack")
60 cmdFlags.StringVar(&format, "format", "text", "output format")
61 rpcAddr := RPCAddrFlag(cmdFlags)
62 rpcAuth := RPCAuthFlag(cmdFlags)
63 if err := cmdFlags.Parse(args); err != nil {
64 return 1
65 }
66
67 // Setup the filter tags
68 filterTags, err := agent.UnmarshalTags(tags)
69 if err != nil {
70 c.Ui.Error(fmt.Sprintf("Error: %s", err))
71 return 1
72 }
73
74 args = cmdFlags.Args()
75 if len(args) < 1 {
76 c.Ui.Error("A query name must be specified.")
77 c.Ui.Error("")
78 c.Ui.Error(c.Help())
79 return 1
80 } else if len(args) > 2 {
81 c.Ui.Error("Too many command line arguments. Only a name and payload must be specified.")
82 c.Ui.Error("")
83 c.Ui.Error(c.Help())
84 return 1
85 }
86
87 name := args[0]
88 var payload []byte
89 if len(args) == 2 {
90 payload = []byte(args[1])
91 }
92
93 cl, err := RPCClient(*rpcAddr, *rpcAuth)
94 if err != nil {
95 c.Ui.Error(fmt.Sprintf("Error connecting to Serf agent: %s", err))
96 return 1
97 }
98 defer cl.Close()
99
100 // Setup the the response handler
101 var handler queryRespFormat
102 switch format {
103 case "text":
104 handler = &textQueryRespFormat{
105 ui: c.Ui,
106 name: name,
107 noAck: noAck,
108 }
109 case "json":
110 handler = &jsonQueryRespFormat{
111 ui: c.Ui,
112 Responses: make(map[string]string),
113 }
114 default:
115 c.Ui.Error(fmt.Sprintf("Invalid format: %s", format))
116 return 1
117 }
118
119 ackCh := make(chan string, 128)
120 respCh := make(chan client.NodeResponse, 128)
121
122 params := client.QueryParam{
123 FilterNodes: nodes,
124 FilterTags: filterTags,
125 RequestAck: !noAck,
126 Timeout: timeout,
127 Name: name,
128 Payload: payload,
129 AckCh: ackCh,
130 RespCh: respCh,
131 }
132 if err := cl.Query(&params); err != nil {
133 c.Ui.Error(fmt.Sprintf("Error sending query: %s", err))
134 return 1
135 }
136 handler.Started()
137
138 OUTER:
139 for {
140 select {
141 case a := <-ackCh:
142 if a == "" {
143 break OUTER
144 }
145 handler.AckReceived(a)
146
147 case r := <-respCh:
148 if r.From == "" {
149 break OUTER
150 }
151 handler.ResponseReceived(r)
152
153 case <-c.ShutdownCh:
154 return 1
155 }
156 }
157
158 if err := handler.Finished(); err != nil {
159 return 1
160 }
161 return 0
162 }
163
164 func (c *QueryCommand) Synopsis() string {
165 return "Send a query to the Serf cluster"
166 }
167
168 // queryRespFormat is used to switch our handler based on the format
169 type queryRespFormat interface {
170 Started()
171 AckReceived(from string)
172 ResponseReceived(resp client.NodeResponse)
173 Finished() error
174 }
175
176 // textQueryRespFormat is used to output the results in a human-readable
177 // format that is streamed as results come in
178 type textQueryRespFormat struct {
179 ui cli.Ui
180 name string
181 noAck bool
182 numAcks int
183 numResp int
184 }
185
186 func (t *textQueryRespFormat) Started() {
187 t.ui.Output(fmt.Sprintf("Query '%s' dispatched", t.name))
188 }
189
190 func (t *textQueryRespFormat) AckReceived(from string) {
191 t.numAcks++
192 t.ui.Info(fmt.Sprintf("Ack from '%s'", from))
193 }
194
195 func (t *textQueryRespFormat) ResponseReceived(r client.NodeResponse) {
196 t.numResp++
197
198 // Remove the trailing newline if there is one
199 payload := r.Payload
200 if n := len(payload); n > 0 && payload[n-1] == '\n' {
201 payload = payload[:n-1]
202 }
203
204 t.ui.Info(fmt.Sprintf("Response from '%s': %s", r.From, payload))
205 }
206
207 func (t *textQueryRespFormat) Finished() error {
208 if !t.noAck {
209 t.ui.Output(fmt.Sprintf("Total Acks: %d", t.numAcks))
210 }
211 t.ui.Output(fmt.Sprintf("Total Responses: %d", t.numResp))
212 return nil
213 }
214
215 // jsonQueryRespFormat is used to output the results in a JSON format
216 type jsonQueryRespFormat struct {
217 ui cli.Ui
218 Acks []string
219 Responses map[string]string
220 }
221
222 func (j *jsonQueryRespFormat) Started() {}
223
224 func (j *jsonQueryRespFormat) AckReceived(from string) {
225 j.Acks = append(j.Acks, from)
226 }
227
228 func (j *jsonQueryRespFormat) ResponseReceived(r client.NodeResponse) {
229 j.Responses[r.From] = string(r.Payload)
230 }
231
232 func (j *jsonQueryRespFormat) Finished() error {
233 output, err := formatOutput(j, "json")
234 if err != nil {
235 j.ui.Error(fmt.Sprintf("Encoding error: %s", err))
236 return err
237 }
238 j.ui.Output(string(output))
239 return nil
240 }
0 package command
1
2 import (
3 "encoding/json"
4 "github.com/mitchellh/cli"
5 "strings"
6 "testing"
7 )
8
9 func TestQUeryCommand_implements(t *testing.T) {
10 var _ cli.Command = &QueryCommand{}
11 }
12
13 func TestQueryCommandRun_noName(t *testing.T) {
14 ui := new(cli.MockUi)
15 c := &QueryCommand{Ui: ui}
16 args := []string{"-rpc-addr=foo"}
17
18 code := c.Run(args)
19 if code != 1 {
20 t.Fatalf("bad: %d", code)
21 }
22
23 if !strings.Contains(ui.ErrorWriter.String(), "query name") {
24 t.Fatalf("bad: %#v", ui.ErrorWriter.String())
25 }
26 }
27
28 func TestQueryCommandRun_tooMany(t *testing.T) {
29 ui := new(cli.MockUi)
30 c := &QueryCommand{Ui: ui}
31 args := []string{"-rpc-addr=foo", "foo", "bar", "baz"}
32
33 code := c.Run(args)
34 if code != 1 {
35 t.Fatalf("bad: %d", code)
36 }
37
38 if !strings.Contains(ui.ErrorWriter.String(), "Too many") {
39 t.Fatalf("bad: %#v", ui.ErrorWriter.String())
40 }
41 }
42
43 func TestQueryCommandRun(t *testing.T) {
44 a1 := testAgent(t)
45 defer a1.Shutdown()
46 rpcAddr, ipc := testIPC(t, a1)
47 defer ipc.Shutdown()
48
49 ui := new(cli.MockUi)
50 c := &QueryCommand{Ui: ui}
51 args := []string{"-rpc-addr=" + rpcAddr, "-timeout=500ms", "deploy", "abcd1234"}
52
53 code := c.Run(args)
54 if code != 0 {
55 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
56 }
57
58 if !strings.Contains(ui.OutputWriter.String(), a1.SerfConfig().NodeName) {
59 t.Fatalf("bad: %#v", ui.OutputWriter.String())
60 }
61 }
62
63 func TestQueryCommandRun_tagFilter(t *testing.T) {
64 a1 := testAgent(t)
65 defer a1.Shutdown()
66 rpcAddr, ipc := testIPC(t, a1)
67 defer ipc.Shutdown()
68
69 ui := new(cli.MockUi)
70 c := &QueryCommand{Ui: ui}
71 args := []string{
72 "-rpc-addr=" + rpcAddr,
73 "-tag=tag1=foo",
74 "foo",
75 }
76
77 code := c.Run(args)
78 if code != 0 {
79 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
80 }
81
82 if !strings.Contains(ui.OutputWriter.String(), a1.SerfConfig().NodeName) {
83 t.Fatalf("bad: %#v", ui.OutputWriter.String())
84 }
85 }
86
87 func TestQueryCommandRun_tagFilter_failed(t *testing.T) {
88 a1 := testAgent(t)
89 defer a1.Shutdown()
90 rpcAddr, ipc := testIPC(t, a1)
91 defer ipc.Shutdown()
92
93 ui := new(cli.MockUi)
94 c := &QueryCommand{Ui: ui}
95 args := []string{
96 "-rpc-addr=" + rpcAddr,
97 "-tag=tag1=nomatch",
98 "foo",
99 }
100
101 code := c.Run(args)
102 if code != 0 {
103 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
104 }
105
106 if strings.Contains(ui.OutputWriter.String(), a1.SerfConfig().NodeName) {
107 t.Fatalf("bad: %#v", ui.OutputWriter.String())
108 }
109 }
110
111 func TestQueryCommandRun_nodeFilter(t *testing.T) {
112 a1 := testAgent(t)
113 defer a1.Shutdown()
114 rpcAddr, ipc := testIPC(t, a1)
115 defer ipc.Shutdown()
116
117 ui := new(cli.MockUi)
118 c := &QueryCommand{Ui: ui}
119 args := []string{
120 "-rpc-addr=" + rpcAddr,
121 "-node", a1.SerfConfig().NodeName,
122 "foo",
123 }
124
125 code := c.Run(args)
126 if code != 0 {
127 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
128 }
129
130 if !strings.Contains(ui.OutputWriter.String(), a1.SerfConfig().NodeName) {
131 t.Fatalf("bad: %#v", ui.OutputWriter.String())
132 }
133 }
134
135 func TestQueryCommandRun_nodeFilter_failed(t *testing.T) {
136 a1 := testAgent(t)
137 defer a1.Shutdown()
138 rpcAddr, ipc := testIPC(t, a1)
139 defer ipc.Shutdown()
140
141 ui := new(cli.MockUi)
142 c := &QueryCommand{Ui: ui}
143 args := []string{
144 "-rpc-addr=" + rpcAddr,
145 "-node=whoisthis",
146 "foo",
147 }
148
149 code := c.Run(args)
150 if code != 0 {
151 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
152 }
153
154 if strings.Contains(ui.OutputWriter.String(), a1.SerfConfig().NodeName) {
155 t.Fatalf("bad: %#v", ui.OutputWriter.String())
156 }
157 }
158
159 func TestQueryCommandRun_formatJSON(t *testing.T) {
160 type output struct {
161 Acks []string
162 Responses map[string]string
163 }
164
165 a1 := testAgent(t)
166 defer a1.Shutdown()
167 rpcAddr, ipc := testIPC(t, a1)
168 defer ipc.Shutdown()
169
170 ui := new(cli.MockUi)
171 c := &QueryCommand{Ui: ui}
172 args := []string{"-rpc-addr=" + rpcAddr,
173 "-format=json",
174 "-timeout=500ms",
175 "deploy", "abcd1234"}
176
177 code := c.Run(args)
178 if code != 0 {
179 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
180 }
181
182 // Decode the output
183 dec := json.NewDecoder(ui.OutputWriter)
184 var out output
185 if err := dec.Decode(&out); err != nil {
186 t.Fatalf("Decode err: %v", err)
187 }
188
189 if out.Acks[0] != a1.SerfConfig().NodeName {
190 t.Fatalf("bad: %#v", out)
191 }
192 }
0 package command
1
2 import (
3 "flag"
4 "fmt"
5 "github.com/hashicorp/serf/client"
6 "github.com/hashicorp/serf/serf"
7 "github.com/mitchellh/cli"
8 "strings"
9 "time"
10 )
11
12 const (
13 tooManyAcks = `This could mean Serf is detecting false-failures due to a misconfiguration or network issue.`
14 tooFewAcks = `This could mean Serf gossip packets are being lost due to a misconfiguration or network issue.`
15 duplicateResponses = `Duplicate responses means there is a misconfiguration. Verify that node names are unique.`
16 troubleshooting = `
17 Troubleshooting tips:
18 * Ensure that the bind addr:port is accessible by all other nodes
19 * If an advertise address is set, ensure it routes to the bind address
20 * Check that no nodes are behind a NAT
21 * If nodes are behind firewalls or iptables, check that Serf traffic is permitted (UDP and TCP)
22 * Verify networking equipment is functional`
23 )
24
25 // ReachabilityCommand is a Command implementation that is used to trigger
26 // a new reachability test
27 type ReachabilityCommand struct {
28 ShutdownCh <-chan struct{}
29 Ui cli.Ui
30 }
31
32 func (c *ReachabilityCommand) Help() string {
33 helpText := `
34 Usage: serf reachability [options]
35
36 Tests the network reachability of this node
37
38 Options:
39
40 -rpc-addr=127.0.0.1:7373 RPC address of the Serf agent.
41 -rpc-auth="" RPC auth token of the Serf agent.
42 -verbose Verbose mode
43 `
44 return strings.TrimSpace(helpText)
45 }
46
47 func (c *ReachabilityCommand) Run(args []string) int {
48 var verbose bool
49 cmdFlags := flag.NewFlagSet("reachability", flag.ContinueOnError)
50 cmdFlags.Usage = func() { c.Ui.Output(c.Help()) }
51 cmdFlags.BoolVar(&verbose, "verbose", false, "verbose mode")
52 rpcAddr := RPCAddrFlag(cmdFlags)
53 rpcAuth := RPCAuthFlag(cmdFlags)
54 if err := cmdFlags.Parse(args); err != nil {
55 return 1
56 }
57
58 cl, err := RPCClient(*rpcAddr, *rpcAuth)
59 if err != nil {
60 c.Ui.Error(fmt.Sprintf("Error connecting to Serf agent: %s", err))
61 return 1
62 }
63 defer cl.Close()
64
65 ackCh := make(chan string, 128)
66
67 // Get the list of members
68 members, err := cl.Members()
69 if err != nil {
70 c.Ui.Error(fmt.Sprintf("Error getting members: %s", err))
71 return 1
72 }
73
74 // Get only the live members
75 liveMembers := make(map[string]struct{})
76 for _, m := range members {
77 if m.Status == "alive" {
78 liveMembers[m.Name] = struct{}{}
79 }
80 }
81 c.Ui.Output(fmt.Sprintf("Total members: %d, live members: %d", len(members), len(liveMembers)))
82
83 // Start the query
84 params := client.QueryParam{
85 RequestAck: true,
86 Name: serf.InternalQueryPrefix + "ping",
87 AckCh: ackCh,
88 }
89 if err := cl.Query(&params); err != nil {
90 c.Ui.Error(fmt.Sprintf("Error sending query: %s", err))
91 return 1
92 }
93 c.Ui.Output("Starting reachability test...")
94 start := time.Now()
95 last := time.Now()
96
97 // Track responses and acknowledgements
98 exit := 0
99 dups := false
100 numAcks := 0
101 acksFrom := make(map[string]struct{}, len(members))
102
103 OUTER:
104 for {
105 select {
106 case a := <-ackCh:
107 if a == "" {
108 break OUTER
109 }
110 if verbose {
111 c.Ui.Output(fmt.Sprintf("\tAck from '%s'", a))
112 }
113 numAcks++
114 if _, ok := acksFrom[a]; ok {
115 dups = true
116 c.Ui.Output(fmt.Sprintf("Duplicate response from '%v'", a))
117 }
118 acksFrom[a] = struct{}{}
119 last = time.Now()
120
121 case <-c.ShutdownCh:
122 c.Ui.Error("Test interrupted")
123 return 1
124 }
125 }
126
127 if verbose {
128 total := float64(time.Now().Sub(start)) / float64(time.Second)
129 timeToLast := float64(last.Sub(start)) / float64(time.Second)
130 c.Ui.Output(fmt.Sprintf("Query time: %0.2f sec, time to last response: %0.2f sec", total, timeToLast))
131 }
132
133 // Print troubleshooting info for duplicate responses
134 if dups {
135 c.Ui.Output(duplicateResponses)
136 exit = 1
137 }
138
139 n := len(liveMembers)
140 if numAcks == n {
141 c.Ui.Output("Successfully contacted all live nodes")
142
143 } else if numAcks > n {
144 c.Ui.Output("Received more acks than live nodes! Acks from non-live nodes:")
145 for m := range acksFrom {
146 if _, ok := liveMembers[m]; !ok {
147 c.Ui.Output(fmt.Sprintf("\t%s", m))
148 }
149 }
150 c.Ui.Output(tooManyAcks)
151 c.Ui.Output(troubleshooting)
152 return 1
153
154 } else if numAcks < n {
155 c.Ui.Output("Received less acks than live nodes! Missing acks from:")
156 for m := range liveMembers {
157 if _, ok := acksFrom[m]; !ok {
158 c.Ui.Output(fmt.Sprintf("\t%s", m))
159 }
160 }
161 c.Ui.Output(tooFewAcks)
162 c.Ui.Output(troubleshooting)
163 return 1
164 }
165 return exit
166 }
167
168 func (c *ReachabilityCommand) Synopsis() string {
169 return "Test network reachability"
170 }
0 package command
1
2 import (
3 "github.com/mitchellh/cli"
4 "strings"
5 "testing"
6 )
7
8 func TestReachabilityCommand_implements(t *testing.T) {
9 var _ cli.Command = &ReachabilityCommand{}
10 }
11
12 func TestReachabilityCommand_Run(t *testing.T) {
13 a1 := testAgent(t)
14 defer a1.Shutdown()
15 rpcAddr, ipc := testIPC(t, a1)
16 defer ipc.Shutdown()
17
18 ui := new(cli.MockUi)
19 c := &ReachabilityCommand{Ui: ui}
20 args := []string{"-rpc-addr=" + rpcAddr, "-verbose"}
21
22 code := c.Run(args)
23 if code != 0 {
24 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
25 }
26
27 if !strings.Contains(ui.OutputWriter.String(), a1.SerfConfig().NodeName) {
28 t.Fatalf("bad: %#v", ui.OutputWriter.String())
29 }
30 if !strings.Contains(ui.OutputWriter.String(), "Successfully") {
31 t.Fatalf("bad: %#v", ui.OutputWriter.String())
32 }
33 }
0 package command
1
2 import (
3 "flag"
4 "github.com/hashicorp/serf/client"
5 "os"
6 )
7
8 // RPCAddrFlag returns a pointer to a string that will be populated
9 // when the given flagset is parsed with the RPC address of the Serf.
10 func RPCAddrFlag(f *flag.FlagSet) *string {
11 defaultRpcAddr := os.Getenv("SERF_RPC_ADDR")
12 if defaultRpcAddr == "" {
13 defaultRpcAddr = "127.0.0.1:7373"
14 }
15
16 return f.String("rpc-addr", defaultRpcAddr,
17 "RPC address of the Serf agent")
18 }
19
20 // RPCAuthFlag returns a pointer to a string that will be populated
21 // when the given flagset is parsed with the RPC auth token of the Serf.
22 func RPCAuthFlag(f *flag.FlagSet) *string {
23 rpcAuth := os.Getenv("SERF_RPC_AUTH")
24 return f.String("rpc-auth", rpcAuth,
25 "RPC auth token of the Serf agent")
26 }
27
28 // RPCClient returns a new Serf RPC client with the given address.
29 func RPCClient(addr, auth string) (*client.RPCClient, error) {
30 config := client.Config{Addr: addr, AuthKey: auth}
31 return client.ClientFromConfig(&config)
32 }
0 package command
1
2 import (
3 "flag"
4 "fmt"
5 "github.com/hashicorp/serf/command/agent"
6 "github.com/mitchellh/cli"
7 "strings"
8 )
9
10 // TagsCommand is an interface to dynamically add or otherwise modify a
11 // running serf agent's tags.
12 type TagsCommand struct {
13 Ui cli.Ui
14 }
15
16 func (c *TagsCommand) Help() string {
17 helpText := `
18 Usage: serf tags [options] ...
19
20 Modifies tags on a running Serf agent.
21
22 Options:
23
24 -rpc-addr=127.0.0.1:7373 RPC Address of the Serf agent.
25 -rpc-auth="" RPC auth token of the Serf agent.
26 -set key=value Creates or modifies the value of a tag
27 -delete key Removes a tag, if present
28 `
29 return strings.TrimSpace(helpText)
30 }
31
32 func (c *TagsCommand) Run(args []string) int {
33 var tagPairs []string
34 var delTags []string
35 cmdFlags := flag.NewFlagSet("tags", flag.ContinueOnError)
36 cmdFlags.Usage = func() { c.Ui.Output(c.Help()) }
37 cmdFlags.Var((*agent.AppendSliceValue)(&tagPairs), "set",
38 "tag pairs, specified as key=value")
39 cmdFlags.Var((*agent.AppendSliceValue)(&delTags), "delete",
40 "tag keys to unset")
41 rpcAddr := RPCAddrFlag(cmdFlags)
42 rpcAuth := RPCAuthFlag(cmdFlags)
43 if err := cmdFlags.Parse(args); err != nil {
44 return 1
45 }
46
47 if len(tagPairs) == 0 && len(delTags) == 0 {
48 c.Ui.Output(c.Help())
49 return 1
50 }
51
52 client, err := RPCClient(*rpcAddr, *rpcAuth)
53 if err != nil {
54 c.Ui.Error(fmt.Sprintf("Error connecting to Serf agent: %s", err))
55 return 1
56 }
57 defer client.Close()
58
59 tags, err := agent.UnmarshalTags(tagPairs)
60 if err != nil {
61 c.Ui.Error(fmt.Sprintf("Error: %s", err))
62 return 1
63 }
64
65 if err := client.UpdateTags(tags, delTags); err != nil {
66 c.Ui.Error(fmt.Sprintf("Error setting tags: %s", err))
67 return 1
68 }
69
70 c.Ui.Output("Successfully updated agent tags")
71 return 0
72 }
73
74 func (c *TagsCommand) Synopsis() string {
75 return "Modify tags of a running Serf agent"
76 }
0 package command
1
2 import (
3 "github.com/hashicorp/serf/client"
4 "github.com/mitchellh/cli"
5 "strings"
6 "testing"
7 )
8
9 func TestTagsCommand_implements(t *testing.T) {
10 var _ cli.Command = &TagsCommand{}
11 }
12
13 func TestTagsCommandRun(t *testing.T) {
14 a1 := testAgent(t)
15 defer a1.Shutdown()
16 rpcAddr, ipc := testIPC(t, a1)
17 defer ipc.Shutdown()
18
19 ui := new(cli.MockUi)
20 c := &TagsCommand{Ui: ui}
21 args := []string{
22 "-rpc-addr=" + rpcAddr,
23 "-delete", "tag2",
24 "-set", "a=1",
25 "-set", "b=2",
26 }
27
28 code := c.Run(args)
29 if code != 0 {
30 t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
31 }
32
33 if !strings.Contains(ui.OutputWriter.String(), "Successfully updated agent tags") {
34 t.Fatalf("bad: %#v", ui.OutputWriter.String())
35 }
36
37 rpcClient, err := client.NewRPCClient(rpcAddr)
38 if err != nil {
39 t.Fatalf("err: %s", err)
40 }
41
42 mem, err := rpcClient.Members()
43 if err != nil {
44 t.Fatalf("err: %s", err)
45 }
46
47 if len(mem) != 1 {
48 t.Fatalf("bad: %v", mem)
49 }
50
51 m0 := mem[0]
52 if _, ok := m0.Tags["tag2"]; ok {
53 t.Fatalf("bad: %v", m0.Tags)
54 }
55 if _, ok := m0.Tags["a"]; !ok {
56 t.Fatalf("bad: %v", m0.Tags)
57 }
58 if _, ok := m0.Tags["b"]; !ok {
59 t.Fatalf("bad: %v", m0.Tags)
60 }
61 }
0 package command
1
2 import (
3 "fmt"
4 "github.com/hashicorp/serf/command/agent"
5 "github.com/hashicorp/serf/serf"
6 "github.com/hashicorp/serf/testutil"
7 "io"
8 "math/rand"
9 "net"
10 "os"
11 "testing"
12 "time"
13 )
14
15 func init() {
16 // Seed the random number generator
17 rand.Seed(time.Now().UnixNano())
18 }
19
20 func testAgent(t *testing.T) *agent.Agent {
21 agentConfig := agent.DefaultConfig()
22 serfConfig := serf.DefaultConfig()
23 return testAgentWithConfig(t, agentConfig, serfConfig)
24 }
25
26 func testAgentWithConfig(t *testing.T, agentConfig *agent.Config,
27 serfConfig *serf.Config) *agent.Agent {
28
29 serfConfig.MemberlistConfig.BindAddr = testutil.GetBindAddr().String()
30 serfConfig.MemberlistConfig.ProbeInterval = 50 * time.Millisecond
31 serfConfig.MemberlistConfig.ProbeTimeout = 25 * time.Millisecond
32 serfConfig.MemberlistConfig.SuspicionMult = 1
33 serfConfig.NodeName = serfConfig.MemberlistConfig.BindAddr
34 serfConfig.Tags = map[string]string{"role": "test", "tag1": "foo", "tag2": "bar"}
35
36 agent, err := agent.Create(agentConfig, serfConfig, nil)
37 if err != nil {
38 t.Fatalf("err: %s", err)
39 }
40
41 if err := agent.Start(); err != nil {
42 t.Fatalf("err: %s", err)
43 }
44
45 return agent
46 }
47
48 func getRPCAddr() string {
49 for i := 0; i < 500; i++ {
50 l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", rand.Int31n(25000)+1024))
51 if err == nil {
52 l.Close()
53 return l.Addr().String()
54 }
55 }
56
57 panic("no listener")
58 }
59
60 func testIPC(t *testing.T, a *agent.Agent) (string, *agent.AgentIPC) {
61 rpcAddr := getRPCAddr()
62
63 l, err := net.Listen("tcp", rpcAddr)
64 if err != nil {
65 t.Fatalf("err: %s", err)
66 }
67
68 lw := agent.NewLogWriter(512)
69 mult := io.MultiWriter(os.Stderr, lw)
70 ipc := agent.NewAgentIPC(a, "", l, mult, lw)
71 return rpcAddr, ipc
72 }
0 package command
1
2 import (
3 "bytes"
4 "fmt"
5 "github.com/hashicorp/serf/serf"
6 "github.com/mitchellh/cli"
7 )
8
9 // VersionCommand is a Command implementation prints the version.
10 type VersionCommand struct {
11 Revision string
12 Version string
13 VersionPrerelease string
14 Ui cli.Ui
15 }
16
17 func (c *VersionCommand) Help() string {
18 return ""
19 }
20
21 func (c *VersionCommand) Run(_ []string) int {
22 var versionString bytes.Buffer
23 fmt.Fprintf(&versionString, "Serf v%s", c.Version)
24 if c.VersionPrerelease != "" {
25 fmt.Fprintf(&versionString, ".%s", c.VersionPrerelease)
26
27 if c.Revision != "" {
28 fmt.Fprintf(&versionString, " (%s)", c.Revision)
29 }
30 }
31
32 c.Ui.Output(versionString.String())
33 c.Ui.Output(fmt.Sprintf("Agent Protocol: %d (Understands back to: %d)",
34 serf.ProtocolVersionMax, serf.ProtocolVersionMin))
35 return 0
36 }
37
38 func (c *VersionCommand) Synopsis() string {
39 return "Prints the Serf version"
40 }
0 package command
1
2 import (
3 "github.com/mitchellh/cli"
4 "testing"
5 )
6
7 func TestVersionCommand_implements(t *testing.T) {
8 var _ cli.Command = &VersionCommand{}
9 }
0 package main
1
2 import (
3 "github.com/hashicorp/serf/command"
4 "github.com/hashicorp/serf/command/agent"
5 "github.com/mitchellh/cli"
6 "os"
7 "os/signal"
8 )
9
10 // Commands is the mapping of all the available Serf commands.
11 var Commands map[string]cli.CommandFactory
12
13 func init() {
14 ui := &cli.BasicUi{Writer: os.Stdout}
15
16 Commands = map[string]cli.CommandFactory{
17 "agent": func() (cli.Command, error) {
18 return &agent.Command{
19 Ui: ui,
20 ShutdownCh: make(chan struct{}),
21 }, nil
22 },
23
24 "event": func() (cli.Command, error) {
25 return &command.EventCommand{
26 Ui: ui,
27 }, nil
28 },
29
30 "query": func() (cli.Command, error) {
31 return &command.QueryCommand{
32 ShutdownCh: makeShutdownCh(),
33 Ui: ui,
34 }, nil
35 },
36
37 "force-leave": func() (cli.Command, error) {
38 return &command.ForceLeaveCommand{
39 Ui: ui,
40 }, nil
41 },
42
43 "join": func() (cli.Command, error) {
44 return &command.JoinCommand{
45 Ui: ui,
46 }, nil
47 },
48
49 "keygen": func() (cli.Command, error) {
50 return &command.KeygenCommand{
51 Ui: ui,
52 }, nil
53 },
54
55 "keys": func() (cli.Command, error) {
56 return &command.KeysCommand{
57 Ui: ui,
58 }, nil
59 },
60
61 "leave": func() (cli.Command, error) {
62 return &command.LeaveCommand{
63 Ui: ui,
64 }, nil
65 },
66
67 "members": func() (cli.Command, error) {
68 return &command.MembersCommand{
69 Ui: ui,
70 }, nil
71 },
72
73 "monitor": func() (cli.Command, error) {
74 return &command.MonitorCommand{
75 ShutdownCh: makeShutdownCh(),
76 Ui: ui,
77 }, nil
78 },
79
80 "tags": func() (cli.Command, error) {
81 return &command.TagsCommand{
82 Ui: ui,
83 }, nil
84 },
85
86 "reachability": func() (cli.Command, error) {
87 return &command.ReachabilityCommand{
88 ShutdownCh: makeShutdownCh(),
89 Ui: ui,
90 }, nil
91 },
92
93 "info": func() (cli.Command, error) {
94 return &command.InfoCommand{
95 Ui: ui,
96 }, nil
97 },
98
99 "version": func() (cli.Command, error) {
100 return &command.VersionCommand{
101 Revision: GitCommit,
102 Version: Version,
103 VersionPrerelease: VersionPrerelease,
104 Ui: ui,
105 }, nil
106 },
107 }
108 }
109
110 // makeShutdownCh returns a channel that can be used for shutdown
111 // notifications for commands. This channel will send a message for every
112 // interrupt received.
113 func makeShutdownCh() <-chan struct{} {
114 resultCh := make(chan struct{})
115
116 signalCh := make(chan os.Signal, 4)
117 signal.Notify(signalCh, os.Interrupt)
118 go func() {
119 for {
120 <-signalCh
121 resultCh <- struct{}{}
122 }
123 }()
124
125 return resultCh
126 }
0 # Vagrant Serf Demo
1
2 This demo provides a very simple Vagrantfile that creates two nodes,
3 one at "172.20.20.10" and another at "172.20.20.11". Both are running
4 a standard Ubuntu 12.04 distribution, and Serf is pre-installed.
5
6 To get started, you can start the cluster by just doing:
7
8 $ vagrant up
9
10 Once it is finished, you should be able to see the following:
11
12 $ vagrant status
13 Current machine states:
14 n1 running (vmware_fusion)
15 n2 running (vmware_fusion)
16
17 At this point the two nodes are running and you can SSH in to play with them:
18
19 $ vagrant ssh n1
20 ...
21 $ vagrant ssh n2
22 ...
23
24 To learn more about starting serf, joining nodes and interacting with the agent,
25 checkout the [getting started guide](http://www.serfdom.io/intro/getting-started/install.html).
26
0 # -*- mode: ruby -*-
1 # vi: set ft=ruby :
2 $script = <<SCRIPT
3
4 echo Installing depedencies...
5 sudo apt-get install -y unzip
6
7 echo Fetching Serf...
8 cd /tmp/
9 wget https://dl.bintray.com/mitchellh/serf/0.6.4_linux_amd64.zip -O serf.zip
10
11 echo Installing Serf...
12 unzip serf.zip
13 sudo chmod +x serf
14 sudo mv serf /usr/bin/serf
15
16 SCRIPT
17
18 # Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
19 VAGRANTFILE_API_VERSION = "2"
20
21 Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
22 config.vm.box = "hashicorp/precise64"
23
24 config.vm.provision "shell", inline: $script
25
26 config.vm.define "n1" do |n1|
27 n1.vm.network "private_network", ip: "172.20.20.10"
28 end
29
30 config.vm.define "n2" do |n2|
31 n2.vm.network "private_network", ip: "172.20.20.11"
32 end
33 end
0 # Serf Demo: Web Servers + Load Balancer
1
2 In this demo, Serf is used to coordinate web servers entering a load
3 balancer. The nodes in this example can come up in any order and they'll
4 eventually converge to a running cluster. When visiting the load balancer,
5 you'll see each of the nodes visited in round-robin.
6
7 After telling the cluster to launch, no further human interaction is
8 required. The cluster will launch and orchestrate itself into a load
9 balanced web server setup.
10
11 The nodes are launched on AWS using CloudFormation and therefore will
12 incur a real cost. You can control this cost based on how many nodes
13 you decide to launch.
14
15 ## Running the Demo
16
17 To run the demo, log into your AWS console, go to the CloudFormation
18 tab, and create a new stack using the "cloudformation.json" file in this
19 directory.
20
21 Configure how many web nodes you'd like to launch then create the stack.
22 When the stack is finished creating, the load balancer IP will be in
23 the "outputs" tab of that stack.
24
25 Visit `http://IP:9999/` to see the HAProxy stats page and list of
26 configured backends. Or visit the IP directly in your browser to see
27 it round robin between the web nodes.
28
29 It will take some time for the load balancer and web servers to install
30 their software and configure themselves. If the load balancer stats page
31 is not up yet, give it a few seconds and try again. Once it is up, it
32 will automatically refresh so you can watch as nodes eventually come
33 online.
34
35 ## How it Works
36
37 ### Node Setup
38
39 All the nodes (webs and load balancer) come up at the same time, with no
40 explicit ordering defined. If there is an order, it doesn't actually matter
41 for the function of the demo. The web servers can all come up before the load
42 balancer, the load balancer can come up in the middle of some web servers, etc.
43
44 Each of the nodes does an initial setup using a shell script. In a real
45 environment, this initial setup would probably be done with a real
46 configuration management system. For the purposes of this demo, we use
47 shell scripts for ease of understanding.
48
49 The load balancer runs `setup_load_balancer.sh` which installs HAProxy.
50 HAProxy is configured with no backend servers. Serf will handle this later.
51
52 The web server runs `setup_web_server.sh` which installs Apache and
53 sets up an index.html to just say what host it is.
54
55 At this stage, the load balancer is running HAProxy, and the web servers
56 are running Apache, but they're not aware of each other in any way.
57
58 Next, both nodes run `setup_serf.sh` which installs Serf and configures it
59 to run. This script also starts the Serf agent. For web servers, it also
60 starts a task that continuosly tries to join the Serf cluster at address
61 `10.0.0.5`, which happens to be the static IP assigned to the load balancer.
62 Note that Serf can join _any_ node in the cluster. We just set a static IP
63 on the load balancer for ease of automation, but in a real production
64 environment you can use any existing node, potentially just using DNS.
65
66 Web server roles also make use of a "serf-query" upstart task to dynamically
67 update the index page they serve. By using a "load" query, each node asks
68 the rest of the cluster for their current load. This information is used
69 to dynamically generate the index page.
70
71 ### Serf Configuration
72
73 The Serf configuration is very simple. Each node runs a Serf agent. The
74 web servers run the agent with a role of "web" and the load balancer runs
75 the agent with a role "lb".
76
77 #### Event: member-join
78
79 On member-join, the following shell script is run. We'll talk about the
80 function of the shell script after the code sample.
81
82 ```sh
83 if [ "x${SERF_TAG_ROLE}" != "xlb" ]; then
84 echo "Not an lb. Ignoring member join."
85 exit 0
86 fi
87
88 while read line; do
89 ROLE=`echo $line | awk '{print \$3 }'`
90 if [ "x${ROLE}" != "xweb" ]; then
91 continue
92 fi
93
94 echo $line | \
95 awk '{ printf " server %s %s check\n", $1, $2 }' >>/etc/haproxy/haproxy.cfg
96 done
97
98 /etc/init.d/haproxy reload
99 ```
100
101 The script first checks if it is a load balancer. If it isn't a load balancer,
102 we don't do anything on the "member-join" event. Web servers don't care about
103 other members.
104
105 Otherwise, if we are a load balancer, then it reads the member join data
106 and begins outputting configuration to HAProxy. In a production environment,
107 you might use something like a template processor to do this rather than
108 appending to the configuration. But this works as well.
109
110 Finally, HAProxy is reloaded so the configuration is read.
111
112 The result of this is that as members join, they're automatically added into
113 rotation on the load balancer.
114
115 #### Event: member-leave,member-failed
116
117 On member-leave and member-failed evets, the following shell script is run.
118 This demo doesn't differentiate between the two events, treating both
119 the same way and simply removing the node from the HAProxy configuration.
120
121 ```sh
122 if [ "x${SERF_TAG_ROLE}" != "xlb" ]; then
123 echo "Not an lb. Ignoring member leave"
124 exit 0
125 fi
126
127 while read line; do
128 NAME=`echo $line | awk '{print \$1 }'`
129 sed -i'' "/${NAME} /d" /etc/haproxy/haproxy.cfg
130 done
131
132 /etc/init.d/haproxy reload
133 ```
134
135 This script also does nothing if it is a web server. Otherwise, it just
136 uses `sed` to remove each of the nodes from the HAProxy configuration and
137 reloads it.
0 {
1 "AWSTemplateFormatVersion": "2010-09-09",
2
3 "Mappings" : {
4 "AWSNATAMI" : {
5 "us-east-1" : { "AMI" : "ami-c6699baf" },
6 "us-west-2" : { "AMI" : "ami-52ff7262" },
7 "us-west-1" : { "AMI" : "ami-3bcc9e7e" },
8 "eu-west-1" : { "AMI" : "ami-0b5b6c7f" },
9 "ap-southeast-1" : { "AMI" : "ami-02eb9350" },
10 "ap-southeast-2" : { "AMI" : "ami-ab990e91" },
11 "ap-northeast-1" : { "AMI" : "ami-14d86d15" },
12 "sa-east-1" : { "AMI" : "ami-0439e619" }
13 },
14
15 "AWSINSTAMI" : {
16 "us-east-1" : { "AMI" : "ami-a73264ce" },
17 "us-west-2" : { "AMI" : "ami-6aad335a" },
18 "us-west-1" : { "AMI" : "ami-acf9cde9" },
19 "eu-west-1" : { "AMI" : "ami-8e987ef9" },
20 "ap-southeast-1" : { "AMI" : "ami-b84e04ea" },
21 "ap-southeast-2" : { "AMI" : "ami-3d128f07" },
22 "ap-northeast-1" : { "AMI" : "ami-3f32ac3e" },
23 "sa-east-1" : { "AMI" : "ami-35258228" }
24 }
25 },
26
27 "Parameters": {
28 "WebNodes": {
29 "Type": "String",
30 "Default": "5",
31 "Description": "Number of web servers to launch."
32 }
33 },
34
35 "Outputs": {
36 "LoadBalancerIP": {
37 "Value": { "Ref": "LoadBalancerIP" }
38 }
39 },
40
41 "Resources": {
42 "Gateway": {
43 "Type": "AWS::EC2::InternetGateway"
44 },
45
46 "VPC": {
47 "Type": "AWS::EC2::VPC",
48 "Properties": {
49 "CidrBlock": "10.0.0.0/16",
50 "EnableDnsHostnames": true
51 }
52 },
53
54 "VPCGateway": {
55 "Type" : "AWS::EC2::VPCGatewayAttachment",
56 "Properties" : {
57 "InternetGatewayId" : { "Ref": "Gateway" },
58 "VpcId": { "Ref": "VPC" }
59 }
60 },
61
62 "PublicSubnet": {
63 "Type": "AWS::EC2::Subnet",
64 "Properties": {
65 "CidrBlock": "10.0.0.0/24",
66 "VpcId": { "Ref": "VPC" }
67 }
68 },
69
70 "PrivateSubnet": {
71 "Type": "AWS::EC2::Subnet",
72 "Properties": {
73 "CidrBlock": "10.0.1.0/24",
74 "VpcId": { "Ref": "VPC" }
75 }
76 },
77
78 "PrivateSubnetRoute": {
79 "Type" : "AWS::EC2::SubnetRouteTableAssociation",
80 "Properties" : {
81 "RouteTableId" : { "Ref": "PrivateRouteTable" },
82 "SubnetId" : { "Ref": "PrivateSubnet" }
83 }
84 },
85
86 "PrivateRouteTable": {
87 "Type": "AWS::EC2::RouteTable",
88 "Properties": {
89 "VpcId": { "Ref": "VPC" }
90 }
91 },
92
93 "PrivateRouteGlobal": {
94 "Type": "AWS::EC2::Route",
95 "Properties": {
96 "RouteTableId": { "Ref": "PrivateRouteTable" },
97 "DestinationCidrBlock": "0.0.0.0/0",
98 "InstanceId" : { "Ref" : "NATDevice" }
99 },
100 "DependsOn": "PublicRouteGlobal"
101 },
102
103 "PublicSubnetRoute": {
104 "Type" : "AWS::EC2::SubnetRouteTableAssociation",
105 "Properties" : {
106 "RouteTableId" : { "Ref": "PublicRouteTable" },
107 "SubnetId" : { "Ref": "PublicSubnet" }
108 }
109 },
110
111 "PublicRouteTable": {
112 "Type": "AWS::EC2::RouteTable",
113 "Properties": {
114 "VpcId": { "Ref": "VPC" }
115 }
116 },
117
118 "PublicRouteGlobal": {
119 "Type": "AWS::EC2::Route",
120 "Properties": {
121 "RouteTableId": { "Ref": "PublicRouteTable" },
122 "DestinationCidrBlock": "0.0.0.0/0",
123 "GatewayId": { "Ref": "Gateway" }
124 }
125 },
126
127 "NATIPAddress": {
128 "Type": "AWS::EC2::EIP",
129 "Properties": {
130 "Domain": "vpc",
131 "InstanceId": { "Ref": "NATDevice" }
132 },
133 "DependsOn": "VPCGateway"
134 },
135
136 "NATDevice" : {
137 "Type" : "AWS::EC2::Instance",
138 "Properties" : {
139 "SubnetId" : { "Ref" : "PublicSubnet" },
140 "SourceDestCheck" : "false",
141 "ImageId" : { "Fn::FindInMap" : [ "AWSNATAMI", { "Ref" : "AWS::Region" }, "AMI" ]},
142 "SecurityGroupIds" : [
143 { "Ref" : "InstanceSecurityGroup" }
144 ],
145 "Tags": [
146 { "Key": "Name", "Value": "Serf Demo NAT Device" }
147 ]
148 }
149 },
150
151 "LoadBalancer": {
152 "Type": "AWS::EC2::Instance",
153 "Properties": {
154 "ImageId": { "Fn::FindInMap" : [ "AWSINSTAMI", { "Ref" : "AWS::Region" }, "AMI" ]},
155 "PrivateIpAddress": "10.0.0.5",
156 "SecurityGroupIds": [
157 {"Ref": "InstanceSecurityGroup"}
158 ],
159 "SubnetId": { "Ref": "PublicSubnet" },
160 "Tags": [
161 { "Key": "Name", "Value": "Serf Demo LB" }
162 ],
163 "UserData": "IyEvYmluL2Jhc2gKTk9ERV9TRVRVUF9VUkw9Imh0dHBzOi8vcmF3LmdpdGh1Yi5jb20vaGFzaGljb3JwL3NlcmYvbWFzdGVyL2RlbW8vd2ViLWxvYWQtYmFsYW5jZXIvc2V0dXBfbG9hZF9iYWxhbmNlci5zaCIKClNFUkZfU0VUVVBfVVJMPSJodHRwczovL3Jhdy5naXRodWIuY29tL2hhc2hpY29ycC9zZXJmL21hc3Rlci9kZW1vL3dlYi1sb2FkLWJhbGFuY2VyL3NldHVwX3NlcmYuc2giCgojIFNldHVwIHRoZSBub2RlIGl0c2VsZgp3Z2V0IC1PIC0gJE5PREVfU0VUVVBfVVJMIHwgYmFzaAoKIyBTZXR1cCB0aGUgc2VyZiBhZ2VudApleHBvcnQgU0VSRl9ST0xFPSJsYiIKd2dldCAtTyAtICRTRVJGX1NFVFVQX1VSTCB8IGJhc2gK"
164 },
165 "DependsOn": "PublicRouteGlobal"
166 },
167
168 "LoadBalancerIP": {
169 "Type" : "AWS::EC2::EIP",
170 "Properties" : {
171 "InstanceId" : { "Ref": "LoadBalancer" },
172 "Domain" : "vpc"
173 },
174 "DependsOn": "VPCGateway"
175 },
176
177 "WebGroup": {
178 "Type": "AWS::AutoScaling::AutoScalingGroup",
179 "Properties": {
180 "AvailabilityZones": [
181 { "Fn::GetAtt" : [ "PrivateSubnet", "AvailabilityZone" ] }
182 ],
183 "LaunchConfigurationName": { "Ref": "WebLaunchConfig" },
184 "DesiredCapacity": { "Ref": "WebNodes" },
185 "MinSize": { "Ref": "WebNodes" },
186 "MaxSize": { "Ref": "WebNodes" },
187 "VPCZoneIdentifier": [
188 { "Ref": "PrivateSubnet" }
189 ]
190 },
191 "DependsOn": ["NATDevice", "NATIPAddress", "PrivateRouteGlobal"]
192 },
193
194 "WebLaunchConfig": {
195 "Type": "AWS::AutoScaling::LaunchConfiguration",
196 "Properties": {
197 "ImageId": { "Fn::FindInMap" : [ "AWSINSTAMI", { "Ref" : "AWS::Region" }, "AMI" ]},
198 "InstanceType": "m1.small",
199 "SecurityGroups": [
200 {"Ref": "InstanceSecurityGroup"}
201 ],
202 "UserData": "IyEvYmluL2Jhc2gKTk9ERV9TRVRVUF9VUkw9Imh0dHBzOi8vcmF3LmdpdGh1Yi5jb20vaGFzaGljb3JwL3NlcmYvbWFzdGVyL2RlbW8vd2ViLWxvYWQtYmFsYW5jZXIvc2V0dXBfd2ViX3NlcnZlci5zaCIKClNFUkZfU0VUVVBfVVJMPSJodHRwczovL3Jhdy5naXRodWIuY29tL2hhc2hpY29ycC9zZXJmL21hc3Rlci9kZW1vL3dlYi1sb2FkLWJhbGFuY2VyL3NldHVwX3NlcmYuc2giCgojIFNldHVwIHRoZSBub2RlIGl0c2VsZgp3Z2V0IC1PIC0gJE5PREVfU0VUVVBfVVJMIHwgYmFzaAoKIyBTZXR1cCB0aGUgc2VyZiBhZ2VudApleHBvcnQgU0VSRl9ST0xFPSJ3ZWIiCndnZXQgLU8gLSAkU0VSRl9TRVRVUF9VUkwgfCBiYXNoCg=="
203 }
204 },
205
206 "InstanceSecurityGroup": {
207 "Type": "AWS::EC2::SecurityGroup",
208 "Properties": {
209 "GroupDescription": "Serf demo security group",
210 "VpcId": { "Ref": "VPC" },
211 "SecurityGroupIngress": [{
212 "IpProtocol": "icmp",
213 "FromPort": "-1",
214 "ToPort": "-1",
215 "CidrIp": "0.0.0.0/0"
216 }, {
217 "IpProtocol": "tcp",
218 "FromPort": "22",
219 "ToPort": "22",
220 "CidrIp": "0.0.0.0/0"
221 }, {
222 "IpProtocol": "tcp",
223 "FromPort": "80",
224 "ToPort": "80",
225 "CidrIp": "0.0.0.0/0"
226 }, {
227 "IpProtocol": "tcp",
228 "FromPort": "9999",
229 "ToPort": "9999",
230 "CidrIp": "0.0.0.0/0"
231 }]
232 }
233 },
234
235 "InstanceSecurityGroupSelfRule": {
236 "Type": "AWS::EC2::SecurityGroupIngress",
237 "Properties": {
238 "GroupId": { "Ref": "InstanceSecurityGroup" },
239 "IpProtocol": "-1",
240 "FromPort": "0",
241 "ToPort": "65535",
242 "SourceSecurityGroupId": { "Ref": "InstanceSecurityGroup" }
243 }
244 }
245 }
246 }
0 #!/bin/sh
1 #
2 # This script sets up the HAProxy load balancer initially, configured with
3 # no working backend servers. Presumably in a real environment you would
4 # do this sort of setup with a real configuration management system. For
5 # this demo, however, this shell script will suffice.
6 #
7 set -e
8
9 # Install HAProxy
10 sudo apt-get update
11 sudo apt-get install -y haproxy
12
13 # Configure it in a jank way
14 cat <<EOF >/tmp/haproxy.cfg
15 global
16 daemon
17 maxconn 256
18
19 defaults
20 mode http
21 timeout connect 5000ms
22 timeout client 50000ms
23 timeout server 50000ms
24
25 listen stats
26 bind *:9999
27 mode http
28 stats enable
29 stats uri /
30 stats refresh 2s
31
32 listen http-in
33 bind *:80
34 balance roundrobin
35 option http-server-close
36 EOF
37 sudo mv /tmp/haproxy.cfg /etc/haproxy/haproxy.cfg
38
39 # Enable HAProxy
40 cat <<EOF >/tmp/haproxy
41 ENABLED=1
42 EOF
43 sudo mv /tmp/haproxy /etc/default/haproxy
44
45 # Start it
46 sudo /etc/init.d/haproxy start
0 #!/bin/sh
1 #
2 # This script installs and configures the Serf agent that runs on
3 # every node. As with the other scripts, this should probably be done with
4 # formal configuration management, but a shell script is simple as well.
5 #
6 # The SERF_ROLE environmental variable must be passed into this script
7 # in order to set the role of the machine. This should be either "lb" or
8 # "web".
9 #
10 set -e
11
12 sudo apt-get install -y unzip
13
14 # Download and install Serf
15 cd /tmp
16 until wget -O serf.zip https://dl.bintray.com/mitchellh/serf/0.6.4_linux_amd64.zip; do
17 sleep 1
18 done
19 unzip serf.zip
20 sudo mv serf /usr/local/bin/serf
21
22 # The member join script is invoked when a member joins the Serf cluster.
23 # Our join script simply adds the node to the load balancer.
24 cat <<EOF >/tmp/join.sh
25 if [ "x\${SERF_TAG_ROLE}" != "xlb" ]; then
26 echo "Not an lb. Ignoring member join."
27 exit 0
28 fi
29
30 while read line; do
31 ROLE=\`echo \$line | awk '{print \\\$3 }'\`
32 if [ "x\${ROLE}" != "xweb" ]; then
33 continue
34 fi
35
36 echo \$line | \\
37 awk '{ printf " server %s %s check\\n", \$1, \$2 }' >>/etc/haproxy/haproxy.cfg
38 done
39
40 /etc/init.d/haproxy reload
41 EOF
42 sudo mv /tmp/join.sh /usr/local/bin/serf_member_join.sh
43 chmod +x /usr/local/bin/serf_member_join.sh
44
45 # The member leave script is invoked when a member leaves or fails out
46 # of the serf cluster. Our script removes the node from the load balancer.
47 cat <<EOF >/tmp/leave.sh
48 if [ "x\${SERF_TAG_ROLE}" != "xlb" ]; then
49 echo "Not an lb. Ignoring member leave"
50 exit 0
51 fi
52
53 while read line; do
54 NAME=\`echo \$line | awk '{print \\\$1 }'\`
55 sed -i'' "/\${NAME} /d" /etc/haproxy/haproxy.cfg
56 done
57
58 /etc/init.d/haproxy reload
59 EOF
60 sudo mv /tmp/leave.sh /usr/local/bin/serf_member_left.sh
61 chmod +x /usr/local/bin/serf_member_left.sh
62
63 # Configure the agent
64 cat <<EOF >/tmp/agent.conf
65 description "Serf agent"
66
67 start on runlevel [2345]
68 stop on runlevel [!2345]
69
70 exec /usr/local/bin/serf agent \\
71 -event-handler "member-join=/usr/local/bin/serf_member_join.sh" \\
72 -event-handler "member-leave,member-failed=/usr/local/bin/serf_member_left.sh" \\
73 -event-handler "query:load=uptime" \\
74 -tag role=${SERF_ROLE} >>/var/log/serf.log 2>&1
75 EOF
76 sudo mv /tmp/agent.conf /etc/init/serf.conf
77
78 # Start the agent!
79 sudo start serf
80
81 # If we're the web node, then we need to configure the join retry
82 if [ "x${SERF_ROLE}" != "xweb" ]; then
83 exit 0
84 fi
85
86 cat <<EOF >/tmp/join.conf
87 description "Join the serf cluster"
88
89 start on runlevel [2345]
90 stop on runlevel [!2345]
91
92 task
93 respawn
94
95 script
96 sleep 5
97 exec /usr/local/bin/serf join 10.0.0.5
98 end script
99 EOF
100 sudo mv /tmp/join.conf /etc/init/serf-join.conf
101 sudo start serf-join
102
103 cat <<EOF >/tmp/query.conf
104 description "Query the serf cluster load"
105
106 start on runlevel [2345]
107 stop on runlevel [!2345]
108
109 respawn
110
111 script
112 echo `date` I am "${HOSTNAME}<br>" > /var/www/index.html.1
113 serf query -no-ack load | sed 's|$|<br>|' >> /var/www/index.html.1
114 mv /var/www/index.html.1 /var/www/index.html
115 sleep 10
116 end script
117 EOF
118 sudo mv /tmp/query.conf /etc/init/serf-query.conf
119 sudo start serf-query
0 #!/bin/sh
1 #
2 # This script sets up the web servers. The web servers just output who
3 # they are initially. Serf will do some additional configuration. Presumably
4 # in a real environment you would use a real configuration management system
5 # to do this. But for this demo a shell script is used for simplicity.
6 #
7 set -e
8
9 # Install apache2
10 sudo apt-get update
11 sudo apt-get install -y apache2
12
13 HOSTNAME=`hostname`
14 cat <<EOF >/tmp/index.html
15 I am "${HOSTNAME}"
16 EOF
17 sudo mv /tmp/index.html /var/www/index.html
0 This directory contains the user data that is used to initialize the
1 nodes that are started by CloudFormation.
0 #!/usr/bin/env python
1 import base64
2 import os
3 import os.path
4
5 # Get the file directory
6 fdir = os.path.dirname(os.path.realpath(__file__))
7
8 # Get the shell scripts
9 shell_scripts = [f for f in os.listdir(fdir) if f.endswith(".sh")]
10
11 # Read each and dump as base64
12 for sh in shell_scripts:
13 raw = open(os.path.join(fdir,sh)).read()
14 enc = base64.b64encode(raw)
15 print "Base64 for %s:" % sh
16 print enc
17 print
18
0 #!/bin/bash
1 NODE_SETUP_URL="https://raw.github.com/hashicorp/serf/master/demo/web-load-balancer/setup_load_balancer.sh"
2
3 SERF_SETUP_URL="https://raw.github.com/hashicorp/serf/master/demo/web-load-balancer/setup_serf.sh"
4
5 # Setup the node itself
6 wget -O - $NODE_SETUP_URL | bash
7
8 # Setup the serf agent
9 export SERF_ROLE="lb"
10 wget -O - $SERF_SETUP_URL | bash
0 #!/bin/bash
1 NODE_SETUP_URL="https://raw.github.com/hashicorp/serf/master/demo/web-load-balancer/setup_web_server.sh"
2
3 SERF_SETUP_URL="https://raw.github.com/hashicorp/serf/master/demo/web-load-balancer/setup_serf.sh"
4
5 # Setup the node itself
6 wget -O - $NODE_SETUP_URL | bash
7
8 # Setup the serf agent
9 export SERF_ROLE="web"
10 wget -O - $SERF_SETUP_URL | bash
0 {
1 "ImportPath": "github.com/hashicorp/serf",
2 "GoVersion": "go1.3rc1",
3 "Deps": [
4 {
5 "ImportPath": "code.google.com/p/go.net/ipv4",
6 "Comment": "null-81",
7 "Rev": "bc411e2ac33f17d301647c10ebc2c28a1fc5e8c8"
8 },
9 {
10 "ImportPath": "code.google.com/p/go.net/ipv6",
11 "Comment": "null-81",
12 "Rev": "bc411e2ac33f17d301647c10ebc2c28a1fc5e8c8"
13 },
14 {
15 "ImportPath": "github.com/armon/circbuf",
16 "Rev": "f092b4f207b6e5cce0569056fba9e1a2735cb6cf"
17 },
18 {
19 "ImportPath": "github.com/armon/go-metrics",
20 "Rev": "02567bbc4f518a43853d262b651a3c8257c3f141"
21 },
22 {
23 "ImportPath": "github.com/armon/mdns",
24 "Rev": "70462deb060d44247356ee238ebafd7699ddcffe"
25 },
26 {
27 "ImportPath": "github.com/hashicorp/go-syslog",
28 "Rev": "ac3963b72ac367e48b1e68a831e62b93fb69091c"
29 },
30 {
31 "ImportPath": "github.com/hashicorp/logutils",
32 "Rev": "8e0820fe7ac5eb2b01626b1d99df47c5449eb2d8"
33 },
34 {
35 "ImportPath": "github.com/hashicorp/memberlist",
36 "Rev": "17d39b695094be943bfb98442a80b082e6b9ac47"
37 },
38 {
39 "ImportPath": "github.com/miekg/dns",
40 "Rev": "05cfaca9f0712f44206ecbfa65a6769434164e7a"
41 },
42 {
43 "ImportPath": "github.com/mitchellh/cli",
44 "Rev": "975a7477b1507ea6bb888c48108e05d26fb30434"
45 },
46 {
47 "ImportPath": "github.com/mitchellh/mapstructure",
48 "Rev": "6fb2c832bcac61d01212ab1d172f7a14a8585b07"
49 },
50 {
51 "ImportPath": "github.com/ryanuber/columnize",
52 "Comment": "v2.0.1",
53 "Rev": "785d943a7b6886e0bb2f139a60487b823dd8d9de"
54 },
55 {
56 "ImportPath": "github.com/ugorji/go/codec",
57 "Rev": "71c2886f5a673a35f909803f38ece5810165097b"
58 }
59 ]
60 }
0 {
1 "ImportPath": "github.com/hashicorp/serf",
2 "GoVersion": "go1.3",
3 "Deps": [
4 {
5 "ImportPath": "code.google.com/p/go.net/ipv4",
6 "Comment": "null-81",
7 "Rev": "bc411e2ac33f17d301647c10ebc2c28a1fc5e8c8"
8 },
9 {
10 "ImportPath": "code.google.com/p/go.net/ipv6",
11 "Comment": "null-81",
12 "Rev": "bc411e2ac33f17d301647c10ebc2c28a1fc5e8c8"
13 },
14 {
15 "ImportPath": "github.com/armon/circbuf",
16 "Rev": "f092b4f207b6e5cce0569056fba9e1a2735cb6cf"
17 },
18 {
19 "ImportPath": "github.com/armon/go-metrics",
20 "Rev": "02567bbc4f518a43853d262b651a3c8257c3f141"
21 },
22 {
23 "ImportPath": "github.com/armon/mdns",
24 "Rev": "decec489d7885d111d55ea4d41581d0dfbd7f046"
25 },
26 {
27 "ImportPath": "github.com/hashicorp/go-syslog",
28 "Rev": "ac3963b72ac367e48b1e68a831e62b93fb69091c"
29 },
30 {
31 "ImportPath": "github.com/hashicorp/logutils",
32 "Rev": "8e0820fe7ac5eb2b01626b1d99df47c5449eb2d8"
33 },
34 {
35 "ImportPath": "github.com/hashicorp/memberlist",
36 "Rev": "e6a282556f0e8f15e9a53dcb0d14912a3c2fb141"
37 },
38 {
39 "ImportPath": "github.com/miekg/dns",
40 "Rev": "05cfaca9f0712f44206ecbfa65a6769434164e7a"
41 },
42 {
43 "ImportPath": "github.com/mitchellh/cli",
44 "Rev": "2aa2fb21524415a6476dae06d49223342366ff2f"
45 },
46 {
47 "ImportPath": "github.com/mitchellh/mapstructure",
48 "Rev": "6fb2c832bcac61d01212ab1d172f7a14a8585b07"
49 },
50 {
51 "ImportPath": "github.com/ryanuber/columnize",
52 "Comment": "v2.0.1",
53 "Rev": "785d943a7b6886e0bb2f139a60487b823dd8d9de"
54 },
55 {
56 "ImportPath": "github.com/ugorji/go/codec",
57 "Rev": "71c2886f5a673a35f909803f38ece5810165097b"
58 }
59 ]
60 }
0 package main
1
2 import (
3 "fmt"
4 "github.com/mitchellh/cli"
5 "io/ioutil"
6 "log"
7 "os"
8 )
9
10 func main() {
11 os.Exit(realMain())
12 }
13
14 func realMain() int {
15 log.SetOutput(ioutil.Discard)
16
17 // Get the command line args. We shortcut "--version" and "-v" to
18 // just show the version.
19 args := os.Args[1:]
20 for _, arg := range args {
21 if arg == "-v" || arg == "--version" {
22 newArgs := make([]string, len(args)+1)
23 newArgs[0] = "version"
24 copy(newArgs[1:], args)
25 args = newArgs
26 break
27 }
28 }
29
30 cli := &cli.CLI{
31 Args: args,
32 Commands: Commands,
33 HelpFunc: cli.BasicHelpFunc("serf"),
34 }
35
36 exitCode, err := cli.Run()
37 if err != nil {
38 fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", err.Error())
39 return 1
40 }
41
42 return exitCode
43 }
0 package main
0 # ops-misc
1
2 This folder is used to share files that are not part of the Serf binary,
3 but are useful for operational purposes. For example, upstart scripts.
4
5 ## Debian/Ubuntu package metadata
6
7 Move the ```debian``` directory to the root of the repo, and run ```dpkg-buildpackage```.
8
0 serf (0.6.0-0) unstable; urgency=low
1 FEATURES:
2
3 * Support for key rotation when using encryption. This adds a new
4 `serf keys` command, and a `-keyring-file` configuration. Thanks
5 to @ryanuber.
6
7 * New `-tags-file` can be specified to persist changes to tags made
8 via the RPC interface. Thanks to @ryanuber.
9
10 * New `serf info` command to provide operator debugging information,
11 and to get info about the local node.
12
13 * Adding `-retry-join` flag to agent which enables retrying the join
14 until success or `-retry-max` attempts have been made.
15
16 IMPROVEMENTS:
17
18 * New `-rejoin` flag can be used along with a snapshot file to
19 automatically rejoin a cluster.
20
21 * Agent uses circular buffer to invoke handlers, guards against unbounded
22 output lengths.
23
24 * Adding support for logging to syslog
25
26 * The SERF_RPC_ADDR environment variable can be used instead of the
27 `-rpc-addr` flags. Thanks to @lalyos [GH-209].
28
29 * `serf query` can now output the results in a JSON format.
30
31 * Unknown configuration directives generate an error [GH-186].
32 Thanks to @vincentbernat.
33
34 BUG FIXES:
35
36 * Fixing environmental variables with invalid characters. [GH-200].
37 Thanks to @arschles.
38
39 * Fixing issue with tag changes with hard restart before
40 failure detection.
41
42 * Fixing issue with reconnect when using dynamic ports.
43
44 MISC:
45
46 * Improved logging of various error messages
47
48 * Improved debian packaging. Thanks to @vincentbernat.
49
50 -- Ross Duggan <ross.duggan@acm.org> Wed, 14 May 2014 16:11:00 +0000
51
52 serf (0.5.0-0) unstable; urgency=low
53
54 * Initial deb package.
55
56 -- Derp Ston <derpston@sleepygeek.org> Wed, 12 Mar 2014 00:30:00 +0000
0 Source: serf
1 Section: net
2 Priority: optional
3 Maintainer: Derp Ston <derpston@sleepygeek.org>
4 Build-Depends:
5 debhelper (>= 7.0.50~)
6 Homepage: http://www.serfdom.io/
7
8 Package: serf
9 Architecture: amd64
10 Description: service orchestration and management tool
11 Serf is a service discovery and orchestration tool that is
12 decentralized, highly available, and fault tolerant.
0 Name: serf
1 Copyright: Hashicorp 2013
0 /usr/bin
1 /etc/serf
0 bin/serf /usr/bin/
1 debian/serf.json.example /etc/serf/
0 #!/bin/sh
1
2 set -e
3
4 case "$1" in
5 configure|reconfigure)
6 adduser --system --disabled-password --disabled-login --home /nonexistent \
7 --no-create-home --quiet --force-badname --group _serf
8 ;;
9 esac
10
11 #DEBHELPER#
12
13 exit 0
0 #!/bin/sh
1
2 set -e
3
4 #DEBHELPER#
5
6 case "$1" in
7 purge)
8 deluser --system _serf || true
9 delgroup --system _serf || true
10 ;;
11 *)
12 ;;
13 esac
14
15 exit 0
0 #!/usr/bin/make -f
1 # -*- makefile -*-
2
3 # Uncomment this to turn on verbose mode.
4 #export DH_VERBOSE=1
5 #export DH_OPTIONS=-v
6
7 %:
8 dh $@
9
10 override_dh_auto_test:
11
0 #! /bin/sh
1 ### BEGIN INIT INFO
2 # Provides: serf
3 # Required-Start: $network $remote_fs $syslog
4 # Required-Stop: $network $remote_fs $syslog
5 # Default-Start: 2 3 4 5
6 # Default-Stop: 0 1 6
7 # Short-Description: Serf Agent
8 # Description: Serf is a service discovery and orchestration tool that is
9 # decentralized, highly available, and fault tolerant.
10 ### END INIT INFO
11
12 # Author: Vincent Bernat <bernat@debian.org>
13
14 # Do NOT "set -e"
15
16 PATH=/sbin:/usr/sbin:/bin:/usr/bin
17 DESC="Serf Agent"
18 NAME=serf
19 USER=_serf
20 GROUP=_serf
21 DAEMON=/usr/bin/serf
22 DAEMON_ARGS="-config-dir /etc/serf"
23 PIDFILE=/var/run/$NAME.pid
24 SCRIPTNAME=/etc/init.d/$NAME
25
26 # Exit if the package is not installed
27 [ -x "$DAEMON" ] || exit 0
28
29 # Read configuration variable file if it is present
30 [ -r /etc/default/$NAME ] && . /etc/default/$NAME
31
32 # Load the VERBOSE setting and other rcS variables
33 . /lib/init/vars.sh
34
35 # Define LSB log_* functions.
36 . /lib/lsb/init-functions
37
38 #
39 # Function that starts the daemon/service
40 #
41 do_start()
42 {
43 # Return
44 # 0 if daemon has been started
45 # 1 if daemon was already running
46 # 2 if daemon could not be started
47 start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
48 || return 1
49 start-stop-daemon --start --quiet --make-pidfile --pidfile $PIDFILE --background \
50 --chuid $USER:$GROUP \
51 --exec $DAEMON -- \
52 agent $DAEMON_ARGS \
53 || return 2
54 }
55
56 #
57 # Function that stops the daemon/service
58 #
59 do_stop()
60 {
61 # Return
62 # 0 if daemon has been stopped
63 # 1 if daemon was already stopped
64 # 2 if daemon could not be stopped
65 # other if a failure occurred
66 start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
67 RETVAL="$?"
68 [ "$RETVAL" = 2 ] && return 2
69 start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
70 [ "$?" = 2 ] && return 2
71 rm -f $PIDFILE
72 return "$RETVAL"
73 }
74
75 #
76 # Function that sends a SIGHUP to the daemon/service
77 #
78 do_reload() {
79 start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
80 return 0
81 }
82
83 case "$1" in
84 start)
85 [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
86 do_start
87 case "$?" in
88 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
89 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
90 esac
91 ;;
92 stop)
93 [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
94 do_stop
95 case "$?" in
96 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
97 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
98 esac
99 ;;
100 status)
101 status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
102 ;;
103 reload|force-reload)
104 log_daemon_msg "Reloading $DESC" "$NAME"
105 do_reload
106 log_end_msg $?
107 ;;
108 restart)
109 log_daemon_msg "Restarting $DESC" "$NAME"
110 do_stop
111 case "$?" in
112 0|1)
113 do_start
114 case "$?" in
115 0) log_end_msg 0 ;;
116 1) log_end_msg 1 ;; # Old process is still running
117 *) log_end_msg 1 ;; # Failed to start
118 esac
119 ;;
120 *)
121 # Failed to stop
122 log_end_msg 1
123 ;;
124 esac
125 ;;
126 *)
127 echo "Usage: $SCRIPTNAME {start|stop|status|restart|reload|force-reload}" >&2
128 exit 3
129 ;;
130 esac
131
132 :
0 {
1 "interface": "eth0"
2 , "profile": "wan"
3 , "log_level": "warn"
4 , "tags": {}
5 }
0 # Serf Agent (Upstart unit)
1 description "Serf Agent"
2 start on (local-filesystems and net-device-up IFACE!=lo)
3 stop on runlevel [06]
4
5 env SERF=/usr/bin/serf
6 env SERFCONFIG=/etc/serf
7
8 setuid _serf
9 setgid _serf
10
11 # Serf emits log messages to stdout, and upstart will write that to
12 # /var/log/upstart/serf.log
13 exec $SERF agent -config-dir $SERFCONFIG
14
15 respawn
16 respawn limit 10 10
17 kill timeout 10
0 #!/bin/sh
1 #
2 # serf - this script starts and stops the serf service daemon
3 #
4 # chkconfig: 345 99 1
5 # description: serf
6 # processname: serf
7 # pidfile: /var/run/serf.pid
8
9 # Source function library.
10 . /etc/rc.d/init.d/functions
11
12 prog=serf
13 cmd="/usr/local/bin/${prog} agent"
14 pidfile="/var/run/${prog}.pid"
15 lockfile="/var/lock/subsys/${prog}"
16
17 SERF_CONFIG_DIR="/etc/serf"
18 SERF_LOG_FILE="/var/log/serf.log"
19
20 [[ -f /etc/sysconfig/serf ]] && . /etc/sysconfig/serf
21
22 [[ -d $SERF_CONFIG_DIR ]] || mkdir -p $SERF_CONFIG_DIR
23
24 start() {
25 echo -n $"Starting $prog: "
26 daemon --pidfile=$pidfile "${cmd} -config-dir=$SERF_CONFIG_DIR >> $SERF_LOG_FILE &"
27 retval=$?
28 ps -ef | grep -F "${cmd}" | grep -v 'grep' | awk '{print $2}' > ${pidfile}
29 [ $retval -eq 0 ] && touch $lockfile && success
30 echo
31 return $retval
32 }
33
34 stop() {
35 echo -n $"Stopping $prog: "
36 killproc -p $pidfile $prog
37 retval=$?
38 echo
39 [ $retval -eq 0 ] && rm -f $lockfile
40 return $retval
41 }
42
43 rh_status() {
44 status $prog
45 }
46
47 rh_status_q() {
48 rh_status >/dev/null 2>&1
49 }
50
51 restart() {
52 stop
53 start
54 }
55
56 case "$1" in
57 start)
58 rh_status_q && exit 0
59 $1
60 ;;
61 stop)
62 #rh_status_q && exit 0
63 $1
64 ;;
65 restart)
66 $1
67 ;;
68 status|status_q)
69 rh_$1
70 ;;
71 *)
72 echo "Usage: $0 {start|stop|restart|status}"
73 exit 2
74 esac
75 exit 0
0 # Serf Agent (systemd service unit)
1 [Unit]
2 Description=Serf Agent
3 After=syslog.target
4 After=network.target
5
6 [Service]
7 Type=simple
8 # Ensure the configuration directory exists.
9 ExecPreStart=/usr/bin/install -d -g root -o root /etc/serf/
10 ExecStart=/usr/local/bin/serf agent -config-dir=/etc/serf/
11 # Use SIGINT instead of SIGTERM so serf can depart the cluster.
12 KillSignal=SIGINT
13 # Restart on success, failure, and any emitted signals like HUP.
14 Restart=always
15 # Wait ten seconds before respawn attempts.
16 RestartSec=10
17
18 [Install]
19 WantedBy=multi-user.target
0 #!/bin/bash
1 #
2 # This script builds the application from source.
3 set -e
4
5 # Get the parent directory of where this script is.
6 SOURCE="${BASH_SOURCE[0]}"
7 while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done
8 DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )"
9
10 # Change into that directory
11 cd $DIR
12
13 # Get the git commit
14 GIT_COMMIT=$(git rev-parse HEAD)
15 GIT_DIRTY=$(test -n "`git status --porcelain`" && echo "+CHANGES" || true)
16
17 # If we're building on Windows, specify an extension
18 EXTENSION=""
19 if [ "$(go env GOOS)" = "windows" ]; then
20 EXTENSION=".exe"
21 fi
22
23 # Install dependencies
24 echo "--> Installing dependencies to speed up builds..."
25 go get ./...
26
27 # Build!
28 echo "--> Building..."
29 go build \
30 -ldflags "-X main.GitCommit ${GIT_COMMIT}${GIT_DIRTY}" \
31 -v \
32 -o bin/serf${EXTENSION}
33 cp bin/serf${EXTENSION} $(echo $GOPATH | sed -e 's/:.*//g')/bin
0 #!/bin/bash
1 set -e
2
3 # Get the parent directory of where this script is.
4 SOURCE="${BASH_SOURCE[0]}"
5 while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done
6 DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )"
7
8 # Change into that dir because we expect that
9 cd $DIR
10
11 # Determine the version that we're building based on the contents
12 # of packer/version.go.
13 VERSION=$(grep "const Version " version.go | sed -E 's/.*"(.+)"$/\1/')
14 VERSIONDIR="${VERSION}"
15 PREVERSION=$(grep "const VersionPrerelease " version.go | sed -E 's/.*"(.*)"$/\1/')
16 if [ ! -z $PREVERSION ]; then
17 PREVERSION="${PREVERSION}.$(date -u +%s)"
18 VERSIONDIR="${VERSIONDIR}-${PREVERSION}"
19 fi
20
21 echo "Version: ${VERSION} ${PREVERSION}"
22
23 # Determine the arch/os combos we're building for
24 XC_ARCH=${XC_ARCH:-"386 amd64 arm"}
25 XC_OS=${XC_OS:-linux darwin windows freebsd openbsd}
26
27 echo "Arch: ${XC_ARCH}"
28 echo "OS: ${XC_OS}"
29
30 # Make sure that if we're killed, we kill all our subprocseses
31 trap "kill 0" SIGINT SIGTERM EXIT
32
33 # This function builds whatever directory we're in...
34 goxc \
35 -arch="$XC_ARCH" \
36 -os="$XC_OS" \
37 -d="${DIR}/pkg" \
38 -pv="${VERSION}" \
39 -pr="${PREVERSION}" \
40 $XC_OPTS \
41 go-install \
42 xc
43
44 # Zip all the packages
45 mkdir -p ./pkg/${VERSIONDIR}/dist
46 for PLATFORM in $(find ./pkg/${VERSIONDIR} -mindepth 1 -maxdepth 1 -type d); do
47 PLATFORM_NAME=$(basename ${PLATFORM})
48 ARCHIVE_NAME="${VERSIONDIR}_${PLATFORM_NAME}"
49
50 if [ $PLATFORM_NAME = "dist" ]; then
51 continue
52 fi
53
54 pushd ${PLATFORM}
55 zip ${DIR}/pkg/${VERSIONDIR}/dist/${ARCHIVE_NAME}.zip ./*
56 popd
57 done
58
59 # Make the checksums
60 pushd ./pkg/${VERSIONDIR}/dist
61 shasum -a256 * > ./${VERSIONDIR}_SHA256SUMS
62 popd
63
64 exit 0
0 #!/bin/bash
1 #
2 # This script makes sure that 127.0.0.x is routable. On Darwin, there
3 # is a bug that it isn't routable and this causes errors.
4 #
5
6 # Check if loopback is setup
7 ping -c 1 -W 10 127.0.0.2 > /dev/null 2>&1
8 if [ $? -eq 0 ]
9 then
10 exit
11 fi
12
13 # If we're not on OS X, then error
14 case $OSTYPE in
15 darwin*)
16 ;;
17 *)
18 echo "Can't setup interfaces on non-Mac. Error!"
19 exit 1
20 ;;
21 esac
22
23 # Setup loopback
24 for ((i=2;i<256;i++))
25 do
26 sudo ifconfig lo0 alias 127.0.0.$i up
27 done
0 #!/bin/bash
1 SERF="./bin/serf"
2
3 # Set secret, blank for disabled
4 SECRET="1FzgH8LsTtr0Wopn4934OQ=="
5 #SECRET=""
6
7 # Set protocol version
8 PROTO="1"
9
10 for i in {0..99}
11 do
12 BIND=`expr 2 + $i`
13 PORT=`expr 7373 + $i`
14 echo Starting Serf agent $i on 127.0.0.1:$BIND, RPC on port $PORT
15 $SERF agent -node=node$i -rpc-addr=127.0.0.1:$PORT -bind=127.0.0.$BIND -log-level=warn -join=127.0.0.2 -encrypt=$SECRET -protocol=$PROTO &
16 if [ $i -eq 0 ]
17 then
18 sleep 1;
19 fi
20 done
0 #!/bin/bash
1 set -e
2
3 # Get the parent directory of where this script is.
4 SOURCE="${BASH_SOURCE[0]}"
5 while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done
6 DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )"
7
8 # Change into that dir because we expect that
9 cd $DIR
10
11 # Get the version from the command line
12 VERSION=$1
13 if [ -z $VERSION ]; then
14 echo "Please specify a version."
15 exit 1
16 fi
17
18 # Make sure we have a bintray API key
19 if [ -z $BINTRAY_API_KEY ]; then
20 echo "Please set your bintray API key in the BINTRAY_API_KEY env var."
21 exit 1
22 fi
23
24 for ARCHIVE in ./pkg/${VERSION}/dist/*; do
25 ARCHIVE_NAME=$(basename ${ARCHIVE})
26
27 echo Uploading: $ARCHIVE_NAME
28 curl \
29 -T ${ARCHIVE} \
30 -umitchellh:${BINTRAY_API_KEY} \
31 "https://api.bintray.com/content/mitchellh/serf/serf/${VERSION}/${ARCHIVE_NAME}"
32 done
0 #!/bin/bash
1
2 # Get the parent directory of where this script is.
3 SOURCE="${BASH_SOURCE[0]}"
4 while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done
5 DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )"
6
7 # Change into that directory
8 cd $DIR
9
10 # If bootstrap is on, this is the first push
11 if [ "${BOOTSTRAP}" != "" ]; then
12 heroku config:set BINTRAY_API_KEY=$BINTRAY_API_KEY
13 heroku config:add BUILDPACK_URL=https://github.com/ddollar/heroku-buildpack-multi.git
14 fi
15
16 # Push the subtree (force)
17 git push heroku `git subtree split --prefix website master`:master --force
0 #!/bin/bash
1
2 # Get the parent directory of where this script is.
3 SOURCE="${BASH_SOURCE[0]}"
4 while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done
5 DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )"
6
7 # Change into that directory
8 cd $DIR/website
9
10 # Run the website
11 echo
12 echo "=========== INSTALLING WEBSITE DEPS ==========="
13 echo
14 bundle
15
16 echo
17 echo "=========== STARTING WEBSITE ==========="
18 echo
19 bundle exec middleman server
0 package serf
1
2 import (
3 "github.com/hashicorp/memberlist"
4 )
5
6 // broadcast is an implementation of memberlist.Broadcast and is used
7 // to manage broadcasts across the memberlist channel that are related
8 // only to Serf.
9 type broadcast struct {
10 msg []byte
11 notify chan<- struct{}
12 }
13
14 func (b *broadcast) Invalidates(other memberlist.Broadcast) bool {
15 return false
16 }
17
18 func (b *broadcast) Message() []byte {
19 return b.msg
20 }
21
22 func (b *broadcast) Finished() {
23 if b.notify != nil {
24 close(b.notify)
25 }
26 }
0 package serf
1
2 import (
3 "github.com/hashicorp/memberlist"
4 "testing"
5 "time"
6 )
7
8 func TestBroadcast_Impl(t *testing.T) {
9 var _ memberlist.Broadcast = &broadcast{}
10 }
11
12 func TestBroadcastFinished(t *testing.T) {
13 t.Parallel()
14
15 ch := make(chan struct{})
16 b := &broadcast{notify: ch}
17 b.Finished()
18
19 select {
20 case <-ch:
21 case <-time.After(10 * time.Millisecond):
22 t.Fatalf("should have notified")
23 }
24 }
25
26 func TestBroadcastFinished_nilNotify(t *testing.T) {
27 t.Parallel()
28
29 b := &broadcast{notify: nil}
30 b.Finished()
31 }
0 package serf
1
2 import (
3 "time"
4 )
5
6 // coalescer is a simple interface that must be implemented to be
7 // used inside of a coalesceLoop
8 type coalescer interface {
9 // Can the coalescer handle this event, if not it is
10 // directly passed through to the destination channel
11 Handle(Event) bool
12
13 // Invoked to coalesce the given event
14 Coalesce(Event)
15
16 // Invoked to flush the coalesced events
17 Flush(outChan chan<- Event)
18 }
19
20 // coalescedEventCh returns an event channel where the events are coalesced
21 // using the given coalescer.
22 func coalescedEventCh(outCh chan<- Event, shutdownCh <-chan struct{},
23 cPeriod time.Duration, qPeriod time.Duration, c coalescer) chan<- Event {
24 inCh := make(chan Event, 1024)
25 go coalesceLoop(inCh, outCh, shutdownCh, cPeriod, qPeriod, c)
26 return inCh
27 }
28
29 // coalesceLoop is a simple long-running routine that manages the high-level
30 // flow of coalescing based on quiescence and a maximum quantum period.
31 func coalesceLoop(inCh <-chan Event, outCh chan<- Event, shutdownCh <-chan struct{},
32 coalescePeriod time.Duration, quiescentPeriod time.Duration, c coalescer) {
33 var quiescent <-chan time.Time
34 var quantum <-chan time.Time
35 shutdown := false
36
37 INGEST:
38 // Reset the timers
39 quantum = nil
40 quiescent = nil
41
42 for {
43 select {
44 case e := <-inCh:
45 // Ignore any non handled events
46 if !c.Handle(e) {
47 outCh <- e
48 continue
49 }
50
51 // Start a new quantum if we need to
52 // and restart the quiescent timer
53 if quantum == nil {
54 quantum = time.After(coalescePeriod)
55 }
56 quiescent = time.After(quiescentPeriod)
57
58 // Coalesce the event
59 c.Coalesce(e)
60
61 case <-quantum:
62 goto FLUSH
63 case <-quiescent:
64 goto FLUSH
65 case <-shutdownCh:
66 shutdown = true
67 goto FLUSH
68 }
69 }
70
71 FLUSH:
72 // Flush the coalesced events
73 c.Flush(outCh)
74
75 // Restart ingestion if we are not done
76 if !shutdown {
77 goto INGEST
78 }
79 }
0 package serf
1
2 type coalesceEvent struct {
3 Type EventType
4 Member *Member
5 }
6
7 type memberEventCoalescer struct {
8 lastEvents map[string]EventType
9 latestEvents map[string]coalesceEvent
10 }
11
12 func (c *memberEventCoalescer) Handle(e Event) bool {
13 switch e.EventType() {
14 case EventMemberJoin:
15 return true
16 case EventMemberLeave:
17 return true
18 case EventMemberFailed:
19 return true
20 case EventMemberUpdate:
21 return true
22 case EventMemberReap:
23 return true
24 default:
25 return false
26 }
27 }
28
29 func (c *memberEventCoalescer) Coalesce(raw Event) {
30 e := raw.(MemberEvent)
31 for _, m := range e.Members {
32 c.latestEvents[m.Name] = coalesceEvent{
33 Type: e.Type,
34 Member: &m,
35 }
36 }
37 }
38
39 func (c *memberEventCoalescer) Flush(outCh chan<- Event) {
40 // Coalesce the various events we got into a single set of events.
41 events := make(map[EventType]*MemberEvent)
42 for name, cevent := range c.latestEvents {
43 previous, ok := c.lastEvents[name]
44
45 // If we sent the same event before, then ignore
46 // unless it is a MemberUpdate
47 if ok && previous == cevent.Type && cevent.Type != EventMemberUpdate {
48 continue
49 }
50
51 // Update our last event
52 c.lastEvents[name] = cevent.Type
53
54 // Add it to our event
55 newEvent, ok := events[cevent.Type]
56 if !ok {
57 newEvent = &MemberEvent{Type: cevent.Type}
58 events[cevent.Type] = newEvent
59 }
60 newEvent.Members = append(newEvent.Members, *cevent.Member)
61 }
62
63 // Send out those events
64 for _, event := range events {
65 outCh <- *event
66 }
67 }
0 package serf
1
2 import (
3 "reflect"
4 "sort"
5 "testing"
6 "time"
7 )
8
9 func TestMemberEventCoalesce_Basic(t *testing.T) {
10 outCh := make(chan Event, 64)
11 shutdownCh := make(chan struct{})
12 defer close(shutdownCh)
13
14 c := &memberEventCoalescer{
15 lastEvents: make(map[string]EventType),
16 latestEvents: make(map[string]coalesceEvent),
17 }
18
19 inCh := coalescedEventCh(outCh, shutdownCh,
20 5*time.Millisecond, 5*time.Millisecond, c)
21
22 send := []Event{
23 MemberEvent{
24 Type: EventMemberJoin,
25 Members: []Member{Member{Name: "foo"}},
26 },
27 MemberEvent{
28 Type: EventMemberLeave,
29 Members: []Member{Member{Name: "foo"}},
30 },
31 MemberEvent{
32 Type: EventMemberLeave,
33 Members: []Member{Member{Name: "bar"}},
34 },
35 MemberEvent{
36 Type: EventMemberUpdate,
37 Members: []Member{Member{Name: "zip", Tags: map[string]string{"role": "foo"}}},
38 },
39 MemberEvent{
40 Type: EventMemberUpdate,
41 Members: []Member{Member{Name: "zip", Tags: map[string]string{"role": "bar"}}},
42 },
43 MemberEvent{
44 Type: EventMemberReap,
45 Members: []Member{Member{Name: "dead"}},
46 },
47 }
48
49 for _, e := range send {
50 inCh <- e
51 }
52
53 events := make(map[EventType]Event)
54 timeout := time.After(10 * time.Millisecond)
55
56 MEMBEREVENTFORLOOP:
57 for {
58 select {
59 case e := <-outCh:
60 events[e.EventType()] = e
61 case <-timeout:
62 break MEMBEREVENTFORLOOP
63 }
64 }
65
66 if len(events) != 3 {
67 t.Fatalf("bad: %#v", events)
68 }
69
70 if e, ok := events[EventMemberLeave]; !ok {
71 t.Fatalf("bad: %#v", events)
72 } else {
73 me := e.(MemberEvent)
74
75 if len(me.Members) != 2 {
76 t.Fatalf("bad: %#v", me)
77 }
78
79 expected := []string{"bar", "foo"}
80 names := []string{me.Members[0].Name, me.Members[1].Name}
81 sort.Strings(names)
82
83 if !reflect.DeepEqual(expected, names) {
84 t.Fatalf("bad: %#v", names)
85 }
86 }
87
88 if e, ok := events[EventMemberUpdate]; !ok {
89 t.Fatalf("bad: %#v", events)
90 } else {
91 me := e.(MemberEvent)
92 if len(me.Members) != 1 {
93 t.Fatalf("bad: %#v", me)
94 }
95
96 if me.Members[0].Name != "zip" {
97 t.Fatalf("bad: %#v", me.Members)
98 }
99 if me.Members[0].Tags["role"] != "bar" {
100 t.Fatalf("bad: %#v", me.Members[0].Tags)
101 }
102 }
103
104 if e, ok := events[EventMemberReap]; !ok {
105 t.Fatalf("bad: %#v", events)
106 } else {
107 me := e.(MemberEvent)
108 if len(me.Members) != 1 {
109 t.Fatalf("bad: %#v", me)
110 }
111
112 if me.Members[0].Name != "dead" {
113 t.Fatalf("bad: %#v", me.Members)
114 }
115 }
116 }
117
118 func TestMemberEventCoalesce_TagUpdate(t *testing.T) {
119 outCh := make(chan Event, 64)
120 shutdownCh := make(chan struct{})
121 defer close(shutdownCh)
122
123 c := &memberEventCoalescer{
124 lastEvents: make(map[string]EventType),
125 latestEvents: make(map[string]coalesceEvent),
126 }
127
128 inCh := coalescedEventCh(outCh, shutdownCh,
129 5*time.Millisecond, 5*time.Millisecond, c)
130
131 inCh <- MemberEvent{
132 Type: EventMemberUpdate,
133 Members: []Member{Member{Name: "foo", Tags: map[string]string{"role": "foo"}}},
134 }
135
136 time.Sleep(10 * time.Millisecond)
137
138 select {
139 case e := <-outCh:
140 if e.EventType() != EventMemberUpdate {
141 t.Fatalf("expected update")
142 }
143 default:
144 t.Fatalf("expected update")
145 }
146
147 // Second update should not be suppressed even though
148 // last event was an update
149 inCh <- MemberEvent{
150 Type: EventMemberUpdate,
151 Members: []Member{Member{Name: "foo", Tags: map[string]string{"role": "bar"}}},
152 }
153 time.Sleep(10 * time.Millisecond)
154
155 select {
156 case e := <-outCh:
157 if e.EventType() != EventMemberUpdate {
158 t.Fatalf("expected update")
159 }
160 default:
161 t.Fatalf("expected update")
162 }
163 }
164
165 func TestMemberEventCoalesce_passThrough(t *testing.T) {
166 cases := []struct {
167 e Event
168 handle bool
169 }{
170 {UserEvent{}, false},
171 {MemberEvent{Type: EventMemberJoin}, true},
172 {MemberEvent{Type: EventMemberLeave}, true},
173 {MemberEvent{Type: EventMemberFailed}, true},
174 {MemberEvent{Type: EventMemberUpdate}, true},
175 {MemberEvent{Type: EventMemberReap}, true},
176 }
177
178 for _, tc := range cases {
179 c := &memberEventCoalescer{}
180 if tc.handle != c.Handle(tc.e) {
181 t.Fatalf("bad: %#v", tc.e)
182 }
183 }
184 }
0 package serf
1
2 import (
3 "fmt"
4 "reflect"
5 "testing"
6 "time"
7 )
8
9 // Mock EventCounter type
10 const EventCounter EventType = 9000
11
12 type counterEvent struct {
13 delta int
14 }
15
16 func (c counterEvent) EventType() EventType {
17 return EventCounter
18 }
19 func (c counterEvent) String() string {
20 return fmt.Sprintf("CounterEvent %d", c.delta)
21 }
22
23 // Mock coalescer
24 type mockCoalesce struct {
25 value int
26 }
27
28 func (c *mockCoalesce) Handle(e Event) bool {
29 return e.EventType() == EventCounter
30 }
31
32 func (c *mockCoalesce) Coalesce(e Event) {
33 c.value += e.(counterEvent).delta
34 }
35
36 func (c *mockCoalesce) Flush(outChan chan<- Event) {
37 outChan <- counterEvent{c.value}
38 c.value = 0
39 }
40
41 func testCoalescer(cPeriod, qPeriod time.Duration) (chan<- Event, <-chan Event, chan<- struct{}) {
42 in := make(chan Event, 64)
43 out := make(chan Event)
44 shutdown := make(chan struct{})
45 c := &mockCoalesce{}
46 go coalesceLoop(in, out, shutdown, cPeriod, qPeriod, c)
47 return in, out, shutdown
48 }
49
50 func TestCoalescer_basic(t *testing.T) {
51 in, out, shutdown := testCoalescer(5*time.Millisecond, time.Second)
52 defer close(shutdown)
53
54 send := []Event{
55 counterEvent{1},
56 counterEvent{39},
57 counterEvent{2},
58 }
59 for _, e := range send {
60 in <- e
61 }
62
63 select {
64 case e := <-out:
65 if e.EventType() != EventCounter {
66 t.Fatalf("expected counter, got: %d", e.EventType())
67 }
68
69 if e.(counterEvent).delta != 42 {
70 t.Fatalf("bad: %#v", e)
71 }
72
73 case <-time.After(50 * time.Millisecond):
74 t.Fatalf("timeout")
75 }
76 }
77
78 func TestCoalescer_quiescent(t *testing.T) {
79 // This tests the quiescence by creating a long coalescence period
80 // with a short quiescent period and waiting only a multiple of the
81 // quiescent period for results.
82 in, out, shutdown := testCoalescer(10*time.Second, 10*time.Millisecond)
83 defer close(shutdown)
84
85 send := []Event{
86 counterEvent{1},
87 counterEvent{39},
88 counterEvent{2},
89 }
90 for _, e := range send {
91 in <- e
92 }
93
94 select {
95 case e := <-out:
96 if e.EventType() != EventCounter {
97 t.Fatalf("expected counter, got: %d", e.EventType())
98 }
99
100 if e.(counterEvent).delta != 42 {
101 t.Fatalf("bad: %#v", e)
102 }
103 case <-time.After(50 * time.Millisecond):
104 t.Fatalf("timeout")
105 }
106 }
107
108 func TestCoalescer_passThrough(t *testing.T) {
109 in, out, shutdown := testCoalescer(time.Second, time.Second)
110 defer close(shutdown)
111
112 send := []Event{
113 UserEvent{
114 Name: "test",
115 Payload: []byte("foo"),
116 },
117 }
118
119 for _, e := range send {
120 in <- e
121 }
122
123 select {
124 case e := <-out:
125 if e.EventType() != EventUser {
126 t.Fatalf("expected user event, got: %d", e.EventType())
127 }
128
129 if e.(UserEvent).Name != "test" {
130 t.Fatalf("name should be test. %v", e)
131 }
132
133 if !reflect.DeepEqual([]byte("foo"), e.(UserEvent).Payload) {
134 t.Fatalf("bad: %#v", e.(UserEvent).Payload)
135 }
136 case <-time.After(50 * time.Millisecond):
137 t.Fatalf("timeout")
138 }
139 }
0 package serf
1
2 type latestUserEvents struct {
3 LTime LamportTime
4 Events []Event
5 }
6
7 type userEventCoalescer struct {
8 // Maps an event name into the latest versions
9 events map[string]*latestUserEvents
10 }
11
12 func (c *userEventCoalescer) Handle(e Event) bool {
13 // Only handle EventUser messages
14 if e.EventType() != EventUser {
15 return false
16 }
17
18 // Check if coalescing is enabled
19 user := e.(UserEvent)
20 return user.Coalesce
21 }
22
23 func (c *userEventCoalescer) Coalesce(e Event) {
24 user := e.(UserEvent)
25 latest, ok := c.events[user.Name]
26
27 // Create a new entry if there are none, or
28 // if this message has the newest LTime
29 if !ok || latest.LTime < user.LTime {
30 latest = &latestUserEvents{
31 LTime: user.LTime,
32 Events: []Event{e},
33 }
34 c.events[user.Name] = latest
35 return
36 }
37
38 // If the the same age, save it
39 if latest.LTime == user.LTime {
40 latest.Events = append(latest.Events, e)
41 }
42 }
43
44 func (c *userEventCoalescer) Flush(outChan chan<- Event) {
45 for _, latest := range c.events {
46 for _, e := range latest.Events {
47 outChan <- e
48 }
49 }
50 c.events = make(map[string]*latestUserEvents)
51 }
0 package serf
1
2 import (
3 "reflect"
4 "testing"
5 "time"
6 )
7
8 func TestUserEventCoalesce_Basic(t *testing.T) {
9 outCh := make(chan Event, 64)
10 shutdownCh := make(chan struct{})
11 defer close(shutdownCh)
12
13 c := &userEventCoalescer{
14 events: make(map[string]*latestUserEvents),
15 }
16
17 inCh := coalescedEventCh(outCh, shutdownCh,
18 5*time.Millisecond, 5*time.Millisecond, c)
19
20 send := []Event{
21 UserEvent{
22 LTime: 1,
23 Name: "foo",
24 Coalesce: true,
25 },
26 UserEvent{
27 LTime: 2,
28 Name: "foo",
29 Coalesce: true,
30 },
31 UserEvent{
32 LTime: 2,
33 Name: "bar",
34 Payload: []byte("test1"),
35 Coalesce: true,
36 },
37 UserEvent{
38 LTime: 2,
39 Name: "bar",
40 Payload: []byte("test2"),
41 Coalesce: true,
42 },
43 }
44
45 for _, e := range send {
46 inCh <- e
47 }
48
49 var gotFoo, gotBar1, gotBar2 bool
50 timeout := time.After(10 * time.Millisecond)
51 USEREVENTFORLOOP:
52 for {
53 select {
54 case e := <-outCh:
55 ue := e.(UserEvent)
56 switch ue.Name {
57 case "foo":
58 if ue.LTime != 2 {
59 t.Fatalf("bad ltime for foo %#v", ue)
60 }
61 gotFoo = true
62 case "bar":
63 if ue.LTime != 2 {
64 t.Fatalf("bad ltime for bar %#v", ue)
65 }
66 if reflect.DeepEqual(ue.Payload, []byte("test1")) {
67 gotBar1 = true
68 }
69 if reflect.DeepEqual(ue.Payload, []byte("test2")) {
70 gotBar2 = true
71 }
72 }
73 case <-timeout:
74 break USEREVENTFORLOOP
75 }
76 }
77
78 if !gotFoo || !gotBar1 || !gotBar2 {
79 t.Fatalf("missing messages %v %v %v", gotFoo, gotBar1, gotBar2)
80 }
81 }
82
83 func TestUserEventCoalesce_passThrough(t *testing.T) {
84 cases := []struct {
85 e Event
86 handle bool
87 }{
88 {UserEvent{Coalesce: false}, false},
89 {UserEvent{Coalesce: true}, true},
90 {MemberEvent{Type: EventMemberJoin}, false},
91 {MemberEvent{Type: EventMemberLeave}, false},
92 {MemberEvent{Type: EventMemberFailed}, false},
93 }
94
95 for _, tc := range cases {
96 c := &userEventCoalescer{}
97 if tc.handle != c.Handle(tc.e) {
98 t.Fatalf("bad: %#v", tc.e)
99 }
100 }
101 }
0 package serf
1
2 import (
3 "io"
4 "os"
5 "time"
6
7 "github.com/hashicorp/memberlist"
8 )
9
10 // ProtocolVersionMap is the mapping of Serf delegate protocol versions
11 // to memberlist protocol versions. We mask the memberlist protocols using
12 // our own protocol version.
13 var ProtocolVersionMap map[uint8]uint8
14
15 func init() {
16 ProtocolVersionMap = map[uint8]uint8{
17 4: 2,
18 3: 2,
19 2: 2,
20 }
21 }
22
23 // Config is the configuration for creating a Serf instance.
24 type Config struct {
25 // The name of this node. This must be unique in the cluster. If this
26 // is not set, Serf will set it to the hostname of the running machine.
27 NodeName string
28
29 // The tags for this role, if any. This is used to provide arbitrary
30 // key/value metadata per-node. For example, a "role" tag may be used to
31 // differentiate "load-balancer" from a "web" role as parts of the same cluster.
32 // Tags are deprecating 'Role', and instead it acts as a special key in this
33 // map.
34 Tags map[string]string
35
36 // EventCh is a channel that receives all the Serf events. The events
37 // are sent on this channel in proper ordering. Care must be taken that
38 // this channel doesn't block, either by processing the events quick
39 // enough or buffering the channel, otherwise it can block state updates
40 // within Serf itself. If no EventCh is specified, no events will be fired,
41 // but point-in-time snapshots of members can still be retrieved by
42 // calling Members on Serf.
43 EventCh chan<- Event
44
45 // ProtocolVersion is the protocol version to speak. This must be between
46 // ProtocolVersionMin and ProtocolVersionMax.
47 ProtocolVersion uint8
48
49 // BroadcastTimeout is the amount of time to wait for a broadcast
50 // message to be sent to the cluster. Broadcast messages are used for
51 // things like leave messages and force remove messages. If this is not
52 // set, a timeout of 5 seconds will be set.
53 BroadcastTimeout time.Duration
54
55 // The settings below relate to Serf's event coalescence feature. Serf
56 // is able to coalesce multiple events into single events in order to
57 // reduce the amount of noise that is sent along the EventCh. For example
58 // if five nodes quickly join, the EventCh will be sent one EventMemberJoin
59 // containing the five nodes rather than five individual EventMemberJoin
60 // events. Coalescence can mitigate potential flapping behavior.
61 //
62 // Coalescence is disabled by default and can be enabled by setting
63 // CoalescePeriod.
64 //
65 // CoalescePeriod specifies the time duration to coalesce events.
66 // For example, if this is set to 5 seconds, then all events received
67 // within 5 seconds that can be coalesced will be.
68 //
69 // QuiescentPeriod specifies the duration of time where if no events
70 // are received, coalescence immediately happens. For example, if
71 // CoalscePeriod is set to 10 seconds but QuiscentPeriod is set to 2
72 // seconds, then the events will be coalesced and dispatched if no
73 // new events are received within 2 seconds of the last event. Otherwise,
74 // every event will always be delayed by at least 10 seconds.
75 CoalescePeriod time.Duration
76 QuiescentPeriod time.Duration
77
78 // The settings below relate to Serf's user event coalescing feature.
79 // The settings operate like above but only affect user messages and
80 // not the Member* messages that Serf generates.
81 UserCoalescePeriod time.Duration
82 UserQuiescentPeriod time.Duration
83
84 // The settings below relate to Serf keeping track of recently
85 // failed/left nodes and attempting reconnects.
86 //
87 // ReapInterval is the interval when the reaper runs. If this is not
88 // set (it is zero), it will be set to a reasonable default.
89 //
90 // ReconnectInterval is the interval when we attempt to reconnect
91 // to failed nodes. If this is not set (it is zero), it will be set
92 // to a reasonable default.
93 //
94 // ReconnectTimeout is the amount of time to attempt to reconnect to
95 // a failed node before giving up and considering it completely gone.
96 //
97 // TombstoneTimeout is the amount of time to keep around nodes
98 // that gracefully left as tombstones for syncing state with other
99 // Serf nodes.
100 ReapInterval time.Duration
101 ReconnectInterval time.Duration
102 ReconnectTimeout time.Duration
103 TombstoneTimeout time.Duration
104
105 // QueueDepthWarning is used to generate warning message if the
106 // number of queued messages to broadcast exceeds this number. This
107 // is to provide the user feedback if events are being triggered
108 // faster than they can be disseminated
109 QueueDepthWarning int
110
111 // MaxQueueDepth is used to start dropping messages if the number
112 // of queued messages to broadcast exceeds this number. This is to
113 // prevent an unbounded growth of memory utilization
114 MaxQueueDepth int
115
116 // RecentIntentBuffer is used to set the size of recent join and leave intent
117 // messages that will be buffered. This is used to guard against
118 // the case where Serf broadcasts an intent that arrives before the
119 // Memberlist event. It is important that this not be too small to avoid
120 // continuous rebroadcasting of dead events.
121 RecentIntentBuffer int
122
123 // EventBuffer is used to control how many events are buffered.
124 // This is used to prevent re-delivery of events to a client. The buffer
125 // must be large enough to handle all "recent" events, since Serf will
126 // not deliver messages that are older than the oldest entry in the buffer.
127 // Thus if a client is generating too many events, it's possible that the
128 // buffer gets overrun and messages are not delivered.
129 EventBuffer int
130
131 // QueryBuffer is used to control how many queries are buffered.
132 // This is used to prevent re-delivery of queries to a client. The buffer
133 // must be large enough to handle all "recent" events, since Serf will not
134 // deliver queries older than the oldest entry in the buffer.
135 // Thus if a client is generating too many queries, it's possible that the
136 // buffer gets overrun and messages are not delivered.
137 QueryBuffer int
138
139 // QueryTimeoutMult configures the default timeout multipler for a query to run if no
140 // specific value is provided. Queries are real-time by nature, where the
141 // reply is time sensitive. As a result, results are collected in an async
142 // fashion, however the query must have a bounded duration. We want the timeout
143 // to be long enough that all nodes have time to receive the message, run a handler,
144 // and generate a reply. Once the timeout is exceeded, any further replies are ignored.
145 // The default value is
146 //
147 // Timeout = GossipInterval * QueryTimeoutMult * log(N+1)
148 //
149 QueryTimeoutMult int
150
151 // MemberlistConfig is the memberlist configuration that Serf will
152 // use to do the underlying membership management and gossip. Some
153 // fields in the MemberlistConfig will be overwritten by Serf no
154 // matter what:
155 //
156 // * Name - This will always be set to the same as the NodeName
157 // in this configuration.
158 //
159 // * Events - Serf uses a custom event delegate.
160 //
161 // * Delegate - Serf uses a custom delegate.
162 //
163 MemberlistConfig *memberlist.Config
164
165 // LogOutput is the location to write logs to. If this is not set,
166 // logs will go to stderr.
167 LogOutput io.Writer
168
169 // SnapshotPath if provided is used to snapshot live nodes as well
170 // as lamport clock values. When Serf is started with a snapshot,
171 // it will attempt to join all the previously known nodes until one
172 // succeeds and will also avoid replaying old user events.
173 SnapshotPath string
174
175 // RejoinAfterLeave controls our interaction with the snapshot file.
176 // When set to false (default), a leave causes a Serf to not rejoin
177 // the cluster until an explicit join is received. If this is set to
178 // true, we ignore the leave, and rejoin the cluster on start.
179 RejoinAfterLeave bool
180
181 // EnableNameConflictResolution controls if Serf will actively attempt
182 // to resolve a name conflict. Since each Serf member must have a unique
183 // name, a cluster can run into issues if multiple nodes claim the same
184 // name. Without automatic resolution, Serf merely logs some warnings, but
185 // otherwise does not take any action. Automatic resolution detects the
186 // conflict and issues a special query which asks the cluster for the
187 // Name -> IP:Port mapping. If there is a simple majority of votes, that
188 // node stays while the other node will leave the cluster and exit.
189 EnableNameConflictResolution bool
190
191 // KeyringFile provides the location of a writable file where Serf can
192 // persist changes to the encryption keyring.
193 KeyringFile string
194
195 // Merge can be optionally provided to intercept a cluster merge
196 // and conditionally abort the merge.
197 Merge MergeDelegate
198 }
199
200 // Init allocates the subdata structures
201 func (c *Config) Init() {
202 if c.Tags == nil {
203 c.Tags = make(map[string]string)
204 }
205 }
206
207 // DefaultConfig returns a Config struct that contains reasonable defaults
208 // for most of the configurations.
209 func DefaultConfig() *Config {
210 hostname, err := os.Hostname()
211 if err != nil {
212 panic(err)
213 }
214
215 return &Config{
216 NodeName: hostname,
217 BroadcastTimeout: 5 * time.Second,
218 EventBuffer: 512,
219 QueryBuffer: 512,
220 LogOutput: os.Stderr,
221 ProtocolVersion: ProtocolVersionMax,
222 ReapInterval: 15 * time.Second,
223 RecentIntentBuffer: 128,
224 ReconnectInterval: 30 * time.Second,
225 ReconnectTimeout: 24 * time.Hour,
226 QueueDepthWarning: 128,
227 MaxQueueDepth: 4096,
228 TombstoneTimeout: 24 * time.Hour,
229 MemberlistConfig: memberlist.DefaultLANConfig(),
230 QueryTimeoutMult: 16,
231 EnableNameConflictResolution: true,
232 }
233 }
0 package serf
1
2 import (
3 "testing"
4 )
5
6 func TestDefaultConfig(t *testing.T) {
7 c := DefaultConfig()
8 if c.ProtocolVersion != ProtocolVersionMax {
9 t.Fatalf("bad: %#v", c)
10 }
11 }
0 package serf
1
2 import (
3 "github.com/hashicorp/memberlist"
4 )
5
6 type conflictDelegate struct {
7 serf *Serf
8 }
9
10 func (c *conflictDelegate) NotifyConflict(existing, other *memberlist.Node) {
11 c.serf.handleNodeConflict(existing, other)
12 }
0 package serf
1
2 import (
3 "fmt"
4 "github.com/armon/go-metrics"
5 )
6
7 // delegate is the memberlist.Delegate implementation that Serf uses.
8 type delegate struct {
9 serf *Serf
10 }
11
12 func (d *delegate) NodeMeta(limit int) []byte {
13 roleBytes := d.serf.encodeTags(d.serf.config.Tags)
14 if len(roleBytes) > limit {
15 panic(fmt.Errorf("Node tags '%v' exceeds length limit of %d bytes", d.serf.config.Tags, limit))
16 }
17
18 return roleBytes
19 }
20
21 func (d *delegate) NotifyMsg(buf []byte) {
22 // If we didn't actually receive any data, then ignore it.
23 if len(buf) == 0 {
24 return
25 }
26 metrics.AddSample([]string{"serf", "msgs", "received"}, float32(len(buf)))
27
28 rebroadcast := false
29 rebroadcastQueue := d.serf.broadcasts
30 t := messageType(buf[0])
31 switch t {
32 case messageLeaveType:
33 var leave messageLeave
34 if err := decodeMessage(buf[1:], &leave); err != nil {
35 d.serf.logger.Printf("[ERR] serf: Error decoding leave message: %s", err)
36 break
37 }
38
39 d.serf.logger.Printf("[DEBUG] serf: messageLeaveType: %s", leave.Node)
40 rebroadcast = d.serf.handleNodeLeaveIntent(&leave)
41
42 case messageJoinType:
43 var join messageJoin
44 if err := decodeMessage(buf[1:], &join); err != nil {
45 d.serf.logger.Printf("[ERR] serf: Error decoding join message: %s", err)
46 break
47 }
48
49 d.serf.logger.Printf("[DEBUG] serf: messageJoinType: %s", join.Node)
50 rebroadcast = d.serf.handleNodeJoinIntent(&join)
51
52 case messageUserEventType:
53 var event messageUserEvent
54 if err := decodeMessage(buf[1:], &event); err != nil {
55 d.serf.logger.Printf("[ERR] serf: Error decoding user event message: %s", err)
56 break
57 }
58
59 d.serf.logger.Printf("[DEBUG] serf: messageUserEventType: %s", event.Name)
60 rebroadcast = d.serf.handleUserEvent(&event)
61 rebroadcastQueue = d.serf.eventBroadcasts
62
63 case messageQueryType:
64 var query messageQuery
65 if err := decodeMessage(buf[1:], &query); err != nil {
66 d.serf.logger.Printf("[ERR] serf: Error decoding query message: %s", err)
67 break
68 }
69
70 d.serf.logger.Printf("[DEBUG] serf: messageQueryType: %s", query.Name)
71 rebroadcast = d.serf.handleQuery(&query)
72 rebroadcastQueue = d.serf.queryBroadcasts
73
74 case messageQueryResponseType:
75 var resp messageQueryResponse
76 if err := decodeMessage(buf[1:], &resp); err != nil {
77 d.serf.logger.Printf("[ERR] serf: Error decoding query response message: %s", err)
78 break
79 }
80
81 d.serf.logger.Printf("[DEBUG] serf: messageQueryResponseType: %v", resp.From)
82 d.serf.handleQueryResponse(&resp)
83
84 default:
85 d.serf.logger.Printf("[WARN] serf: Received message of unknown type: %d", t)
86 }
87
88 if rebroadcast {
89 // Copy the buffer since it we cannot rely on the slice not changing
90 newBuf := make([]byte, len(buf))
91 copy(newBuf, buf)
92
93 rebroadcastQueue.QueueBroadcast(&broadcast{
94 msg: newBuf,
95 notify: nil,
96 })
97 }
98 }
99
100 func (d *delegate) GetBroadcasts(overhead, limit int) [][]byte {
101 msgs := d.serf.broadcasts.GetBroadcasts(overhead, limit)
102
103 // Determine the bytes used already
104 bytesUsed := 0
105 for _, msg := range msgs {
106 lm := len(msg)
107 bytesUsed += lm + overhead
108 metrics.AddSample([]string{"serf", "msgs", "sent"}, float32(lm))
109 }
110
111 // Get any additional query broadcasts
112 queryMsgs := d.serf.queryBroadcasts.GetBroadcasts(overhead, limit-bytesUsed)
113 if queryMsgs != nil {
114 for _, m := range queryMsgs {
115 lm := len(m)
116 bytesUsed += lm + overhead
117 metrics.AddSample([]string{"serf", "msgs", "sent"}, float32(lm))
118 }
119 msgs = append(msgs, queryMsgs...)
120 }
121
122 // Get any additional event broadcasts
123 eventMsgs := d.serf.eventBroadcasts.GetBroadcasts(overhead, limit-bytesUsed)
124 if eventMsgs != nil {
125 for _, m := range eventMsgs {
126 lm := len(m)
127 bytesUsed += lm + overhead
128 metrics.AddSample([]string{"serf", "msgs", "sent"}, float32(lm))
129 }
130 msgs = append(msgs, eventMsgs...)
131 }
132
133 return msgs
134 }
135
136 func (d *delegate) LocalState(join bool) []byte {
137 d.serf.memberLock.RLock()
138 defer d.serf.memberLock.RUnlock()
139 d.serf.eventLock.RLock()
140 defer d.serf.eventLock.RUnlock()
141
142 // Create the message to send
143 pp := messagePushPull{
144 LTime: d.serf.clock.Time(),
145 StatusLTimes: make(map[string]LamportTime, len(d.serf.members)),
146 LeftMembers: make([]string, 0, len(d.serf.leftMembers)),
147 EventLTime: d.serf.eventClock.Time(),
148 Events: d.serf.eventBuffer,
149 QueryLTime: d.serf.queryClock.Time(),
150 }
151
152 // Add all the join LTimes
153 for name, member := range d.serf.members {
154 pp.StatusLTimes[name] = member.statusLTime
155 }
156
157 // Add all the left nodes
158 for _, member := range d.serf.leftMembers {
159 pp.LeftMembers = append(pp.LeftMembers, member.Name)
160 }
161
162 // Encode the push pull state
163 buf, err := encodeMessage(messagePushPullType, &pp)
164 if err != nil {
165 d.serf.logger.Printf("[ERR] serf: Failed to encode local state: %v", err)
166 return nil
167 }
168 return buf
169 }
170
171 func (d *delegate) MergeRemoteState(buf []byte, isJoin bool) {
172 // Check the message type
173 if messageType(buf[0]) != messagePushPullType {
174 d.serf.logger.Printf("[ERR] serf: Remote state has bad type prefix: %v", buf[0])
175 return
176 }
177
178 // Attempt a decode
179 pp := messagePushPull{}
180 if err := decodeMessage(buf[1:], &pp); err != nil {
181 d.serf.logger.Printf("[ERR] serf: Failed to decode remote state: %v", err)
182 return
183 }
184
185 // Witness the Lamport clocks first.
186 // We subtract 1 since no message with that clock has been sent yet
187 if pp.LTime > 0 {
188 d.serf.clock.Witness(pp.LTime - 1)
189 }
190 if pp.EventLTime > 0 {
191 d.serf.eventClock.Witness(pp.EventLTime - 1)
192 }
193 if pp.QueryLTime > 0 {
194 d.serf.queryClock.Witness(pp.QueryLTime - 1)
195 }
196
197 // Process the left nodes first to avoid the LTimes from being increment
198 // in the wrong order
199 leftMap := make(map[string]struct{}, len(pp.LeftMembers))
200 leave := messageLeave{}
201 for _, name := range pp.LeftMembers {
202 leftMap[name] = struct{}{}
203 leave.LTime = pp.StatusLTimes[name]
204 leave.Node = name
205 d.serf.handleNodeLeaveIntent(&leave)
206 }
207
208 // Update any other LTimes
209 join := messageJoin{}
210 for name, statusLTime := range pp.StatusLTimes {
211 // Skip the left nodes
212 if _, ok := leftMap[name]; ok {
213 continue
214 }
215
216 // Create an artificial join message
217 join.LTime = statusLTime
218 join.Node = name
219 d.serf.handleNodeJoinIntent(&join)
220 }
221
222 // If we are doing a join, and eventJoinIgnore is set
223 // then we set the eventMinTime to the EventLTime. This
224 // prevents any of the incoming events from being processed
225 if isJoin && d.serf.eventJoinIgnore {
226 d.serf.eventLock.Lock()
227 if pp.EventLTime > d.serf.eventMinTime {
228 d.serf.eventMinTime = pp.EventLTime
229 }
230 d.serf.eventLock.Unlock()
231 }
232
233 // Process all the events
234 userEvent := messageUserEvent{}
235 for _, events := range pp.Events {
236 if events == nil {
237 continue
238 }
239 userEvent.LTime = events.LTime
240 for _, e := range events.Events {
241 userEvent.Name = e.Name
242 userEvent.Payload = e.Payload
243 d.serf.handleUserEvent(&userEvent)
244 }
245 }
246 }
0 package serf
1
2 import (
3 "github.com/hashicorp/memberlist"
4 "github.com/hashicorp/serf/testutil"
5 "reflect"
6 "testing"
7 )
8
9 func TestDelegate_impl(t *testing.T) {
10 var raw interface{}
11 raw = new(delegate)
12 if _, ok := raw.(memberlist.Delegate); !ok {
13 t.Fatal("should be an Delegate")
14 }
15 }
16
17 func TestDelegate_NodeMeta_Old(t *testing.T) {
18 c := testConfig()
19 c.ProtocolVersion = 2
20 c.Tags["role"] = "test"
21 d := &delegate{&Serf{config: c}}
22 meta := d.NodeMeta(32)
23
24 if !reflect.DeepEqual(meta, []byte("test")) {
25 t.Fatalf("bad meta data: %v", meta)
26 }
27
28 out := d.serf.decodeTags(meta)
29 if out["role"] != "test" {
30 t.Fatalf("bad meta data: %v", meta)
31 }
32
33 defer func() {
34 if r := recover(); r == nil {
35 t.Fatalf("expected panic")
36 }
37 }()
38 d.NodeMeta(1)
39 }
40
41 func TestDelegate_NodeMeta_New(t *testing.T) {
42 c := testConfig()
43 c.ProtocolVersion = 3
44 c.Tags["role"] = "test"
45 d := &delegate{&Serf{config: c}}
46 meta := d.NodeMeta(32)
47
48 out := d.serf.decodeTags(meta)
49 if out["role"] != "test" {
50 t.Fatalf("bad meta data: %v", meta)
51 }
52
53 defer func() {
54 if r := recover(); r == nil {
55 t.Fatalf("expected panic")
56 }
57 }()
58 d.NodeMeta(1)
59 }
60
61 // internals
62 func TestDelegate_LocalState(t *testing.T) {
63 c1 := testConfig()
64 s1, err := Create(c1)
65 if err != nil {
66 t.Fatalf("err: %s", err)
67 }
68 defer s1.Shutdown()
69
70 c2 := testConfig()
71 s2, err := Create(c2)
72 if err != nil {
73 t.Fatalf("err: %s", err)
74 }
75 defer s2.Shutdown()
76
77 testutil.Yield()
78
79 _, err = s1.Join([]string{c2.MemberlistConfig.BindAddr}, false)
80 if err != nil {
81 t.Fatalf("err: %s", err)
82 }
83
84 err = s1.UserEvent("test", []byte("test"), false)
85 if err != nil {
86 t.Fatalf("err: %s", err)
87 }
88
89 _, err = s1.Query("foo", nil, nil)
90 if err != nil {
91 t.Fatalf("err: %s", err)
92 }
93
94 // s2 can leave now
95 s2.Leave()
96
97 // Do a state dump
98 d := c1.MemberlistConfig.Delegate
99 buf := d.LocalState(false)
100
101 // Verify
102 if messageType(buf[0]) != messagePushPullType {
103 t.Fatalf("bad message type")
104 }
105
106 // Attempt a decode
107 pp := messagePushPull{}
108 if err := decodeMessage(buf[1:], &pp); err != nil {
109 t.Fatalf("decode failed %v", err)
110 }
111
112 // Verify lamport
113 if pp.LTime != s1.clock.Time() {
114 t.Fatalf("clock mismatch")
115 }
116
117 // Verify the status
118 if len(pp.StatusLTimes) != 2 {
119 t.Fatalf("missing ltimes")
120 }
121
122 if len(pp.LeftMembers) != 1 {
123 t.Fatalf("missing left members")
124 }
125
126 if pp.EventLTime != s1.eventClock.Time() {
127 t.Fatalf("clock mismatch")
128 }
129
130 if len(pp.Events) != s1.config.EventBuffer {
131 t.Fatalf("should send full event buffer")
132 }
133
134 if pp.QueryLTime != s1.queryClock.Time() {
135 t.Fatalf("clock mismatch")
136 }
137 }
138
139 // internals
140 func TestDelegate_MergeRemoteState(t *testing.T) {
141 c1 := testConfig()
142 s1, err := Create(c1)
143 if err != nil {
144 t.Fatalf("err: %s", err)
145 }
146 defer s1.Shutdown()
147
148 // Do a state dump
149 d := c1.MemberlistConfig.Delegate
150
151 // Make a fake push pull
152 pp := messagePushPull{
153 LTime: 42,
154 StatusLTimes: map[string]LamportTime{
155 "test": 20,
156 "foo": 15,
157 },
158 LeftMembers: []string{"foo"},
159 EventLTime: 50,
160 Events: []*userEvents{
161 &userEvents{
162 LTime: 45,
163 Events: []userEvent{
164 userEvent{
165 Name: "test",
166 Payload: nil,
167 },
168 },
169 },
170 },
171 QueryLTime: 100,
172 }
173
174 buf, err := encodeMessage(messagePushPullType, &pp)
175 if err != nil {
176 t.Fatalf("err: %s", err)
177 }
178
179 // Merge in fake state
180 d.MergeRemoteState(buf, false)
181
182 // Verify lamport
183 if s1.clock.Time() != 42 {
184 t.Fatalf("clock mismatch")
185 }
186
187 // Verify pending join for test
188 if s1.recentJoin[0].Node != "test" || s1.recentJoin[0].LTime != 20 {
189 t.Fatalf("bad recent join")
190 }
191
192 // Verify pending leave for foo
193 if s1.recentLeave[0].Node != "foo" || s1.recentLeave[0].LTime != 15 {
194 t.Fatalf("bad recent leave")
195 }
196
197 // Very event time
198 if s1.eventClock.Time() != 50 {
199 t.Fatalf("bad event clock")
200 }
201
202 if s1.eventBuffer[45] == nil {
203 t.Fatalf("missing event buffer for time")
204 }
205 if s1.eventBuffer[45].Events[0].Name != "test" {
206 t.Fatalf("missing event")
207 }
208
209 if s1.queryClock.Time() != 100 {
210 t.Fatalf("bad query clock")
211 }
212 }
0 package serf
1
2 import (
3 "fmt"
4 "net"
5 "sync"
6 "time"
7 )
8
9 // EventType are all the types of events that may occur and be sent
10 // along the Serf channel.
11 type EventType int
12
13 const (
14 EventMemberJoin EventType = iota
15 EventMemberLeave
16 EventMemberFailed
17 EventMemberUpdate
18 EventMemberReap
19 EventUser
20 EventQuery
21 )
22
23 func (t EventType) String() string {
24 switch t {
25 case EventMemberJoin:
26 return "member-join"
27 case EventMemberLeave:
28 return "member-leave"
29 case EventMemberFailed:
30 return "member-failed"
31 case EventMemberUpdate:
32 return "member-update"
33 case EventMemberReap:
34 return "member-reap"
35 case EventUser:
36 return "user"
37 case EventQuery:
38 return "query"
39 default:
40 panic(fmt.Sprintf("unknown event type: %d", t))
41 }
42 }
43
44 // Event is a generic interface for exposing Serf events
45 // Clients will usually need to use a type switches to get
46 // to a more useful type
47 type Event interface {
48 EventType() EventType
49 String() string
50 }
51
52 // MemberEvent is the struct used for member related events
53 // Because Serf coalesces events, an event may contain multiple members.
54 type MemberEvent struct {
55 Type EventType
56 Members []Member
57 }
58
59 func (m MemberEvent) EventType() EventType {
60 return m.Type
61 }
62
63 func (m MemberEvent) String() string {
64 switch m.Type {
65 case EventMemberJoin:
66 return "member-join"
67 case EventMemberLeave:
68 return "member-leave"
69 case EventMemberFailed:
70 return "member-failed"
71 case EventMemberUpdate:
72 return "member-update"
73 case EventMemberReap:
74 return "member-reap"
75 default:
76 panic(fmt.Sprintf("unknown event type: %d", m.Type))
77 }
78 }
79
80 // UserEvent is the struct used for events that are triggered
81 // by the user and are not related to members
82 type UserEvent struct {
83 LTime LamportTime
84 Name string
85 Payload []byte
86 Coalesce bool
87 }
88
89 func (u UserEvent) EventType() EventType {
90 return EventUser
91 }
92
93 func (u UserEvent) String() string {
94 return fmt.Sprintf("user-event: %s", u.Name)
95 }
96
97 // Query is the struct used EventQuery type events
98 type Query struct {
99 LTime LamportTime
100 Name string
101 Payload []byte
102
103 serf *Serf
104 id uint32 // ID is not exported, since it may change
105 addr []byte // Address to respond to
106 port uint16 // Port to respond to
107 deadline time.Time // Must respond by this deadline
108 respLock sync.Mutex
109 }
110
111 func (q *Query) EventType() EventType {
112 return EventQuery
113 }
114
115 func (q *Query) String() string {
116 return fmt.Sprintf("query: %s", q.Name)
117 }
118
119 // Deadline returns the time by which a response must be sent
120 func (q *Query) Deadline() time.Time {
121 return q.deadline
122 }
123
124 // Respond is used to send a response to the user query
125 func (q *Query) Respond(buf []byte) error {
126 q.respLock.Lock()
127 defer q.respLock.Unlock()
128
129 // Check if we've already responded
130 if q.deadline.IsZero() {
131 return fmt.Errorf("Response already sent")
132 }
133
134 // Ensure we aren't past our response deadline
135 if time.Now().After(q.deadline) {
136 return fmt.Errorf("Response is past the deadline")
137 }
138
139 // Create response
140 resp := messageQueryResponse{
141 LTime: q.LTime,
142 ID: q.id,
143 From: q.serf.config.NodeName,
144 Payload: buf,
145 }
146
147 // Format the response
148 raw, err := encodeMessage(messageQueryResponseType, &resp)
149 if err != nil {
150 return fmt.Errorf("Failed to format response: %v", err)
151 }
152
153 // Check the size limit
154 if len(raw) > QueryResponseSizeLimit {
155 return fmt.Errorf("response exceeds limit of %d bytes", QueryResponseSizeLimit)
156 }
157
158 // Send the response
159 addr := net.UDPAddr{IP: q.addr, Port: int(q.port)}
160 if err := q.serf.memberlist.SendTo(&addr, raw); err != nil {
161 return err
162 }
163
164 // Clera the deadline, response sent
165 q.deadline = time.Time{}
166 return nil
167 }
0 package serf
1
2 import (
3 "github.com/hashicorp/memberlist"
4 )
5
6 type eventDelegate struct {
7 serf *Serf
8 }
9
10 func (e *eventDelegate) NotifyJoin(n *memberlist.Node) {
11 e.serf.handleNodeJoin(n)
12 }
13
14 func (e *eventDelegate) NotifyLeave(n *memberlist.Node) {
15 e.serf.handleNodeLeave(n)
16 }
17
18 func (e *eventDelegate) NotifyUpdate(n *memberlist.Node) {
19 e.serf.handleNodeUpdate(n)
20 }
0 package serf
1
2 import (
3 "reflect"
4 "testing"
5 "time"
6 )
7
8 // testEvents tests that the given node had the given sequence of events
9 // on the event channel.
10 func testEvents(t *testing.T, ch <-chan Event, node string, expected []EventType) {
11 actual := make([]EventType, 0, len(expected))
12
13 TESTEVENTLOOP:
14 for {
15 select {
16 case r := <-ch:
17 e, ok := r.(MemberEvent)
18 if !ok {
19 continue
20 }
21
22 found := false
23 for _, m := range e.Members {
24 if m.Name == node {
25 found = true
26 break
27 }
28 }
29
30 if found {
31 actual = append(actual, e.Type)
32 }
33 case <-time.After(10 * time.Millisecond):
34 break TESTEVENTLOOP
35 }
36 }
37
38 if !reflect.DeepEqual(actual, expected) {
39 t.Fatalf("expected events: %v. Got: %v", expected, actual)
40 }
41 }
42
43 // testUserEvents tests that the given sequence of usr events
44 // on the event channel took place.
45 func testUserEvents(t *testing.T, ch <-chan Event, expectedName []string, expectedPayload [][]byte) {
46 actualName := make([]string, 0, len(expectedName))
47 actualPayload := make([][]byte, 0, len(expectedPayload))
48
49 TESTEVENTLOOP:
50 for {
51 select {
52 case r, ok := <-ch:
53 if !ok {
54 break TESTEVENTLOOP
55 }
56 u, ok := r.(UserEvent)
57 if !ok {
58 continue
59 }
60
61 actualName = append(actualName, u.Name)
62 actualPayload = append(actualPayload, u.Payload)
63 case <-time.After(10 * time.Millisecond):
64 break TESTEVENTLOOP
65 }
66 }
67
68 if !reflect.DeepEqual(actualName, expectedName) {
69 t.Fatalf("expected names: %v. Got: %v", expectedName, actualName)
70 }
71 if !reflect.DeepEqual(actualPayload, expectedPayload) {
72 t.Fatalf("expected payloads: %v. Got: %v", expectedPayload, actualPayload)
73 }
74
75 }
76
77 // testQueryEvents tests that the given sequence of queries
78 // on the event channel took place.
79 func testQueryEvents(t *testing.T, ch <-chan Event, expectedName []string, expectedPayload [][]byte) {
80 actualName := make([]string, 0, len(expectedName))
81 actualPayload := make([][]byte, 0, len(expectedPayload))
82
83 TESTEVENTLOOP:
84 for {
85 select {
86 case r, ok := <-ch:
87 if !ok {
88 break TESTEVENTLOOP
89 }
90 q, ok := r.(*Query)
91 if !ok {
92 continue
93 }
94
95 actualName = append(actualName, q.Name)
96 actualPayload = append(actualPayload, q.Payload)
97 case <-time.After(10 * time.Millisecond):
98 break TESTEVENTLOOP
99 }
100 }
101
102 if !reflect.DeepEqual(actualName, expectedName) {
103 t.Fatalf("expected names: %v. Got: %v", expectedName, actualName)
104 }
105 if !reflect.DeepEqual(actualPayload, expectedPayload) {
106 t.Fatalf("expected payloads: %v. Got: %v", expectedPayload, actualPayload)
107 }
108
109 }
110
111 func TestMemberEvent(t *testing.T) {
112 me := MemberEvent{
113 Type: EventMemberJoin,
114 Members: nil,
115 }
116 if me.EventType() != EventMemberJoin {
117 t.Fatalf("bad event type")
118 }
119 if me.String() != "member-join" {
120 t.Fatalf("bad string val")
121 }
122
123 me.Type = EventMemberLeave
124 if me.EventType() != EventMemberLeave {
125 t.Fatalf("bad event type")
126 }
127 if me.String() != "member-leave" {
128 t.Fatalf("bad string val")
129 }
130
131 me.Type = EventMemberFailed
132 if me.EventType() != EventMemberFailed {
133 t.Fatalf("bad event type")
134 }
135 if me.String() != "member-failed" {
136 t.Fatalf("bad string val")
137 }
138
139 me.Type = EventMemberUpdate
140 if me.EventType() != EventMemberUpdate {
141 t.Fatalf("bad event type")
142 }
143 if me.String() != "member-update" {
144 t.Fatalf("bad string val")
145 }
146
147 me.Type = EventMemberReap
148 if me.EventType() != EventMemberReap {
149 t.Fatalf("bad event type")
150 }
151 if me.String() != "member-reap" {
152 t.Fatalf("bad string val")
153 }
154
155 defer func() {
156 if r := recover(); r == nil {
157 t.Fatalf("expected panic")
158 }
159 }()
160 me.Type = EventUser
161 me.String()
162 }
163
164 func TestUserEvent(t *testing.T) {
165 ue := UserEvent{
166 Name: "test",
167 Payload: []byte("foobar"),
168 }
169 if ue.EventType() != EventUser {
170 t.Fatalf("bad event type")
171 }
172 if ue.String() != "user-event: test" {
173 t.Fatalf("bad string val")
174 }
175 }
176
177 func TestQuery(t *testing.T) {
178 q := Query{
179 LTime: 42,
180 Name: "update",
181 Payload: []byte("abcd1234"),
182 }
183 if q.EventType() != EventQuery {
184 t.Fatalf("Bad")
185 }
186 if q.String() != "query: update" {
187 t.Fatalf("bad: %v", q.String())
188 }
189 }
190
191 func TestEventType_String(t *testing.T) {
192 events := []EventType{EventMemberJoin, EventMemberLeave, EventMemberFailed,
193 EventMemberUpdate, EventMemberReap, EventUser, EventQuery}
194 expect := []string{"member-join", "member-leave", "member-failed",
195 "member-update", "member-reap", "user", "query"}
196
197 for idx, event := range events {
198 if event.String() != expect[idx] {
199 t.Fatalf("expect %v got %v", expect[idx], event.String())
200 }
201 }
202
203 other := EventType(100)
204 defer func() {
205 if r := recover(); r == nil {
206 t.Fatalf("expected panic")
207 }
208 }()
209 other.String()
210 }
0 package serf
1
2 import (
3 "encoding/base64"
4 "log"
5 "strings"
6 )
7
8 const (
9 // This is the prefix we use for queries that are internal to Serf.
10 // They are handled internally, and not forwarded to a client.
11 InternalQueryPrefix = "_serf_"
12
13 // pingQuery is run to check for reachability
14 pingQuery = "ping"
15
16 // conflictQuery is run to resolve a name conflict
17 conflictQuery = "conflict"
18
19 // installKeyQuery is used to install a new key
20 installKeyQuery = "install-key"
21
22 // useKeyQuery is used to change the primary encryption key
23 useKeyQuery = "use-key"
24
25 // removeKeyQuery is used to remove a key from the keyring
26 removeKeyQuery = "remove-key"
27
28 // listKeysQuery is used to list all known keys in the cluster
29 listKeysQuery = "list-keys"
30 )
31
32 // internalQueryName is used to generate a query name for an internal query
33 func internalQueryName(name string) string {
34 return InternalQueryPrefix + name
35 }
36
37 // serfQueries is used to listen for queries that start with
38 // _serf and respond to them as appropriate.
39 type serfQueries struct {
40 inCh chan Event
41 logger *log.Logger
42 outCh chan<- Event
43 serf *Serf
44 shutdownCh <-chan struct{}
45 }
46
47 // nodeKeyResponse is used to store the result from an individual node while
48 // replying to key modification queries
49 type nodeKeyResponse struct {
50 // Result indicates true/false if there were errors or not
51 Result bool
52
53 // Message contains error messages or other information
54 Message string
55
56 // Keys is used in listing queries to relay a list of installed keys
57 Keys []string
58 }
59
60 // newSerfQueries is used to create a new serfQueries. We return an event
61 // channel that is ingested and forwarded to an outCh. Any Queries that
62 // have the InternalQueryPrefix are handled instead of forwarded.
63 func newSerfQueries(serf *Serf, logger *log.Logger, outCh chan<- Event, shutdownCh <-chan struct{}) (chan<- Event, error) {
64 inCh := make(chan Event, 1024)
65 q := &serfQueries{
66 inCh: inCh,
67 logger: logger,
68 outCh: outCh,
69 serf: serf,
70 shutdownCh: shutdownCh,
71 }
72 go q.stream()
73 return inCh, nil
74 }
75
76 // stream is a long running routine to ingest the event stream
77 func (s *serfQueries) stream() {
78 for {
79 select {
80 case e := <-s.inCh:
81 // Check if this is a query we should process
82 if q, ok := e.(*Query); ok && strings.HasPrefix(q.Name, InternalQueryPrefix) {
83 go s.handleQuery(q)
84
85 } else if s.outCh != nil {
86 s.outCh <- e
87 }
88
89 case <-s.shutdownCh:
90 return
91 }
92 }
93 }
94
95 // handleQuery is invoked when we get an internal query
96 func (s *serfQueries) handleQuery(q *Query) {
97 // Get the queryName after the initial prefix
98 queryName := q.Name[len(InternalQueryPrefix):]
99 switch queryName {
100 case pingQuery:
101 // Nothing to do, we will ack the query
102 case conflictQuery:
103 s.handleConflict(q)
104 case installKeyQuery:
105 s.handleInstallKey(q)
106 case useKeyQuery:
107 s.handleUseKey(q)
108 case removeKeyQuery:
109 s.handleRemoveKey(q)
110 case listKeysQuery:
111 s.handleListKeys(q)
112 default:
113 s.logger.Printf("[WARN] serf: Unhandled internal query '%s'", queryName)
114 }
115 }
116
117 // handleConflict is invoked when we get a query that is attempting to
118 // disambiguate a name conflict. They payload is a node name, and the response
119 // should the address we believe that node is at, if any.
120 func (s *serfQueries) handleConflict(q *Query) {
121 // The target node name is the payload
122 node := string(q.Payload)
123
124 // Do not respond to the query if it is about us
125 if node == s.serf.config.NodeName {
126 return
127 }
128 s.logger.Printf("[DEBUG] serf: Got conflict resolution query for '%s'", node)
129
130 // Look for the member info
131 var out *Member
132 s.serf.memberLock.Lock()
133 if member, ok := s.serf.members[node]; ok {
134 out = &member.Member
135 }
136 s.serf.memberLock.Unlock()
137
138 // Encode the response
139 buf, err := encodeMessage(messageConflictResponseType, out)
140 if err != nil {
141 s.logger.Printf("[ERR] serf: Failed to encode conflict query response: %v", err)
142 return
143 }
144
145 // Send our answer
146 if err := q.Respond(buf); err != nil {
147 s.logger.Printf("[ERR] serf: Failed to respond to conflict query: %v", err)
148 }
149 }
150
151 // sendKeyResponse handles responding to key-related queries.
152 func (s *serfQueries) sendKeyResponse(q *Query, resp *nodeKeyResponse) {
153 buf, err := encodeMessage(messageKeyResponseType, resp)
154 if err != nil {
155 s.logger.Printf("[ERR] serf: Failed to encode key response: %v", err)
156 return
157 }
158
159 if err := q.Respond(buf); err != nil {
160 s.logger.Printf("[ERR] serf: Failed to respond to key query: %v", err)
161 return
162 }
163 }
164
165 // handleInstallKey is invoked whenever a new encryption key is received from
166 // another member in the cluster, and handles the process of installing it onto
167 // the memberlist keyring. This type of query may fail if the provided key does
168 // not fit the constraints that memberlist enforces. If the query fails, the
169 // response will contain the error message so that it may be relayed.
170 func (s *serfQueries) handleInstallKey(q *Query) {
171 response := nodeKeyResponse{Result: false}
172 keyring := s.serf.config.MemberlistConfig.Keyring
173 req := keyRequest{}
174
175 err := decodeMessage(q.Payload[1:], &req)
176 if err != nil {
177 s.logger.Printf("[ERR] serf: Failed to decode key request: %v", err)
178 goto SEND
179 }
180
181 if !s.serf.EncryptionEnabled() {
182 response.Message = "No keyring to modify (encryption not enabled)"
183 s.logger.Printf("[ERR] serf: No keyring to modify (encryption not enabled)")
184 goto SEND
185 }
186
187 s.logger.Printf("[INFO] serf: Received install-key query")
188 if err := keyring.AddKey(req.Key); err != nil {
189 response.Message = err.Error()
190 s.logger.Printf("[ERR] serf: Failed to install key: %s", err)
191 goto SEND
192 }
193
194 if err := s.serf.writeKeyringFile(); err != nil {
195 response.Message = err.Error()
196 s.logger.Printf("[ERR] serf: Failed to write keyring file: %s", err)
197 goto SEND
198 }
199
200 response.Result = true
201
202 SEND:
203 s.sendKeyResponse(q, &response)
204 }
205
206 // handleUseKey is invoked whenever a query is received to mark a different key
207 // in the internal keyring as the primary key. This type of query may fail due
208 // to operator error (requested key not in ring), and thus sends error messages
209 // back in the response.
210 func (s *serfQueries) handleUseKey(q *Query) {
211 response := nodeKeyResponse{Result: false}
212 keyring := s.serf.config.MemberlistConfig.Keyring
213 req := keyRequest{}
214
215 err := decodeMessage(q.Payload[1:], &req)
216 if err != nil {
217 s.logger.Printf("[ERR] serf: Failed to decode key request: %v", err)
218 goto SEND
219 }
220
221 if !s.serf.EncryptionEnabled() {
222 response.Message = "No keyring to modify (encryption not enabled)"
223 s.logger.Printf("[ERR] serf: No keyring to modify (encryption not enabled)")
224 goto SEND
225 }
226
227 s.logger.Printf("[INFO] serf: Received use-key query")
228 if err := keyring.UseKey(req.Key); err != nil {
229 response.Message = err.Error()
230 s.logger.Printf("[ERR] serf: Failed to change primary key: %s", err)
231 goto SEND
232 }
233
234 if err := s.serf.writeKeyringFile(); err != nil {
235 response.Message = err.Error()
236 s.logger.Printf("[ERR] serf: Failed to write keyring file: %s", err)
237 goto SEND
238 }
239
240 response.Result = true
241
242 SEND:
243 s.sendKeyResponse(q, &response)
244 }
245
246 // handleRemoveKey is invoked when a query is received to remove a particular
247 // key from the keyring. This type of query can fail if the key requested for
248 // deletion is currently the primary key in the keyring, so therefore it will
249 // reply to the query with any relevant errors from the operation.
250 func (s *serfQueries) handleRemoveKey(q *Query) {
251 response := nodeKeyResponse{Result: false}
252 keyring := s.serf.config.MemberlistConfig.Keyring
253 req := keyRequest{}
254
255 err := decodeMessage(q.Payload[1:], &req)
256 if err != nil {
257 s.logger.Printf("[ERR] serf: Failed to decode key request: %v", err)
258 goto SEND
259 }
260
261 if !s.serf.EncryptionEnabled() {
262 response.Message = "No keyring to modify (encryption not enabled)"
263 s.logger.Printf("[ERR] serf: No keyring to modify (encryption not enabled)")
264 goto SEND
265 }
266
267 s.logger.Printf("[INFO] serf: Received remove-key query")
268 if err := keyring.RemoveKey(req.Key); err != nil {
269 response.Message = err.Error()
270 s.logger.Printf("[ERR] serf: Failed to remove key: %s", err)
271 goto SEND
272 }
273
274 if err := s.serf.writeKeyringFile(); err != nil {
275 response.Message = err.Error()
276 s.logger.Printf("[ERR] serf: Failed to write keyring file: %s", err)
277 goto SEND
278 }
279
280 response.Result = true
281
282 SEND:
283 s.sendKeyResponse(q, &response)
284 }
285
286 // handleListKeys is invoked when a query is received to return a list of all
287 // installed keys the Serf instance knows of. For performance, the keys are
288 // encoded to base64 on each of the members to remove this burden from the
289 // node asking for the results.
290 func (s *serfQueries) handleListKeys(q *Query) {
291 response := nodeKeyResponse{Result: false}
292 keyring := s.serf.config.MemberlistConfig.Keyring
293
294 if !s.serf.EncryptionEnabled() {
295 response.Message = "Keyring is empty (encryption not enabled)"
296 s.logger.Printf("[ERR] serf: Keyring is empty (encryption not enabled)")
297 goto SEND
298 }
299
300 s.logger.Printf("[INFO] serf: Received list-keys query")
301 for _, keyBytes := range keyring.GetKeys() {
302 // Encode the keys before sending the response. This should help take
303 // some the burden of doing this off of the asking member.
304 key := base64.StdEncoding.EncodeToString(keyBytes)
305 response.Keys = append(response.Keys, key)
306 }
307 response.Result = true
308
309 SEND:
310 s.sendKeyResponse(q, &response)
311 }
0 package serf
1
2 import (
3 "log"
4 "os"
5 "testing"
6 "time"
7 )
8
9 func TestInternalQueryName(t *testing.T) {
10 name := internalQueryName(conflictQuery)
11 if name != "_serf_conflict" {
12 t.Fatalf("bad: %v", name)
13 }
14 }
15
16 func TestSerfQueries_Passthrough(t *testing.T) {
17 serf := &Serf{}
18 logger := log.New(os.Stderr, "", log.LstdFlags)
19 outCh := make(chan Event, 4)
20 shutdown := make(chan struct{})
21 defer close(shutdown)
22 eventCh, err := newSerfQueries(serf, logger, outCh, shutdown)
23 if err != nil {
24 t.Fatalf("err: %v", err)
25 }
26
27 // Push a user event
28 eventCh <- UserEvent{LTime: 42, Name: "foo"}
29
30 // Push a query
31 eventCh <- &Query{LTime: 42, Name: "foo"}
32
33 // Push a query
34 eventCh <- MemberEvent{Type: EventMemberJoin}
35
36 // Should get passed through
37 for i := 0; i < 3; i++ {
38 select {
39 case <-outCh:
40 case <-time.After(100 * time.Millisecond):
41 t.Fatalf("time out")
42 }
43 }
44 }
45
46 func TestSerfQueries_Ping(t *testing.T) {
47 serf := &Serf{}
48 logger := log.New(os.Stderr, "", log.LstdFlags)
49 outCh := make(chan Event, 4)
50 shutdown := make(chan struct{})
51 defer close(shutdown)
52 eventCh, err := newSerfQueries(serf, logger, outCh, shutdown)
53 if err != nil {
54 t.Fatalf("err: %v", err)
55 }
56
57 // Send a ping
58 eventCh <- &Query{LTime: 42, Name: "_serf_ping"}
59
60 // Should not get passed through
61 select {
62 case <-outCh:
63 t.Fatalf("Should not passthrough query!")
64 case <-time.After(50 * time.Millisecond):
65 }
66 }
67
68 func TestSerfQueries_Conflict_SameName(t *testing.T) {
69 serf := &Serf{config: &Config{NodeName: "foo"}}
70 logger := log.New(os.Stderr, "", log.LstdFlags)
71 outCh := make(chan Event, 4)
72 shutdown := make(chan struct{})
73 defer close(shutdown)
74 eventCh, err := newSerfQueries(serf, logger, outCh, shutdown)
75 if err != nil {
76 t.Fatalf("err: %v", err)
77 }
78
79 // Query for our own name
80 eventCh <- &Query{Name: "_serf_conflict", Payload: []byte("foo")}
81
82 // Should not passthrough OR respond
83 select {
84 case <-outCh:
85 t.Fatalf("Should not passthrough query!")
86 case <-time.After(50 * time.Millisecond):
87 }
88 }
0 package serf
1
2 import (
3 "encoding/base64"
4 "fmt"
5 "sync"
6 )
7
8 // KeyManager encapsulates all functionality within Serf for handling
9 // encryption keyring changes across a cluster.
10 type KeyManager struct {
11 serf *Serf
12
13 // Lock to protect read and write operations
14 l sync.RWMutex
15 }
16
17 // keyRequest is used to contain input parameters which get broadcasted to all
18 // nodes as part of a key query operation.
19 type keyRequest struct {
20 Key []byte
21 }
22
23 // KeyResponse is used to relay a query for a list of all keys in use.
24 type KeyResponse struct {
25 Messages map[string]string // Map of node name to response message
26 NumNodes int // Total nodes memberlist knows of
27 NumResp int // Total responses received
28 NumErr int // Total errors from request
29
30 // Keys is a mapping of the base64-encoded value of the key bytes to the
31 // number of nodes that have the key installed.
32 Keys map[string]int
33 }
34
35 // streamKeyResp takes care of reading responses from a channel and composing
36 // them into a KeyResponse. It will update a KeyResponse *in place* and
37 // therefore has nothing to return.
38 func (k *KeyManager) streamKeyResp(resp *KeyResponse, ch <-chan NodeResponse) {
39 for r := range ch {
40 var nodeResponse nodeKeyResponse
41
42 resp.NumResp++
43
44 // Decode the response
45 if len(r.Payload) < 1 || messageType(r.Payload[0]) != messageKeyResponseType {
46 resp.Messages[r.From] = fmt.Sprintf(
47 "Invalid key query response type: %v", r.Payload)
48 resp.NumErr++
49 goto NEXT
50 }
51 if err := decodeMessage(r.Payload[1:], &nodeResponse); err != nil {
52 resp.Messages[r.From] = fmt.Sprintf(
53 "Failed to decode key query response: %v", r.Payload)
54 resp.NumErr++
55 goto NEXT
56 }
57
58 if !nodeResponse.Result {
59 resp.Messages[r.From] = nodeResponse.Message
60 resp.NumErr++
61 }
62
63 // Currently only used for key list queries, this adds keys to a counter
64 // and increments them for each node response which contains them.
65 for _, key := range nodeResponse.Keys {
66 if _, ok := resp.Keys[key]; !ok {
67 resp.Keys[key] = 1
68 } else {
69 resp.Keys[key]++
70 }
71 }
72
73 NEXT:
74 // Return early if all nodes have responded. This allows us to avoid
75 // waiting for the full timeout when there is nothing left to do.
76 if resp.NumResp == resp.NumNodes {
77 return
78 }
79 }
80 }
81
82 // handleKeyRequest performs query broadcasting to all members for any type of
83 // key operation and manages gathering responses and packing them up into a
84 // KeyResponse for uniform response handling.
85 func (k *KeyManager) handleKeyRequest(key, query string) (*KeyResponse, error) {
86 resp := &KeyResponse{
87 Messages: make(map[string]string),
88 Keys: make(map[string]int),
89 }
90 qName := internalQueryName(query)
91
92 // Decode the new key into raw bytes
93 rawKey, err := base64.StdEncoding.DecodeString(key)
94 if err != nil {
95 return resp, err
96 }
97
98 // Encode the query request
99 req, err := encodeMessage(messageKeyRequestType, keyRequest{Key: rawKey})
100 if err != nil {
101 return resp, err
102 }
103
104 qParam := k.serf.DefaultQueryParams()
105 queryResp, err := k.serf.Query(qName, req, qParam)
106 if err != nil {
107 return resp, err
108 }
109
110 // Handle the response stream and populate the KeyResponse
111 resp.NumNodes = k.serf.memberlist.NumMembers()
112 k.streamKeyResp(resp, queryResp.respCh)
113
114 // Check the response for any reported failure conditions
115 if resp.NumErr != 0 {
116 return resp, fmt.Errorf("%d/%d nodes reported failure", resp.NumErr, resp.NumNodes)
117 }
118 if resp.NumResp != resp.NumNodes {
119 return resp, fmt.Errorf("%d/%d nodes reported success", resp.NumResp, resp.NumNodes)
120 }
121
122 return resp, nil
123 }
124
125 // InstallKey handles broadcasting a query to all members and gathering
126 // responses from each of them, returning a list of messages from each node
127 // and any applicable error conditions.
128 func (k *KeyManager) InstallKey(key string) (*KeyResponse, error) {
129 k.l.Lock()
130 defer k.l.Unlock()
131
132 return k.handleKeyRequest(key, installKeyQuery)
133 }
134
135 // UseKey handles broadcasting a primary key change to all members in the
136 // cluster, and gathering any response messages. If successful, there should
137 // be an empty KeyResponse returned.
138 func (k *KeyManager) UseKey(key string) (*KeyResponse, error) {
139 k.l.Lock()
140 defer k.l.Unlock()
141
142 return k.handleKeyRequest(key, useKeyQuery)
143 }
144
145 // RemoveKey handles broadcasting a key to the cluster for removal. Each member
146 // will receive this event, and if they have the key in their keyring, remove
147 // it. If any errors are encountered, RemoveKey will collect and relay them.
148 func (k *KeyManager) RemoveKey(key string) (*KeyResponse, error) {
149 k.l.Lock()
150 defer k.l.Unlock()
151
152 return k.handleKeyRequest(key, removeKeyQuery)
153 }
154
155 // ListKeys is used to collect installed keys from members in a Serf cluster
156 // and return an aggregated list of all installed keys. This is useful to
157 // operators to ensure that there are no lingering keys installed on any agents.
158 // Since having multiple keys installed can cause performance penalties in some
159 // cases, it's important to verify this information and remove unneeded keys.
160 func (k *KeyManager) ListKeys() (*KeyResponse, error) {
161 k.l.RLock()
162 defer k.l.RUnlock()
163
164 return k.handleKeyRequest("", listKeysQuery)
165 }
0 package serf
1
2 import (
3 "bytes"
4 "encoding/base64"
5 "github.com/hashicorp/memberlist"
6 "github.com/hashicorp/serf/testutil"
7 "testing"
8 )
9
10 func testKeyring() (*memberlist.Keyring, error) {
11 keys := []string{
12 "enjTwAFRe4IE71bOFhirzQ==",
13 "csT9mxI7aTf9ap3HLBbdmA==",
14 "noha2tVc0OyD/2LtCBoAOQ==",
15 }
16
17 keysDecoded := make([][]byte, len(keys))
18 for i, key := range keys {
19 decoded, err := base64.StdEncoding.DecodeString(key)
20 if err != nil {
21 return nil, err
22 }
23 keysDecoded[i] = decoded
24 }
25
26 return memberlist.NewKeyring(keysDecoded, keysDecoded[0])
27 }
28
29 func testKeyringSerf() (*Serf, error) {
30 config := testConfig()
31
32 keyring, err := testKeyring()
33 if err != nil {
34 return nil, err
35 }
36 config.MemberlistConfig.Keyring = keyring
37
38 s, err := Create(config)
39 if err != nil {
40 return nil, err
41 }
42
43 return s, nil
44 }
45
46 func keyExistsInRing(kr *memberlist.Keyring, key []byte) bool {
47 for _, installedKey := range kr.GetKeys() {
48 if bytes.Equal(key, installedKey) {
49 return true
50 }
51 }
52 return false
53 }
54
55 func TestSerf_InstallKey(t *testing.T) {
56 s1, err := testKeyringSerf()
57 if err != nil {
58 t.Fatalf("%s", err)
59 }
60 defer s1.Shutdown()
61
62 s2, err := testKeyringSerf()
63 if err != nil {
64 t.Fatalf("%s", err)
65 }
66 defer s2.Shutdown()
67
68 primaryKey := s1.config.MemberlistConfig.Keyring.GetPrimaryKey()
69
70 // Join s1 and s2
71 _, err = s1.Join([]string{s2.config.MemberlistConfig.BindAddr}, false)
72 if err != nil {
73 t.Fatalf("err: %s", err)
74 }
75
76 testutil.Yield()
77
78 // Begin tests
79 newKey := "l4ZkaypGLT8AsB0LBldthw=="
80 newKeyBytes, err := base64.StdEncoding.DecodeString(newKey)
81 if err != nil {
82 t.Fatalf("err: %s", err)
83 }
84
85 manager := s1.KeyManager()
86
87 // Install a new key onto the existing ring. This is a blocking call, so no
88 // need for a yield.
89 _, err = manager.InstallKey(newKey)
90 if err != nil {
91 t.Fatalf("err: %s", err)
92 }
93
94 // Key installation did not affect the current primary key
95 if !bytes.Equal(primaryKey, s1.config.MemberlistConfig.Keyring.GetPrimaryKey()) {
96 t.Fatal("Unexpected primary key change on s1")
97 }
98
99 if !bytes.Equal(primaryKey, s2.config.MemberlistConfig.Keyring.GetPrimaryKey()) {
100 t.Fatal("Unexpected primary key change on s2")
101 }
102
103 // New key was successfully broadcasted and installed on all members
104 if !keyExistsInRing(s1.config.MemberlistConfig.Keyring, newKeyBytes) {
105 t.Fatal("Newly-installed key not found in keyring on s1")
106 }
107
108 if !keyExistsInRing(s2.config.MemberlistConfig.Keyring, newKeyBytes) {
109 t.Fatal("Newly-installed key not found in keyring on s2")
110 }
111 }
112
113 func TestSerf_UseKey(t *testing.T) {
114 s1, err := testKeyringSerf()
115 if err != nil {
116 t.Fatalf("%s", err)
117 }
118 defer s1.Shutdown()
119
120 s2, err := testKeyringSerf()
121 if err != nil {
122 t.Fatalf("%s", err)
123 }
124 defer s2.Shutdown()
125
126 // Join s1 and s2
127 _, err = s1.Join([]string{s2.config.MemberlistConfig.BindAddr}, false)
128 if err != nil {
129 t.Fatalf("err: %s", err)
130 }
131
132 testutil.Yield()
133
134 // Begin tests
135 useKey := "csT9mxI7aTf9ap3HLBbdmA=="
136 useKeyBytes, err := base64.StdEncoding.DecodeString(useKey)
137 if err != nil {
138 t.Fatalf("err: %s", err)
139 }
140
141 manager := s1.KeyManager()
142
143 // Change the primary encryption key
144 _, err = manager.UseKey(useKey)
145 if err != nil {
146 t.Fatalf("err: %s", err)
147 }
148
149 // First make sure that the primary key is what we expect it to be
150 if !bytes.Equal(useKeyBytes, s1.config.MemberlistConfig.Keyring.GetPrimaryKey()) {
151 t.Fatal("Unexpected primary key on s1")
152 }
153
154 if !bytes.Equal(useKeyBytes, s2.config.MemberlistConfig.Keyring.GetPrimaryKey()) {
155 t.Fatal("Unexpected primary key on s2")
156 }
157
158 // Make sure an error is thrown if the key doesn't exist
159 _, err = manager.UseKey("aE6AfGEvay+UJbkfxBk4SQ==")
160 if err == nil {
161 t.Fatalf("Expected error changing to non-existent primary key")
162 }
163 }
164
165 func TestSerf_RemoveKey(t *testing.T) {
166 s1, err := testKeyringSerf()
167 if err != nil {
168 t.Fatalf("%s", err)
169 }
170 defer s1.Shutdown()
171
172 s2, err := testKeyringSerf()
173 if err != nil {
174 t.Fatalf("%s", err)
175 }
176 defer s2.Shutdown()
177
178 // Join s1 and s2
179 _, err = s1.Join([]string{s2.config.MemberlistConfig.BindAddr}, false)
180 if err != nil {
181 t.Fatalf("err: %s", err)
182 }
183
184 testutil.Yield()
185
186 // Begin tests
187 removeKey := "noha2tVc0OyD/2LtCBoAOQ=="
188 removeKeyBytes, err := base64.StdEncoding.DecodeString(removeKey)
189 if err != nil {
190 t.Fatalf("err: %s", err)
191 }
192
193 manager := s1.KeyManager()
194
195 // Remove a key from the ring
196 _, err = manager.RemoveKey(removeKey)
197 if err != nil {
198 t.Fatalf("err: %s", err)
199 }
200
201 // Key was successfully removed from all members
202 if keyExistsInRing(s1.config.MemberlistConfig.Keyring, removeKeyBytes) {
203 t.Fatal("Key not removed from keyring on s1")
204 }
205
206 if keyExistsInRing(s2.config.MemberlistConfig.Keyring, removeKeyBytes) {
207 t.Fatal("Key not removed from keyring on s2")
208 }
209 }
210
211 func TestSerf_ListKeys(t *testing.T) {
212 s1, err := testKeyringSerf()
213 if err != nil {
214 t.Fatalf("%s", err)
215 }
216 defer s1.Shutdown()
217
218 s2, err := testKeyringSerf()
219 if err != nil {
220 t.Fatalf("%s", err)
221 }
222 defer s2.Shutdown()
223
224 manager := s1.KeyManager()
225
226 initialKeyringLen := len(s1.config.MemberlistConfig.Keyring.GetKeys())
227
228 // Extra key on s2 to make sure we see it in the list
229 extraKey := "JHAxGU13qDaXhUW6jIpyog=="
230 extraKeyBytes, err := base64.StdEncoding.DecodeString(extraKey)
231 if err != nil {
232 t.Fatalf("err: %s", err)
233 }
234 s2.config.MemberlistConfig.Keyring.AddKey(extraKeyBytes)
235
236 // Join s1 and s2
237 _, err = s1.Join([]string{s2.config.MemberlistConfig.BindAddr}, false)
238 if err != nil {
239 t.Fatalf("err: %s", err)
240 }
241
242 testutil.Yield()
243
244 resp, err := manager.ListKeys()
245 if err != nil {
246 t.Fatalf("err: %s", err)
247 }
248
249 // Found all keys in the list
250 expected := initialKeyringLen + 1
251 if expected != len(resp.Keys) {
252 t.Fatalf("Expected %d keys in result, found %d", expected, len(resp.Keys))
253 }
254
255 found := false
256 for key, _ := range resp.Keys {
257 if key == extraKey {
258 found = true
259 }
260 }
261 if !found {
262 t.Fatalf("Did not find expected key in list: %s", extraKey)
263 }
264
265 // Number of members with extra key installed should be 1
266 for key, num := range resp.Keys {
267 if key == extraKey && num != 1 {
268 t.Fatalf("Expected 1 nodes with key %s but have %d", extraKey, num)
269 }
270 }
271 }
0 package serf
1
2 import (
3 "sync/atomic"
4 )
5
6 // LamportClock is a thread safe implementation of a lamport clock. It
7 // uses efficient atomic operations for all of its functions, falling back
8 // to a heavy lock only if there are enough CAS failures.
9 type LamportClock struct {
10 counter uint64
11 }
12
13 // LamportTime is the value of a LamportClock.
14 type LamportTime uint64
15
16 // Time is used to return the current value of the lamport clock
17 func (l *LamportClock) Time() LamportTime {
18 return LamportTime(atomic.LoadUint64(&l.counter))
19 }
20
21 // Increment is used to increment and return the value of the lamport clock
22 func (l *LamportClock) Increment() LamportTime {
23 return LamportTime(atomic.AddUint64(&l.counter, 1))
24 }
25
26 // Witness is called to update our local clock if necessary after
27 // witnessing a clock value received from another process
28 func (l *LamportClock) Witness(v LamportTime) {
29 WITNESS:
30 // If the other value is old, we do not need to do anything
31 cur := atomic.LoadUint64(&l.counter)
32 other := uint64(v)
33 if other < cur {
34 return
35 }
36
37 // Ensure that our local clock is at least one ahead.
38 if !atomic.CompareAndSwapUint64(&l.counter, cur, other+1) {
39 // The CAS failed, so we just retry. Eventually our CAS should
40 // succeed or a future witness will pass us by and our witness
41 // will end.
42 goto WITNESS
43 }
44 }
0 package serf
1
2 import (
3 "testing"
4 )
5
6 func TestLamportClock(t *testing.T) {
7 l := &LamportClock{}
8
9 if l.Time() != 0 {
10 t.Fatalf("bad time value")
11 }
12
13 if l.Increment() != 1 {
14 t.Fatalf("bad time value")
15 }
16
17 if l.Time() != 1 {
18 t.Fatalf("bad time value")
19 }
20
21 l.Witness(41)
22
23 if l.Time() != 42 {
24 t.Fatalf("bad time value")
25 }
26
27 l.Witness(41)
28
29 if l.Time() != 42 {
30 t.Fatalf("bad time value")
31 }
32
33 l.Witness(30)
34
35 if l.Time() != 42 {
36 t.Fatalf("bad time value")
37 }
38 }
0 package serf
1
2 import (
3 "net"
4
5 "github.com/hashicorp/memberlist"
6 )
7
8 type MergeDelegate interface {
9 NotifyMerge([]*Member) (cancel bool)
10 }
11
12 type mergeDelegate struct {
13 serf *Serf
14 }
15
16 func (m *mergeDelegate) NotifyMerge(nodes []*memberlist.Node) (cancel bool) {
17 members := make([]*Member, len(nodes))
18 for idx, n := range nodes {
19 members[idx] = &Member{
20 Name: n.Name,
21 Addr: net.IP(n.Addr),
22 Port: n.Port,
23 Tags: m.serf.decodeTags(n.Meta),
24 Status: StatusNone,
25 ProtocolMin: n.PMin,
26 ProtocolMax: n.PMax,
27 ProtocolCur: n.PCur,
28 DelegateMin: n.DMin,
29 DelegateMax: n.DMax,
30 DelegateCur: n.DCur,
31 }
32 }
33 return m.serf.config.Merge.NotifyMerge(members)
34 }
0 package serf
1
2 import (
3 "bytes"
4 "github.com/hashicorp/go-msgpack/codec"
5 "time"
6 )
7
8 // messageType are the types of gossip messages Serf will send along
9 // memberlist.
10 type messageType uint8
11
12 const (
13 messageLeaveType messageType = iota
14 messageJoinType
15 messagePushPullType
16 messageUserEventType
17 messageQueryType
18 messageQueryResponseType
19 messageConflictResponseType
20 messageKeyRequestType
21 messageKeyResponseType
22 )
23
24 const (
25 // Ack flag is used to force receiver to send an ack back
26 queryFlagAck uint32 = 1 << iota
27
28 // NoBroadcast is used to prevent re-broadcast of a query.
29 // this can be used to selectively send queries to individual members
30 queryFlagNoBroadcast
31 )
32
33 // filterType is used with a queryFilter to specify the type of
34 // filter we are sending
35 type filterType uint8
36
37 const (
38 filterNodeType filterType = iota
39 filterTagType
40 )
41
42 // messageJoin is the message broadcasted after we join to
43 // associated the node with a lamport clock
44 type messageJoin struct {
45 LTime LamportTime
46 Node string
47 }
48
49 // messageLeave is the message broadcasted to signal the intentional to
50 // leave.
51 type messageLeave struct {
52 LTime LamportTime
53 Node string
54 }
55
56 // messagePushPullType is used when doing a state exchange. This
57 // is a relatively large message, but is sent infrequently
58 type messagePushPull struct {
59 LTime LamportTime // Current node lamport time
60 StatusLTimes map[string]LamportTime // Maps the node to its status time
61 LeftMembers []string // List of left nodes
62 EventLTime LamportTime // Lamport time for event clock
63 Events []*userEvents // Recent events
64 QueryLTime LamportTime // Lamport time for query clock
65 }
66
67 // messageUserEvent is used for user-generated events
68 type messageUserEvent struct {
69 LTime LamportTime
70 Name string
71 Payload []byte
72 CC bool // "Can Coalesce". Zero value is compatible with Serf 0.1
73 }
74
75 // messageQuery is used for query events
76 type messageQuery struct {
77 LTime LamportTime // Event lamport time
78 ID uint32 // Query ID, randomly generated
79 Addr []byte // Source address, used for a direct reply
80 Port uint16 // Source port, used for a direct reply
81 Filters [][]byte // Potential query filters
82 Flags uint32 // Used to provide various flags
83 Timeout time.Duration // Maximum time between delivery and response
84 Name string // Query name
85 Payload []byte // Query payload
86 }
87
88 // Ack checks if the ack flag is set
89 func (m *messageQuery) Ack() bool {
90 return (m.Flags & queryFlagAck) != 0
91 }
92
93 // NoBroadcast checks if the no broadcast flag is set
94 func (m *messageQuery) NoBroadcast() bool {
95 return (m.Flags & queryFlagNoBroadcast) != 0
96 }
97
98 // filterNode is used with the filterNodeType, and is a list
99 // of node names
100 type filterNode []string
101
102 // filterTag is used with the filterTagType and is a regular
103 // expression to apply to a tag
104 type filterTag struct {
105 Tag string
106 Expr string
107 }
108
109 // messageQueryResponse is used to respond to a query
110 type messageQueryResponse struct {
111 LTime LamportTime // Event lamport time
112 ID uint32 // Query ID
113 From string // Node name
114 Flags uint32 // Used to provide various flags
115 Payload []byte // Optional response payload
116 }
117
118 // Ack checks if the ack flag is set
119 func (m *messageQueryResponse) Ack() bool {
120 return (m.Flags & queryFlagAck) != 0
121 }
122
123 func decodeMessage(buf []byte, out interface{}) error {
124 var handle codec.MsgpackHandle
125 return codec.NewDecoder(bytes.NewReader(buf), &handle).Decode(out)
126 }
127
128 func encodeMessage(t messageType, msg interface{}) ([]byte, error) {
129 buf := bytes.NewBuffer(nil)
130 buf.WriteByte(uint8(t))
131
132 handle := codec.MsgpackHandle{}
133 encoder := codec.NewEncoder(buf, &handle)
134 err := encoder.Encode(msg)
135 return buf.Bytes(), err
136 }
137
138 func encodeFilter(f filterType, filt interface{}) ([]byte, error) {
139 buf := bytes.NewBuffer(nil)
140 buf.WriteByte(uint8(f))
141
142 handle := codec.MsgpackHandle{}
143 encoder := codec.NewEncoder(buf, &handle)
144 err := encoder.Encode(filt)
145 return buf.Bytes(), err
146 }
0 package serf
1
2 import (
3 "reflect"
4 "testing"
5 )
6
7 func TestQueryFlags(t *testing.T) {
8 if queryFlagAck != 1 {
9 t.Fatalf("Bad: %v", queryFlagAck)
10 }
11 if queryFlagNoBroadcast != 2 {
12 t.Fatalf("Bad: %v", queryFlagNoBroadcast)
13 }
14 }
15
16 func TestEncodeMessage(t *testing.T) {
17 in := &messageLeave{Node: "foo"}
18 raw, err := encodeMessage(messageLeaveType, in)
19 if err != nil {
20 t.Fatalf("err: %s", err)
21 }
22
23 if raw[0] != byte(messageLeaveType) {
24 t.Fatal("should have type header")
25 }
26
27 var out messageLeave
28 if err := decodeMessage(raw[1:], &out); err != nil {
29 t.Fatalf("err: %s", err)
30 }
31
32 if !reflect.DeepEqual(in, &out) {
33 t.Fatalf("mis-match")
34 }
35 }
36
37 func TestEncodeFilter(t *testing.T) {
38 nodes := []string{"foo", "bar"}
39
40 raw, err := encodeFilter(filterNodeType, nodes)
41 if err != nil {
42 t.Fatalf("err: %s", err)
43 }
44
45 if raw[0] != byte(filterNodeType) {
46 t.Fatal("should have type header")
47 }
48
49 var out []string
50 if err := decodeMessage(raw[1:], &out); err != nil {
51 t.Fatalf("err: %s", err)
52 }
53
54 if !reflect.DeepEqual(nodes, out) {
55 t.Fatalf("mis-match")
56 }
57 }
0 package serf
1
2 import (
3 "math"
4 "regexp"
5 "sync"
6 "time"
7 )
8
9 // QueryParam is provided to Query() to configure the parameters of the
10 // query. If not provided, sane defaults will be used.
11 type QueryParam struct {
12 // If provided, we restrict the nodes that should respond to those
13 // with names in this list
14 FilterNodes []string
15
16 // FilterTags maps a tag name to a regular expression that is applied
17 // to restrict the nodes that should respond
18 FilterTags map[string]string
19
20 // If true, we are requesting an delivery acknowledgement from
21 // every node that meets the filter requirement. This means nodes
22 // the receive the message but do not pass the filters, will not
23 // send an ack.
24 RequestAck bool
25
26 // The timeout limits how long the query is left open. If not provided,
27 // then a default timeout is used based on the configuration of Serf
28 Timeout time.Duration
29 }
30
31 // DefaultQueryTimeout returns the default timeout value for a query
32 // Computed as GossipInterval * QueryTimeoutMult * log(N+1)
33 func (s *Serf) DefaultQueryTimeout() time.Duration {
34 n := s.memberlist.NumMembers()
35 timeout := s.config.MemberlistConfig.GossipInterval
36 timeout *= time.Duration(s.config.QueryTimeoutMult)
37 timeout *= time.Duration(math.Ceil(math.Log10(float64(n + 1))))
38 return timeout
39 }
40
41 // DefaultQueryParam is used to return the default query parameters
42 func (s *Serf) DefaultQueryParams() *QueryParam {
43 return &QueryParam{
44 FilterNodes: nil,
45 FilterTags: nil,
46 RequestAck: false,
47 Timeout: s.DefaultQueryTimeout(),
48 }
49 }
50
51 // encodeFilters is used to convert the filters into the wire format
52 func (q *QueryParam) encodeFilters() ([][]byte, error) {
53 var filters [][]byte
54
55 // Add the node filter
56 if len(q.FilterNodes) > 0 {
57 if buf, err := encodeFilter(filterNodeType, q.FilterNodes); err != nil {
58 return nil, err
59 } else {
60 filters = append(filters, buf)
61 }
62 }
63
64 // Add the tag filters
65 for tag, expr := range q.FilterTags {
66 filt := filterTag{tag, expr}
67 if buf, err := encodeFilter(filterTagType, &filt); err != nil {
68 return nil, err
69 } else {
70 filters = append(filters, buf)
71 }
72 }
73
74 return filters, nil
75 }
76
77 // QueryResponse is returned for each new Query. It is used to collect
78 // Ack's as well as responses and to provide those back to a client.
79 type QueryResponse struct {
80 // ackCh is used to send the name of a node for which we've received an ack
81 ackCh chan string
82
83 // deadline is the query end time (start + query timeout)
84 deadline time.Time
85
86 // Query ID
87 id uint32
88
89 // Stores the LTime of the query
90 lTime LamportTime
91
92 // respCh is used to send a response from a node
93 respCh chan NodeResponse
94
95 closed bool
96 closeLock sync.Mutex
97 }
98
99 // newQueryResponse is used to construct a new query response
100 func newQueryResponse(n int, q *messageQuery) *QueryResponse {
101 resp := &QueryResponse{
102 deadline: time.Now().Add(q.Timeout),
103 id: q.ID,
104 lTime: q.LTime,
105 respCh: make(chan NodeResponse, n),
106 }
107 if q.Ack() {
108 resp.ackCh = make(chan string, n)
109 }
110 return resp
111 }
112
113 // Close is used to close the query, which will close the underlying
114 // channels and prevent further deliveries
115 func (r *QueryResponse) Close() {
116 r.closeLock.Lock()
117 defer r.closeLock.Unlock()
118 if r.closed {
119 return
120 }
121 r.closed = true
122 if r.ackCh != nil {
123 close(r.ackCh)
124 }
125 if r.respCh != nil {
126 close(r.respCh)
127 }
128 }
129
130 // Deadline returns the ending deadline of the query
131 func (r *QueryResponse) Deadline() time.Time {
132 return r.deadline
133 }
134
135 // Finished returns if the query is finished running
136 func (r *QueryResponse) Finished() bool {
137 return r.closed || time.Now().After(r.deadline)
138 }
139
140 // AckCh returns a channel that can be used to listen for acks
141 // Channel will be closed when the query is finished. This is nil,
142 // if the query did not specify RequestAck.
143 func (r *QueryResponse) AckCh() <-chan string {
144 return r.ackCh
145 }
146
147 // ResponseCh returns a channel that can be used to listen for responses.
148 // Channel will be closed when the query is finished.
149 func (r *QueryResponse) ResponseCh() <-chan NodeResponse {
150 return r.respCh
151 }
152
153 // NodeResponse is used to represent a single response from a node
154 type NodeResponse struct {
155 From string
156 Payload []byte
157 }
158
159 // shouldProcessQuery checks if a query should be proceeded given
160 // a set of filers.
161 func (s *Serf) shouldProcessQuery(filters [][]byte) bool {
162 for _, filter := range filters {
163 switch filterType(filter[0]) {
164 case filterNodeType:
165 // Decode the filter
166 var nodes filterNode
167 if err := decodeMessage(filter[1:], &nodes); err != nil {
168 s.logger.Printf("[WARN] serf: failed to decode filterNodeType: %v", err)
169 return false
170 }
171
172 // Check if we are being targeted
173 found := false
174 for _, n := range nodes {
175 if n == s.config.NodeName {
176 found = true
177 break
178 }
179 }
180 if !found {
181 return false
182 }
183
184 case filterTagType:
185 // Decode the filter
186 var filt filterTag
187 if err := decodeMessage(filter[1:], &filt); err != nil {
188 s.logger.Printf("[WARN] serf: failed to decode filterTagType: %v", err)
189 return false
190 }
191
192 // Check if we match this regex
193 tags := s.config.Tags
194 matched, err := regexp.MatchString(filt.Expr, tags[filt.Tag])
195 if err != nil {
196 s.logger.Printf("[WARN] serf: failed to compile filter regex (%s): %v", filt.Expr, err)
197 return false
198 }
199 if !matched {
200 return false
201 }
202
203 default:
204 s.logger.Printf("[WARN] serf: query has unrecognized filter type: %d", filter[0])
205 return false
206 }
207 }
208 return true
209 }
0 package serf
1
2 import (
3 "testing"
4 "time"
5 )
6
7 func TestDefaultQuery(t *testing.T) {
8 s1Config := testConfig()
9 s1, err := Create(s1Config)
10 if err != nil {
11 t.Fatalf("err: %s", err)
12 }
13 defer s1.Shutdown()
14
15 timeout := s1.DefaultQueryTimeout()
16 if timeout != s1Config.MemberlistConfig.GossipInterval*time.Duration(s1Config.QueryTimeoutMult) {
17 t.Fatalf("bad: %v", timeout)
18 }
19
20 params := s1.DefaultQueryParams()
21 if params.FilterNodes != nil {
22 t.Fatalf("bad: %v", *params)
23 }
24 if params.FilterTags != nil {
25 t.Fatalf("bad: %v", *params)
26 }
27 if params.RequestAck {
28 t.Fatalf("bad: %v", *params)
29 }
30 if params.Timeout != timeout {
31 t.Fatalf("bad: %v", *params)
32 }
33 }
34
35 func TestQueryParams_EncodeFilters(t *testing.T) {
36 q := &QueryParam{
37 FilterNodes: []string{"foo", "bar"},
38 FilterTags: map[string]string{
39 "role": "^web",
40 "datacenter": "aws$",
41 },
42 }
43
44 filters, err := q.encodeFilters()
45 if err != nil {
46 t.Fatalf("err: %s", err)
47 }
48 if len(filters) != 3 {
49 t.Fatalf("bad: %v", filters)
50 }
51
52 nodeFilt := filters[0]
53 if filterType(nodeFilt[0]) != filterNodeType {
54 t.Fatalf("bad: %v", nodeFilt)
55 }
56
57 tagFilt := filters[1]
58 if filterType(tagFilt[0]) != filterTagType {
59 t.Fatalf("bad: %v", tagFilt)
60 }
61
62 tagFilt = filters[2]
63 if filterType(tagFilt[0]) != filterTagType {
64 t.Fatalf("bad: %v", tagFilt)
65 }
66 }
67
68 func TestSerf_ShouldProcess(t *testing.T) {
69 s1Config := testConfig()
70 s1Config.NodeName = "zip"
71 s1Config.Tags = map[string]string{
72 "role": "webserver",
73 "datacenter": "east-aws",
74 }
75 s1, err := Create(s1Config)
76 if err != nil {
77 t.Fatalf("err: %s", err)
78 }
79 defer s1.Shutdown()
80
81 // Try a matching query
82 q := &QueryParam{
83 FilterNodes: []string{"foo", "bar", "zip"},
84 FilterTags: map[string]string{
85 "role": "^web",
86 "datacenter": "aws$",
87 },
88 }
89 filters, err := q.encodeFilters()
90 if err != nil {
91 t.Fatalf("err: %s", err)
92 }
93
94 if !s1.shouldProcessQuery(filters) {
95 t.Fatalf("expected true")
96 }
97
98 // Omit node
99 q = &QueryParam{
100 FilterNodes: []string{"foo", "bar"},
101 }
102 filters, err = q.encodeFilters()
103 if err != nil {
104 t.Fatalf("err: %s", err)
105 }
106 if s1.shouldProcessQuery(filters) {
107 t.Fatalf("expected false")
108 }
109
110 // Filter on missing tag
111 q = &QueryParam{
112 FilterTags: map[string]string{
113 "other": "cool",
114 },
115 }
116 filters, err = q.encodeFilters()
117 if err != nil {
118 t.Fatalf("err: %s", err)
119 }
120 if s1.shouldProcessQuery(filters) {
121 t.Fatalf("expected false")
122 }
123
124 // Bad tag
125 q = &QueryParam{
126 FilterTags: map[string]string{
127 "role": "db",
128 },
129 }
130 filters, err = q.encodeFilters()
131 if err != nil {
132 t.Fatalf("err: %s", err)
133 }
134 if s1.shouldProcessQuery(filters) {
135 t.Fatalf("expected false")
136 }
137 }
0 package serf
1
2 import (
3 "bytes"
4 "encoding/base64"
5 "encoding/json"
6 "errors"
7 "fmt"
8 "io/ioutil"
9 "log"
10 "math/rand"
11 "net"
12 "strconv"
13 "sync"
14 "time"
15
16 "github.com/armon/go-metrics"
17 "github.com/hashicorp/go-msgpack/codec"
18 "github.com/hashicorp/memberlist"
19 )
20
21 // These are the protocol versions that Serf can _understand_. These are
22 // Serf-level protocol versions that are passed down as the delegate
23 // version to memberlist below.
24 const (
25 ProtocolVersionMin uint8 = 2
26 ProtocolVersionMax = 4
27 )
28
29 const (
30 // Used to detect if the meta data is tags
31 // or if it is a raw role
32 tagMagicByte uint8 = 255
33 )
34
35 var (
36 // FeatureNotSupported is returned if a feature cannot be used
37 // due to an older protocol version being used.
38 FeatureNotSupported = fmt.Errorf("Feature not supported")
39 )
40
41 func init() {
42 // Seed the random number generator
43 rand.Seed(time.Now().UnixNano())
44 }
45
46 // Serf is a single node that is part of a single cluster that gets
47 // events about joins/leaves/failures/etc. It is created with the Create
48 // method.
49 //
50 // All functions on the Serf structure are safe to call concurrently.
51 type Serf struct {
52 // The clocks for different purposes. These MUST be the first things
53 // in this struct due to Golang issue #599.
54 clock LamportClock
55 eventClock LamportClock
56 queryClock LamportClock
57
58 broadcasts *memberlist.TransmitLimitedQueue
59 config *Config
60 failedMembers []*memberState
61 leftMembers []*memberState
62 memberlist *memberlist.Memberlist
63 memberLock sync.RWMutex
64 members map[string]*memberState
65
66 // Circular buffers for recent intents, used
67 // in case we get the intent before the relevant event
68 recentLeave []nodeIntent
69 recentLeaveIndex int
70 recentJoin []nodeIntent
71 recentJoinIndex int
72
73 eventBroadcasts *memberlist.TransmitLimitedQueue
74 eventBuffer []*userEvents
75 eventJoinIgnore bool
76 eventMinTime LamportTime
77 eventLock sync.RWMutex
78
79 queryBroadcasts *memberlist.TransmitLimitedQueue
80 queryBuffer []*queries
81 queryMinTime LamportTime
82 queryResponse map[LamportTime]*QueryResponse
83 queryLock sync.RWMutex
84
85 logger *log.Logger
86 joinLock sync.Mutex
87 stateLock sync.Mutex
88 state SerfState
89 shutdownCh chan struct{}
90
91 snapshotter *Snapshotter
92 keyManager *KeyManager
93 }
94
95 // SerfState is the state of the Serf instance.
96 type SerfState int
97
98 const (
99 SerfAlive SerfState = iota
100 SerfLeaving
101 SerfLeft
102 SerfShutdown
103 )
104
105 func (s SerfState) String() string {
106 switch s {
107 case SerfAlive:
108 return "alive"
109 case SerfLeaving:
110 return "leaving"
111 case SerfLeft:
112 return "left"
113 case SerfShutdown:
114 return "shutdown"
115 default:
116 return "unknown"
117 }
118 }
119
120 // Member is a single member of the Serf cluster.
121 type Member struct {
122 Name string
123 Addr net.IP
124 Port uint16
125 Tags map[string]string
126 Status MemberStatus
127
128 // The minimum, maximum, and current values of the protocol versions
129 // and delegate (Serf) protocol versions that each member can understand
130 // or is speaking.
131 ProtocolMin uint8
132 ProtocolMax uint8
133 ProtocolCur uint8
134 DelegateMin uint8
135 DelegateMax uint8
136 DelegateCur uint8
137 }
138
139 // MemberStatus is the state that a member is in.
140 type MemberStatus int
141
142 const (
143 StatusNone MemberStatus = iota
144 StatusAlive
145 StatusLeaving
146 StatusLeft
147 StatusFailed
148 )
149
150 func (s MemberStatus) String() string {
151 switch s {
152 case StatusNone:
153 return "none"
154 case StatusAlive:
155 return "alive"
156 case StatusLeaving:
157 return "leaving"
158 case StatusLeft:
159 return "left"
160 case StatusFailed:
161 return "failed"
162 default:
163 panic(fmt.Sprintf("unknown MemberStatus: %d", s))
164 }
165 }
166
167 // memberState is used to track members that are no longer active due to
168 // leaving, failing, partitioning, etc. It tracks the member along with
169 // when that member was marked as leaving.
170 type memberState struct {
171 Member
172 statusLTime LamportTime // lamport clock time of last received message
173 leaveTime time.Time // wall clock time of leave
174 }
175
176 // nodeIntent is used to buffer intents for out-of-order deliveries
177 type nodeIntent struct {
178 LTime LamportTime
179 Node string
180 }
181
182 // userEvent is used to buffer events to prevent re-delivery
183 type userEvent struct {
184 Name string
185 Payload []byte
186 }
187
188 func (ue *userEvent) Equals(other *userEvent) bool {
189 if ue.Name != other.Name {
190 return false
191 }
192 if bytes.Compare(ue.Payload, other.Payload) != 0 {
193 return false
194 }
195 return true
196 }
197
198 // userEvents stores all the user events at a specific time
199 type userEvents struct {
200 LTime LamportTime
201 Events []userEvent
202 }
203
204 // queries stores all the query ids at a specific time
205 type queries struct {
206 LTime LamportTime
207 QueryIDs []uint32
208 }
209
210 const (
211 UserEventSizeLimit = 512 // Maximum byte size for event name and payload
212 QuerySizeLimit = 1024 // Maximum byte size for query
213 QueryResponseSizeLimit = 1024 // Maximum bytes size for response
214 snapshotSizeLimit = 128 * 1024 // Maximum 128 KB snapshot
215 )
216
217 // Create creates a new Serf instance, starting all the background tasks
218 // to maintain cluster membership information.
219 //
220 // After calling this function, the configuration should no longer be used
221 // or modified by the caller.
222 func Create(conf *Config) (*Serf, error) {
223 conf.Init()
224 if conf.ProtocolVersion < ProtocolVersionMin {
225 return nil, fmt.Errorf("Protocol version '%d' too low. Must be in range: [%d, %d]",
226 conf.ProtocolVersion, ProtocolVersionMin, ProtocolVersionMax)
227 } else if conf.ProtocolVersion > ProtocolVersionMax {
228 return nil, fmt.Errorf("Protocol version '%d' too high. Must be in range: [%d, %d]",
229 conf.ProtocolVersion, ProtocolVersionMin, ProtocolVersionMax)
230 }
231
232 serf := &Serf{
233 config: conf,
234 logger: log.New(conf.LogOutput, "", log.LstdFlags),
235 members: make(map[string]*memberState),
236 queryResponse: make(map[LamportTime]*QueryResponse),
237 shutdownCh: make(chan struct{}),
238 state: SerfAlive,
239 }
240
241 // Check that the meta data length is okay
242 if len(serf.encodeTags(conf.Tags)) > memberlist.MetaMaxSize {
243 return nil, fmt.Errorf("Encoded length of tags exceeds limit of %d bytes", memberlist.MetaMaxSize)
244 }
245
246 // Check if serf member event coalescing is enabled
247 if conf.CoalescePeriod > 0 && conf.QuiescentPeriod > 0 && conf.EventCh != nil {
248 c := &memberEventCoalescer{
249 lastEvents: make(map[string]EventType),
250 latestEvents: make(map[string]coalesceEvent),
251 }
252
253 conf.EventCh = coalescedEventCh(conf.EventCh, serf.shutdownCh,
254 conf.CoalescePeriod, conf.QuiescentPeriod, c)
255 }
256
257 // Check if user event coalescing is enabled
258 if conf.UserCoalescePeriod > 0 && conf.UserQuiescentPeriod > 0 && conf.EventCh != nil {
259 c := &userEventCoalescer{
260 events: make(map[string]*latestUserEvents),
261 }
262
263 conf.EventCh = coalescedEventCh(conf.EventCh, serf.shutdownCh,
264 conf.UserCoalescePeriod, conf.UserQuiescentPeriod, c)
265 }
266
267 // Listen for internal Serf queries. This is setup before the snapshotter, since
268 // we want to capture the query-time, but the internal listener does not passthrough
269 // the queries
270 outCh, err := newSerfQueries(serf, serf.logger, conf.EventCh, serf.shutdownCh)
271 if err != nil {
272 return nil, fmt.Errorf("Failed to setup serf query handler: %v", err)
273 }
274 conf.EventCh = outCh
275
276 // Try access the snapshot
277 var oldClock, oldEventClock, oldQueryClock LamportTime
278 var prev []*PreviousNode
279 if conf.SnapshotPath != "" {
280 eventCh, snap, err := NewSnapshotter(conf.SnapshotPath,
281 snapshotSizeLimit,
282 conf.RejoinAfterLeave,
283 serf.logger,
284 &serf.clock,
285 conf.EventCh,
286 serf.shutdownCh)
287 if err != nil {
288 return nil, fmt.Errorf("Failed to setup snapshot: %v", err)
289 }
290 serf.snapshotter = snap
291 conf.EventCh = eventCh
292 prev = snap.AliveNodes()
293 oldClock = snap.LastClock()
294 oldEventClock = snap.LastEventClock()
295 oldQueryClock = snap.LastQueryClock()
296 serf.eventMinTime = oldEventClock + 1
297 serf.queryMinTime = oldQueryClock + 1
298 }
299
300 // Setup the various broadcast queues, which we use to send our own
301 // custom broadcasts along the gossip channel.
302 serf.broadcasts = &memberlist.TransmitLimitedQueue{
303 NumNodes: func() int {
304 return len(serf.members)
305 },
306 RetransmitMult: conf.MemberlistConfig.RetransmitMult,
307 }
308 serf.eventBroadcasts = &memberlist.TransmitLimitedQueue{
309 NumNodes: func() int {
310 return len(serf.members)
311 },
312 RetransmitMult: conf.MemberlistConfig.RetransmitMult,
313 }
314 serf.queryBroadcasts = &memberlist.TransmitLimitedQueue{
315 NumNodes: func() int {
316 return len(serf.members)
317 },
318 RetransmitMult: conf.MemberlistConfig.RetransmitMult,
319 }
320
321 // Create the buffer for recent intents
322 serf.recentJoin = make([]nodeIntent, conf.RecentIntentBuffer)
323 serf.recentLeave = make([]nodeIntent, conf.RecentIntentBuffer)
324
325 // Create a buffer for events and queries
326 serf.eventBuffer = make([]*userEvents, conf.EventBuffer)
327 serf.queryBuffer = make([]*queries, conf.QueryBuffer)
328
329 // Ensure our lamport clock is at least 1, so that the default
330 // join LTime of 0 does not cause issues
331 serf.clock.Increment()
332 serf.eventClock.Increment()
333 serf.queryClock.Increment()
334
335 // Restore the clock from snap if we have one
336 serf.clock.Witness(oldClock)
337 serf.eventClock.Witness(oldEventClock)
338 serf.queryClock.Witness(oldQueryClock)
339
340 // Modify the memberlist configuration with keys that we set
341 conf.MemberlistConfig.Events = &eventDelegate{serf: serf}
342 conf.MemberlistConfig.Conflict = &conflictDelegate{serf: serf}
343 conf.MemberlistConfig.Delegate = &delegate{serf: serf}
344 conf.MemberlistConfig.DelegateProtocolVersion = conf.ProtocolVersion
345 conf.MemberlistConfig.DelegateProtocolMin = ProtocolVersionMin
346 conf.MemberlistConfig.DelegateProtocolMax = ProtocolVersionMax
347 conf.MemberlistConfig.Name = conf.NodeName
348 conf.MemberlistConfig.ProtocolVersion = ProtocolVersionMap[conf.ProtocolVersion]
349
350 // Setup a merge delegate if necessary
351 if conf.Merge != nil {
352 conf.MemberlistConfig.Merge = &mergeDelegate{serf: serf}
353 }
354
355 // Create the underlying memberlist that will manage membership
356 // and failure detection for the Serf instance.
357 memberlist, err := memberlist.Create(conf.MemberlistConfig)
358 if err != nil {
359 return nil, err
360 }
361
362 serf.memberlist = memberlist
363
364 // Create a key manager for handling all encryption key changes
365 serf.keyManager = &KeyManager{serf: serf}
366
367 // Start the background tasks. See the documentation above each method
368 // for more information on their role.
369 go serf.handleReap()
370 go serf.handleReconnect()
371 go serf.checkQueueDepth("Intent", serf.broadcasts)
372 go serf.checkQueueDepth("Event", serf.eventBroadcasts)
373 go serf.checkQueueDepth("Query", serf.queryBroadcasts)
374
375 // Attempt to re-join the cluster if we have known nodes
376 if len(prev) != 0 {
377 go serf.handleRejoin(prev)
378 }
379
380 return serf, nil
381 }
382
383 // ProtocolVersion returns the current protocol version in use by Serf.
384 // This is the Serf protocol version, not the memberlist protocol version.
385 func (s *Serf) ProtocolVersion() uint8 {
386 return s.config.ProtocolVersion
387 }
388
389 // EncryptionEnabled is a predicate that determines whether or not encryption
390 // is enabled, which can be possible in one of 2 cases:
391 // - Single encryption key passed at agent start (no persistence)
392 // - Keyring file provided at agent start
393 func (s *Serf) EncryptionEnabled() bool {
394 return s.config.MemberlistConfig.Keyring != nil
395 }
396
397 // KeyManager returns the key manager for the current Serf instance.
398 func (s *Serf) KeyManager() *KeyManager {
399 return s.keyManager
400 }
401
402 // UserEvent is used to broadcast a custom user event with a given
403 // name and payload. The events must be fairly small, and if the
404 // size limit is exceeded and error will be returned. If coalesce is enabled,
405 // nodes are allowed to coalesce this event. Coalescing is only available
406 // starting in v0.2
407 func (s *Serf) UserEvent(name string, payload []byte, coalesce bool) error {
408 // Check the size limit
409 if len(name)+len(payload) > UserEventSizeLimit {
410 return fmt.Errorf("user event exceeds limit of %d bytes", UserEventSizeLimit)
411 }
412
413 // Create a message
414 msg := messageUserEvent{
415 LTime: s.eventClock.Time(),
416 Name: name,
417 Payload: payload,
418 CC: coalesce,
419 }
420 s.eventClock.Increment()
421
422 // Process update locally
423 s.handleUserEvent(&msg)
424
425 // Start broadcasting the event
426 raw, err := encodeMessage(messageUserEventType, &msg)
427 if err != nil {
428 return err
429 }
430 s.eventBroadcasts.QueueBroadcast(&broadcast{
431 msg: raw,
432 })
433 return nil
434 }
435
436 // Query is used to broadcast a new query. The query must be fairly small,
437 // and an error will be returned if the size limit is exceeded. This is only
438 // available with protocol version 4 and newer. Query parameters are optional,
439 // and if not provided, a sane set of defaults will be used.
440 func (s *Serf) Query(name string, payload []byte, params *QueryParam) (*QueryResponse, error) {
441 // Check that the latest protocol is in use
442 if s.ProtocolVersion() < 4 {
443 return nil, FeatureNotSupported
444 }
445
446 // Provide default parameters if none given
447 if params == nil {
448 params = s.DefaultQueryParams()
449 } else if params.Timeout == 0 {
450 params.Timeout = s.DefaultQueryTimeout()
451 }
452
453 // Get the local node
454 local := s.memberlist.LocalNode()
455
456 // Encode the filters
457 filters, err := params.encodeFilters()
458 if err != nil {
459 return nil, fmt.Errorf("Failed to format filters: %v", err)
460 }
461
462 // Setup the flags
463 var flags uint32
464 if params.RequestAck {
465 flags |= queryFlagAck
466 }
467
468 // Create a message
469 q := messageQuery{
470 LTime: s.queryClock.Time(),
471 ID: uint32(rand.Int31()),
472 Addr: local.Addr,
473 Port: local.Port,
474 Filters: filters,
475 Flags: flags,
476 Timeout: params.Timeout,
477 Name: name,
478 Payload: payload,
479 }
480
481 // Encode the query
482 raw, err := encodeMessage(messageQueryType, &q)
483 if err != nil {
484 return nil, err
485 }
486
487 // Check the size
488 if len(raw) > QuerySizeLimit {
489 return nil, fmt.Errorf("query exceeds limit of %d bytes", QuerySizeLimit)
490 }
491
492 // Register QueryResponse to track acks and responses
493 resp := newQueryResponse(s.memberlist.NumMembers(), &q)
494 s.registerQueryResponse(params.Timeout, resp)
495
496 // Process query locally
497 s.handleQuery(&q)
498
499 // Start broadcasting the event
500 s.queryBroadcasts.QueueBroadcast(&broadcast{
501 msg: raw,
502 })
503 return resp, nil
504 }
505
506 // registerQueryResponse is used to setup the listeners for the query,
507 // and to schedule closing the query after the timeout.
508 func (s *Serf) registerQueryResponse(timeout time.Duration, resp *QueryResponse) {
509 s.queryLock.Lock()
510 defer s.queryLock.Unlock()
511
512 // Map the LTime to the QueryResponse. This is necessarily 1-to-1,
513 // since we increment the time for each new query.
514 s.queryResponse[resp.lTime] = resp
515
516 // Setup a timer to close the response and deregister after the timeout
517 time.AfterFunc(timeout, func() {
518 s.queryLock.Lock()
519 delete(s.queryResponse, resp.lTime)
520 resp.Close()
521 s.queryLock.Unlock()
522 })
523 }
524
525 // SetTags is used to dynamically update the tags associated with
526 // the local node. This will propagate the change to the rest of
527 // the cluster. Blocks until a the message is broadcast out.
528 func (s *Serf) SetTags(tags map[string]string) error {
529 // Check that the meta data length is okay
530 if len(s.encodeTags(tags)) > memberlist.MetaMaxSize {
531 return fmt.Errorf("Encoded length of tags exceeds limit of %d bytes",
532 memberlist.MetaMaxSize)
533 }
534
535 // Update the config
536 s.config.Tags = tags
537
538 // Trigger a memberlist update
539 return s.memberlist.UpdateNode(s.config.BroadcastTimeout)
540 }
541
542 // Join joins an existing Serf cluster. Returns the number of nodes
543 // successfully contacted. The returned error will be non-nil only in the
544 // case that no nodes could be contacted. If ignoreOld is true, then any
545 // user messages sent prior to the join will be ignored.
546 func (s *Serf) Join(existing []string, ignoreOld bool) (int, error) {
547 // Do a quick state check
548 if s.State() != SerfAlive {
549 return 0, fmt.Errorf("Serf can't Join after Leave or Shutdown")
550 }
551
552 // Hold the joinLock, this is to make eventJoinIgnore safe
553 s.joinLock.Lock()
554 defer s.joinLock.Unlock()
555
556 // Ignore any events from a potential join. This is safe since we hold
557 // the joinLock and nobody else can be doing a Join
558 if ignoreOld {
559 s.eventJoinIgnore = true
560 defer func() {
561 s.eventJoinIgnore = false
562 }()
563 }
564
565 // Have memberlist attempt to join
566 num, err := s.memberlist.Join(existing)
567
568 // If we joined any nodes, broadcast the join message
569 if num > 0 {
570 // Start broadcasting the update
571 if err := s.broadcastJoin(s.clock.Time()); err != nil {
572 return num, err
573 }
574 }
575
576 return num, err
577 }
578
579 // broadcastJoin broadcasts a new join intent with a
580 // given clock value. It is used on either join, or if
581 // we need to refute an older leave intent. Cannot be called
582 // with the memberLock held.
583 func (s *Serf) broadcastJoin(ltime LamportTime) error {
584 // Construct message to update our lamport clock
585 msg := messageJoin{
586 LTime: ltime,
587 Node: s.config.NodeName,
588 }
589 s.clock.Witness(ltime)
590
591 // Process update locally
592 s.handleNodeJoinIntent(&msg)
593
594 // Start broadcasting the update
595 if err := s.broadcast(messageJoinType, &msg, nil); err != nil {
596 s.logger.Printf("[WARN] serf: Failed to broadcast join intent: %v", err)
597 return err
598 }
599 return nil
600 }
601
602 // Leave gracefully exits the cluster. It is safe to call this multiple
603 // times.
604 func (s *Serf) Leave() error {
605 // Check the current state
606 s.stateLock.Lock()
607 if s.state == SerfLeft {
608 s.stateLock.Unlock()
609 return nil
610 } else if s.state == SerfLeaving {
611 s.stateLock.Unlock()
612 return fmt.Errorf("Leave already in progress")
613 } else if s.state == SerfShutdown {
614 s.stateLock.Unlock()
615 return fmt.Errorf("Leave called after Shutdown")
616 }
617 s.state = SerfLeaving
618 s.stateLock.Unlock()
619
620 // If we have a snapshot, mark we are leaving
621 if s.snapshotter != nil {
622 s.snapshotter.Leave()
623 }
624
625 // Construct the message for the graceful leave
626 msg := messageLeave{
627 LTime: s.clock.Time(),
628 Node: s.config.NodeName,
629 }
630 s.clock.Increment()
631
632 // Process the leave locally
633 s.handleNodeLeaveIntent(&msg)
634
635 // Only broadcast the leave message if there is at least one
636 // other node alive.
637 if s.hasAliveMembers() {
638 notifyCh := make(chan struct{})
639 if err := s.broadcast(messageLeaveType, &msg, notifyCh); err != nil {
640 return err
641 }
642
643 select {
644 case <-notifyCh:
645 case <-time.After(s.config.BroadcastTimeout):
646 return errors.New("timeout while waiting for graceful leave")
647 }
648 }
649
650 // Attempt the memberlist leave
651 err := s.memberlist.Leave(s.config.BroadcastTimeout)
652 if err != nil {
653 return err
654 }
655
656 // Transition to Left only if we not already shutdown
657 s.stateLock.Lock()
658 if s.state != SerfShutdown {
659 s.state = SerfLeft
660 }
661 s.stateLock.Unlock()
662 return nil
663 }
664
665 // hasAliveMembers is called to check for any alive members other than
666 // ourself.
667 func (s *Serf) hasAliveMembers() bool {
668 s.memberLock.RLock()
669 defer s.memberLock.RUnlock()
670
671 hasAlive := false
672 for _, m := range s.members {
673 // Skip ourself, we want to know if OTHER members are alive
674 if m.Name == s.config.NodeName {
675 continue
676 }
677
678 if m.Status == StatusAlive {
679 hasAlive = true
680 break
681 }
682 }
683 return hasAlive
684 }
685
686 // LocalMember returns the Member information for the local node
687 func (s *Serf) LocalMember() Member {
688 s.memberLock.RLock()
689 defer s.memberLock.RUnlock()
690 return s.members[s.config.NodeName].Member
691 }
692
693 // Members returns a point-in-time snapshot of the members of this cluster.
694 func (s *Serf) Members() []Member {
695 s.memberLock.RLock()
696 defer s.memberLock.RUnlock()
697
698 members := make([]Member, 0, len(s.members))
699 for _, m := range s.members {
700 members = append(members, m.Member)
701 }
702
703 return members
704 }
705
706 // RemoveFailedNode forcibly removes a failed node from the cluster
707 // immediately, instead of waiting for the reaper to eventually reclaim it.
708 // This also has the effect that Serf will no longer attempt to reconnect
709 // to this node.
710 func (s *Serf) RemoveFailedNode(node string) error {
711 // Construct the message to broadcast
712 msg := messageLeave{
713 LTime: s.clock.Time(),
714 Node: node,
715 }
716 s.clock.Increment()
717
718 // Process our own event
719 s.handleNodeLeaveIntent(&msg)
720
721 // If we have no members, then we don't need to broadcast
722 if !s.hasAliveMembers() {
723 return nil
724 }
725
726 // Broadcast the remove
727 notifyCh := make(chan struct{})
728 if err := s.broadcast(messageLeaveType, &msg, notifyCh); err != nil {
729 return err
730 }
731
732 // Wait for the broadcast
733 select {
734 case <-notifyCh:
735 case <-time.After(s.config.BroadcastTimeout):
736 return fmt.Errorf("timed out broadcasting node removal")
737 }
738
739 return nil
740 }
741
742 // Shutdown forcefully shuts down the Serf instance, stopping all network
743 // activity and background maintenance associated with the instance.
744 //
745 // This is not a graceful shutdown, and should be preceded by a call
746 // to Leave. Otherwise, other nodes in the cluster will detect this node's
747 // exit as a node failure.
748 //
749 // It is safe to call this method multiple times.
750 func (s *Serf) Shutdown() error {
751 s.stateLock.Lock()
752 defer s.stateLock.Unlock()
753
754 if s.state == SerfShutdown {
755 return nil
756 }
757
758 if s.state != SerfLeft {
759 s.logger.Printf("[WARN] serf: Shutdown without a Leave")
760 }
761
762 s.state = SerfShutdown
763 close(s.shutdownCh)
764
765 err := s.memberlist.Shutdown()
766 if err != nil {
767 return err
768 }
769
770 // Wait for the snapshoter to finish if we have one
771 if s.snapshotter != nil {
772 s.snapshotter.Wait()
773 }
774
775 return nil
776 }
777
778 // ShutdownCh returns a channel that can be used to wait for
779 // Serf to shutdown.
780 func (s *Serf) ShutdownCh() <-chan struct{} {
781 return s.shutdownCh
782 }
783
784 // Memberlist is used to get access to the underlying Memberlist instance
785 func (s *Serf) Memberlist() *memberlist.Memberlist {
786 return s.memberlist
787 }
788
789 // State is the current state of this Serf instance.
790 func (s *Serf) State() SerfState {
791 s.stateLock.Lock()
792 defer s.stateLock.Unlock()
793 return s.state
794 }
795
796 // broadcast takes a Serf message type, encodes it for the wire, and queues
797 // the broadcast. If a notify channel is given, this channel will be closed
798 // when the broadcast is sent.
799 func (s *Serf) broadcast(t messageType, msg interface{}, notify chan<- struct{}) error {
800 raw, err := encodeMessage(t, msg)
801 if err != nil {
802 return err
803 }
804
805 s.broadcasts.QueueBroadcast(&broadcast{
806 msg: raw,
807 notify: notify,
808 })
809 return nil
810 }
811
812 // handleNodeJoin is called when a node join event is received
813 // from memberlist.
814 func (s *Serf) handleNodeJoin(n *memberlist.Node) {
815 s.memberLock.Lock()
816 defer s.memberLock.Unlock()
817
818 var oldStatus MemberStatus
819 member, ok := s.members[n.Name]
820 if !ok {
821 oldStatus = StatusNone
822 member = &memberState{
823 Member: Member{
824 Name: n.Name,
825 Addr: net.IP(n.Addr),
826 Port: n.Port,
827 Tags: s.decodeTags(n.Meta),
828 Status: StatusAlive,
829 },
830 }
831
832 // Check if we have a join intent and use the LTime
833 if join := recentIntent(s.recentJoin, n.Name); join != nil {
834 member.statusLTime = join.LTime
835 }
836
837 // Check if we have a leave intent
838 if leave := recentIntent(s.recentLeave, n.Name); leave != nil {
839 if leave.LTime > member.statusLTime {
840 member.Status = StatusLeaving
841 member.statusLTime = leave.LTime
842 }
843 }
844
845 s.members[n.Name] = member
846 } else {
847 oldStatus = member.Status
848 member.Status = StatusAlive
849 member.leaveTime = time.Time{}
850 member.Addr = net.IP(n.Addr)
851 member.Port = n.Port
852 member.Tags = s.decodeTags(n.Meta)
853 }
854
855 // Update the protocol versions every time we get an event
856 member.ProtocolMin = n.PMin
857 member.ProtocolMax = n.PMax
858 member.ProtocolCur = n.PCur
859 member.DelegateMin = n.DMin
860 member.DelegateMax = n.DMax
861 member.DelegateCur = n.DCur
862
863 // If node was previously in a failed state, then clean up some
864 // internal accounting.
865 // TODO(mitchellh): needs tests to verify not reaped
866 if oldStatus == StatusFailed || oldStatus == StatusLeft {
867 s.failedMembers = removeOldMember(s.failedMembers, member.Name)
868 s.leftMembers = removeOldMember(s.leftMembers, member.Name)
869 }
870
871 // Update some metrics
872 metrics.IncrCounter([]string{"serf", "member", "join"}, 1)
873
874 // Send an event along
875 s.logger.Printf("[INFO] serf: EventMemberJoin: %s %s",
876 member.Member.Name, member.Member.Addr)
877 if s.config.EventCh != nil {
878 s.config.EventCh <- MemberEvent{
879 Type: EventMemberJoin,
880 Members: []Member{member.Member},
881 }
882 }
883 }
884
885 // handleNodeLeave is called when a node leave event is received
886 // from memberlist.
887 func (s *Serf) handleNodeLeave(n *memberlist.Node) {
888 s.memberLock.Lock()
889 defer s.memberLock.Unlock()
890
891 member, ok := s.members[n.Name]
892 if !ok {
893 // We've never even heard of this node that is supposedly
894 // leaving. Just ignore it completely.
895 return
896 }
897
898 switch member.Status {
899 case StatusLeaving:
900 member.Status = StatusLeft
901 member.leaveTime = time.Now()
902 s.leftMembers = append(s.leftMembers, member)
903 case StatusAlive:
904 member.Status = StatusFailed
905 member.leaveTime = time.Now()
906 s.failedMembers = append(s.failedMembers, member)
907 default:
908 // Unknown state that it was in? Just don't do anything
909 s.logger.Printf("[WARN] serf: Bad state when leave: %d", member.Status)
910 return
911 }
912
913 // Send an event along
914 event := EventMemberLeave
915 eventStr := "EventMemberLeave"
916 if member.Status != StatusLeft {
917 event = EventMemberFailed
918 eventStr = "EventMemberFailed"
919 }
920
921 // Update some metrics
922 metrics.IncrCounter([]string{"serf", "member", member.Status.String()}, 1)
923
924 s.logger.Printf("[INFO] serf: %s: %s %s",
925 eventStr, member.Member.Name, member.Member.Addr)
926 if s.config.EventCh != nil {
927 s.config.EventCh <- MemberEvent{
928 Type: event,
929 Members: []Member{member.Member},
930 }
931 }
932 }
933
934 // handleNodeUpdate is called when a node meta data update
935 // has taken place
936 func (s *Serf) handleNodeUpdate(n *memberlist.Node) {
937 s.memberLock.Lock()
938 defer s.memberLock.Unlock()
939
940 member, ok := s.members[n.Name]
941 if !ok {
942 // We've never even heard of this node that is updating.
943 // Just ignore it completely.
944 return
945 }
946
947 // Update the member attributes
948 member.Addr = net.IP(n.Addr)
949 member.Port = n.Port
950 member.Tags = s.decodeTags(n.Meta)
951
952 // Update some metrics
953 metrics.IncrCounter([]string{"serf", "member", "update"}, 1)
954
955 // Send an event along
956 s.logger.Printf("[INFO] serf: EventMemberUpdate: %s", member.Member.Name)
957 if s.config.EventCh != nil {
958 s.config.EventCh <- MemberEvent{
959 Type: EventMemberUpdate,
960 Members: []Member{member.Member},
961 }
962 }
963 }
964
965 // handleNodeLeaveIntent is called when an intent to leave is received.
966 func (s *Serf) handleNodeLeaveIntent(leaveMsg *messageLeave) bool {
967 // Witness a potentially newer time
968 s.clock.Witness(leaveMsg.LTime)
969
970 s.memberLock.Lock()
971 defer s.memberLock.Unlock()
972
973 member, ok := s.members[leaveMsg.Node]
974 if !ok {
975 // If we've already seen this message don't rebroadcast
976 if recentIntent(s.recentLeave, leaveMsg.Node) != nil {
977 return false
978 }
979
980 // We don't know this member so store it in a buffer for now
981 s.recentLeave[s.recentLeaveIndex] = nodeIntent{
982 LTime: leaveMsg.LTime,
983 Node: leaveMsg.Node,
984 }
985 s.recentLeaveIndex = (s.recentLeaveIndex + 1) % len(s.recentLeave)
986 return true
987 }
988
989 // If the message is old, then it is irrelevant and we can skip it
990 if leaveMsg.LTime <= member.statusLTime {
991 return false
992 }
993
994 // Refute us leaving if we are in the alive state
995 // Must be done in another goroutine since we have the memberLock
996 if leaveMsg.Node == s.config.NodeName && s.state == SerfAlive {
997 s.logger.Printf("[DEBUG] serf: Refuting an older leave intent")
998 go s.broadcastJoin(s.clock.Time())
999 return false
1000 }
1001
1002 // State transition depends on current state
1003 switch member.Status {
1004 case StatusAlive:
1005 member.Status = StatusLeaving
1006 member.statusLTime = leaveMsg.LTime
1007 return true
1008 case StatusFailed:
1009 member.Status = StatusLeft
1010 member.statusLTime = leaveMsg.LTime
1011
1012 // Remove from the failed list and add to the left list. We add
1013 // to the left list so that when we do a sync, other nodes will
1014 // remove it from their failed list.
1015 s.failedMembers = removeOldMember(s.failedMembers, member.Name)
1016 s.leftMembers = append(s.leftMembers, member)
1017
1018 return true
1019 default:
1020 return false
1021 }
1022 }
1023
1024 // handleNodeJoinIntent is called when a node broadcasts a
1025 // join message to set the lamport time of its join
1026 func (s *Serf) handleNodeJoinIntent(joinMsg *messageJoin) bool {
1027 // Witness a potentially newer time
1028 s.clock.Witness(joinMsg.LTime)
1029
1030 s.memberLock.Lock()
1031 defer s.memberLock.Unlock()
1032
1033 member, ok := s.members[joinMsg.Node]
1034 if !ok {
1035 // If we've already seen this message don't rebroadcast
1036 if recentIntent(s.recentJoin, joinMsg.Node) != nil {
1037 return false
1038 }
1039
1040 // We don't know this member so store it in a buffer for now
1041 s.recentJoin[s.recentJoinIndex] = nodeIntent{LTime: joinMsg.LTime, Node: joinMsg.Node}
1042 s.recentJoinIndex = (s.recentJoinIndex + 1) % len(s.recentJoin)
1043 return true
1044 }
1045
1046 // Check if this time is newer than what we have
1047 if joinMsg.LTime <= member.statusLTime {
1048 return false
1049 }
1050
1051 // Update the LTime
1052 member.statusLTime = joinMsg.LTime
1053
1054 // If we are in the leaving state, we should go back to alive,
1055 // since the leaving message must have been for an older time
1056 if member.Status == StatusLeaving {
1057 member.Status = StatusAlive
1058 }
1059 return true
1060 }
1061
1062 // handleUserEvent is called when a user event broadcast is
1063 // received. Returns if the message should be rebroadcast.
1064 func (s *Serf) handleUserEvent(eventMsg *messageUserEvent) bool {
1065 // Witness a potentially newer time
1066 s.eventClock.Witness(eventMsg.LTime)
1067
1068 s.eventLock.Lock()
1069 defer s.eventLock.Unlock()
1070
1071 // Ignore if it is before our minimum event time
1072 if eventMsg.LTime < s.eventMinTime {
1073 return false
1074 }
1075
1076 // Check if this message is too old
1077 curTime := s.eventClock.Time()
1078 if curTime > LamportTime(len(s.eventBuffer)) &&
1079 eventMsg.LTime < curTime-LamportTime(len(s.eventBuffer)) {
1080 s.logger.Printf(
1081 "[WARN] serf: received old event %s from time %d (current: %d)",
1082 eventMsg.Name,
1083 eventMsg.LTime,
1084 s.eventClock.Time())
1085 return false
1086 }
1087
1088 // Check if we've already seen this
1089 idx := eventMsg.LTime % LamportTime(len(s.eventBuffer))
1090 seen := s.eventBuffer[idx]
1091 userEvent := userEvent{Name: eventMsg.Name, Payload: eventMsg.Payload}
1092 if seen != nil && seen.LTime == eventMsg.LTime {
1093 for _, previous := range seen.Events {
1094 if previous.Equals(&userEvent) {
1095 return false
1096 }
1097 }
1098 } else {
1099 seen = &userEvents{LTime: eventMsg.LTime}
1100 s.eventBuffer[idx] = seen
1101 }
1102
1103 // Add to recent events
1104 seen.Events = append(seen.Events, userEvent)
1105
1106 // Update some metrics
1107 metrics.IncrCounter([]string{"serf", "events"}, 1)
1108 metrics.IncrCounter([]string{"serf", "events", eventMsg.Name}, 1)
1109
1110 if s.config.EventCh != nil {
1111 s.config.EventCh <- UserEvent{
1112 LTime: eventMsg.LTime,
1113 Name: eventMsg.Name,
1114 Payload: eventMsg.Payload,
1115 Coalesce: eventMsg.CC,
1116 }
1117 }
1118 return true
1119 }
1120
1121 // handleQuery is called when a query broadcast is
1122 // received. Returns if the message should be rebroadcast.
1123 func (s *Serf) handleQuery(query *messageQuery) bool {
1124 // Witness a potentially newer time
1125 s.queryClock.Witness(query.LTime)
1126
1127 s.queryLock.Lock()
1128 defer s.queryLock.Unlock()
1129
1130 // Ignore if it is before our minimum query time
1131 if query.LTime < s.queryMinTime {
1132 return false
1133 }
1134
1135 // Check if this message is too old
1136 curTime := s.queryClock.Time()
1137 if curTime > LamportTime(len(s.queryBuffer)) &&
1138 query.LTime < curTime-LamportTime(len(s.queryBuffer)) {
1139 s.logger.Printf(
1140 "[WARN] serf: received old query %s from time %d (current: %d)",
1141 query.Name,
1142 query.LTime,
1143 s.queryClock.Time())
1144 return false
1145 }
1146
1147 // Check if we've already seen this
1148 idx := query.LTime % LamportTime(len(s.queryBuffer))
1149 seen := s.queryBuffer[idx]
1150 if seen != nil && seen.LTime == query.LTime {
1151 for _, previous := range seen.QueryIDs {
1152 if previous == query.ID {
1153 // Seen this ID already
1154 return false
1155 }
1156 }
1157 } else {
1158 seen = &queries{LTime: query.LTime}
1159 s.queryBuffer[idx] = seen
1160 }
1161
1162 // Add to recent queries
1163 seen.QueryIDs = append(seen.QueryIDs, query.ID)
1164
1165 // Update some metrics
1166 metrics.IncrCounter([]string{"serf", "queries"}, 1)
1167 metrics.IncrCounter([]string{"serf", "queries", query.Name}, 1)
1168
1169 // Check if we should rebroadcast, this may be disabled by a flag
1170 rebroadcast := true
1171 if query.NoBroadcast() {
1172 rebroadcast = false
1173 }
1174
1175 // Filter the query
1176 if !s.shouldProcessQuery(query.Filters) {
1177 // Even if we don't process it further, we should rebroadcast,
1178 // since it is the first time we've seen this.
1179 return rebroadcast
1180 }
1181
1182 // Send ack if requested, without waiting for client to Respond()
1183 if query.Ack() {
1184 ack := messageQueryResponse{
1185 LTime: query.LTime,
1186 ID: query.ID,
1187 From: s.config.NodeName,
1188 Flags: queryFlagAck,
1189 }
1190 raw, err := encodeMessage(messageQueryResponseType, &ack)
1191 if err != nil {
1192 s.logger.Printf("[ERR] serf: failed to format ack: %v", err)
1193 } else {
1194 addr := net.UDPAddr{IP: query.Addr, Port: int(query.Port)}
1195 if err := s.memberlist.SendTo(&addr, raw); err != nil {
1196 s.logger.Printf("[ERR] serf: failed to send ack: %v", err)
1197 }
1198 }
1199 }
1200
1201 if s.config.EventCh != nil {
1202 s.config.EventCh <- &Query{
1203 LTime: query.LTime,
1204 Name: query.Name,
1205 Payload: query.Payload,
1206 serf: s,
1207 id: query.ID,
1208 addr: query.Addr,
1209 port: query.Port,
1210 deadline: time.Now().Add(query.Timeout),
1211 }
1212 }
1213 return rebroadcast
1214 }
1215
1216 // handleResponse is called when a query response is
1217 // received.
1218 func (s *Serf) handleQueryResponse(resp *messageQueryResponse) {
1219 // Look for a corresponding QueryResponse
1220 s.queryLock.RLock()
1221 query, ok := s.queryResponse[resp.LTime]
1222 s.queryLock.RUnlock()
1223 if !ok {
1224 s.logger.Printf("[WARN] serf: reply for non-running query (LTime: %d, ID: %d) From: %s",
1225 resp.LTime, resp.ID, resp.From)
1226 return
1227 }
1228
1229 // Verify the ID matches
1230 if query.id != resp.ID {
1231 s.logger.Printf("[WARN] serf: query reply ID mismatch (Local: %d, Response: %d)",
1232 query.id, resp.ID)
1233 return
1234 }
1235
1236 // Check if the query is closed
1237 if query.Finished() {
1238 return
1239 }
1240
1241 // Process each type of response
1242 if resp.Ack() {
1243 metrics.IncrCounter([]string{"serf", "query_acks"}, 1)
1244 select {
1245 case query.ackCh <- resp.From:
1246 default:
1247 s.logger.Printf("[WARN] serf: Failed to delivery query ack, dropping")
1248 }
1249 } else {
1250 metrics.IncrCounter([]string{"serf", "query_responses"}, 1)
1251 select {
1252 case query.respCh <- NodeResponse{From: resp.From, Payload: resp.Payload}:
1253 default:
1254 s.logger.Printf("[WARN] serf: Failed to delivery query response, dropping")
1255 }
1256 }
1257 }
1258
1259 // handleNodeConflict is invoked when a join detects a conflict over a name.
1260 // This means two different nodes (IP/Port) are claiming the same name. Memberlist
1261 // will reject the "new" node mapping, but we can still be notified
1262 func (s *Serf) handleNodeConflict(existing, other *memberlist.Node) {
1263 // Log a basic warning if the node is not us...
1264 if existing.Name != s.config.NodeName {
1265 s.logger.Printf("[WARN] serf: Name conflict for '%s' both %s:%d and %s:%d are claiming",
1266 existing.Name, existing.Addr, existing.Port, other.Addr, other.Port)
1267 return
1268 }
1269
1270 // The current node is conflicting! This is an error
1271 s.logger.Printf("[ERR] serf: Node name conflicts with another node at %s:%d. Names must be unique! (Resolution enabled: %v)",
1272 other.Addr, other.Port, s.config.EnableNameConflictResolution)
1273
1274 // If automatic resolution is enabled, kick off the resolution
1275 if s.config.EnableNameConflictResolution {
1276 go s.resolveNodeConflict()
1277 }
1278 }
1279
1280 // resolveNodeConflict is used to determine which node should remain during
1281 // a name conflict. This is done by running an internal query.
1282 func (s *Serf) resolveNodeConflict() {
1283 // Get the local node
1284 local := s.memberlist.LocalNode()
1285
1286 // Start a name resolution query
1287 qName := internalQueryName(conflictQuery)
1288 payload := []byte(s.config.NodeName)
1289 resp, err := s.Query(qName, payload, nil)
1290 if err != nil {
1291 s.logger.Printf("[ERR] serf: Failed to start name resolution query: %v", err)
1292 return
1293 }
1294
1295 // Counter to determine winner
1296 var responses, matching int
1297
1298 // Gather responses
1299 respCh := resp.ResponseCh()
1300 for r := range respCh {
1301 // Decode the response
1302 if len(r.Payload) < 1 || messageType(r.Payload[0]) != messageConflictResponseType {
1303 s.logger.Printf("[ERR] serf: Invalid conflict query response type: %v", r.Payload)
1304 continue
1305 }
1306 var member Member
1307 if err := decodeMessage(r.Payload[1:], &member); err != nil {
1308 s.logger.Printf("[ERR] serf: Failed to decode conflict query response: %v", err)
1309 continue
1310 }
1311
1312 // Update the counters
1313 responses++
1314 if bytes.Equal(member.Addr, local.Addr) && member.Port == local.Port {
1315 matching++
1316 }
1317 }
1318
1319 // Query over, determine if we should live
1320 majority := (responses / 2) + 1
1321 if matching >= majority {
1322 s.logger.Printf("[INFO] serf: majority in name conflict resolution [%d / %d]",
1323 matching, responses)
1324 return
1325 }
1326
1327 // Since we lost the vote, we need to exit
1328 s.logger.Printf("[WARN] serf: minority in name conflict resolution, quiting [%d / %d]",
1329 matching, responses)
1330 if err := s.Shutdown(); err != nil {
1331 s.logger.Printf("[ERR] serf: Failed to shutdown: %v", err)
1332 }
1333 }
1334
1335 // handleReap periodically reaps the list of failed and left members.
1336 func (s *Serf) handleReap() {
1337 for {
1338 select {
1339 case <-time.After(s.config.ReapInterval):
1340 s.memberLock.Lock()
1341 s.failedMembers = s.reap(s.failedMembers, s.config.ReconnectTimeout)
1342 s.leftMembers = s.reap(s.leftMembers, s.config.TombstoneTimeout)
1343 s.memberLock.Unlock()
1344 case <-s.shutdownCh:
1345 return
1346 }
1347 }
1348 }
1349
1350 // handleReconnect attempts to reconnect to recently failed nodes
1351 // on configured intervals.
1352 func (s *Serf) handleReconnect() {
1353 for {
1354 select {
1355 case <-time.After(s.config.ReconnectInterval):
1356 s.reconnect()
1357 case <-s.shutdownCh:
1358 return
1359 }
1360 }
1361 }
1362
1363 // reap is called with a list of old members and a timeout, and removes
1364 // members that have exceeded the timeout. The members are removed from
1365 // both the old list and the members itself. Locking is left to the caller.
1366 func (s *Serf) reap(old []*memberState, timeout time.Duration) []*memberState {
1367 now := time.Now()
1368 n := len(old)
1369 for i := 0; i < n; i++ {
1370 m := old[i]
1371
1372 // Skip if the timeout is not yet reached
1373 if now.Sub(m.leaveTime) <= timeout {
1374 continue
1375 }
1376
1377 // Delete from the list
1378 old[i], old[n-1] = old[n-1], nil
1379 old = old[:n-1]
1380 n--
1381 i--
1382
1383 // Delete from members
1384 delete(s.members, m.Name)
1385
1386 // Send an event along
1387 s.logger.Printf("[INFO] serf: EventMemberReap: %s", m.Name)
1388 if s.config.EventCh != nil {
1389 s.config.EventCh <- MemberEvent{
1390 Type: EventMemberReap,
1391 Members: []Member{m.Member},
1392 }
1393 }
1394 }
1395
1396 return old
1397 }
1398
1399 // reconnect attempts to reconnect to recently fail nodes.
1400 func (s *Serf) reconnect() {
1401 s.memberLock.RLock()
1402
1403 // Nothing to do if there are no failed members
1404 n := len(s.failedMembers)
1405 if n == 0 {
1406 s.memberLock.RUnlock()
1407 return
1408 }
1409
1410 // Probability we should attempt to reconect is given
1411 // by num failed / (num members - num failed - num left)
1412 // This means that we probabilistically expect the cluster
1413 // to attempt to connect to each failed member once per
1414 // reconnect interval
1415 numFailed := float32(len(s.failedMembers))
1416 numAlive := float32(len(s.members) - len(s.failedMembers) - len(s.leftMembers))
1417 if numAlive == 0 {
1418 numAlive = 1 // guard against zero divide
1419 }
1420 prob := numFailed / numAlive
1421 if rand.Float32() > prob {
1422 s.memberLock.RUnlock()
1423 s.logger.Printf("[DEBUG] serf: forgoing reconnect for random throttling")
1424 return
1425 }
1426
1427 // Select a random member to try and join
1428 idx := int(rand.Uint32() % uint32(n))
1429 mem := s.failedMembers[idx]
1430 s.memberLock.RUnlock()
1431
1432 // Format the addr
1433 addr := net.UDPAddr{IP: mem.Addr, Port: int(mem.Port)}
1434 s.logger.Printf("[INFO] serf: attempting reconnect to %v %s", mem.Name, addr.String())
1435
1436 // Attempt to join at the memberlist level
1437 s.memberlist.Join([]string{addr.String()})
1438 }
1439
1440 // checkQueueDepth periodically checks the size of a queue to see if
1441 // it is too large
1442 func (s *Serf) checkQueueDepth(name string, queue *memberlist.TransmitLimitedQueue) {
1443 for {
1444 select {
1445 case <-time.After(time.Second):
1446 numq := queue.NumQueued()
1447 metrics.AddSample([]string{"serf", "queue", name}, float32(numq))
1448 if numq >= s.config.QueueDepthWarning {
1449 s.logger.Printf("[WARN] serf: %s queue depth: %d", name, numq)
1450 }
1451 if numq > s.config.MaxQueueDepth {
1452 s.logger.Printf("[WARN] serf: %s queue depth (%d) exceeds limit (%d), dropping messages!",
1453 name, numq, s.config.MaxQueueDepth)
1454 queue.Prune(s.config.MaxQueueDepth)
1455 }
1456 case <-s.shutdownCh:
1457 return
1458 }
1459 }
1460 }
1461
1462 // removeOldMember is used to remove an old member from a list of old
1463 // members.
1464 func removeOldMember(old []*memberState, name string) []*memberState {
1465 for i, m := range old {
1466 if m.Name == name {
1467 n := len(old)
1468 old[i], old[n-1] = old[n-1], nil
1469 return old[:n-1]
1470 }
1471 }
1472
1473 return old
1474 }
1475
1476 // recentIntent checks the recent intent buffer for a matching
1477 // entry for a given node, and either returns the message or nil
1478 func recentIntent(recent []nodeIntent, node string) (intent *nodeIntent) {
1479 for i := 0; i < len(recent); i++ {
1480 // Break fast if we hit a zero entry
1481 if recent[i].LTime == 0 {
1482 break
1483 }
1484
1485 // Check for a node match
1486 if recent[i].Node == node {
1487 // Take the most recent entry
1488 if intent == nil || recent[i].LTime > intent.LTime {
1489 intent = &recent[i]
1490 }
1491 }
1492 }
1493 return
1494 }
1495
1496 // handleRejoin attempts to reconnect to previously known alive nodes
1497 func (s *Serf) handleRejoin(previous []*PreviousNode) {
1498 for _, prev := range previous {
1499 // Do not attempt to join ourself
1500 if prev.Name == s.config.NodeName {
1501 continue
1502 }
1503
1504 s.logger.Printf("[INFO] serf: Attempting re-join to previously known node: %s", prev)
1505 _, err := s.memberlist.Join([]string{prev.Addr})
1506 if err == nil {
1507 s.logger.Printf("[INFO] serf: Re-joined to previously known node: %s", prev)
1508 return
1509 }
1510 }
1511 s.logger.Printf("[WARN] serf: Failed to re-join any previously known node")
1512 }
1513
1514 // encodeTags is used to encode a tag map
1515 func (s *Serf) encodeTags(tags map[string]string) []byte {
1516 // Support role-only backwards compatibility
1517 if s.ProtocolVersion() < 3 {
1518 role := tags["role"]
1519 return []byte(role)
1520 }
1521
1522 // Use a magic byte prefix and msgpack encode the tags
1523 var buf bytes.Buffer
1524 buf.WriteByte(tagMagicByte)
1525 enc := codec.NewEncoder(&buf, &codec.MsgpackHandle{})
1526 if err := enc.Encode(tags); err != nil {
1527 panic(fmt.Sprintf("Failed to encode tags: %v", err))
1528 }
1529 return buf.Bytes()
1530 }
1531
1532 // decodeTags is used to decode a tag map
1533 func (s *Serf) decodeTags(buf []byte) map[string]string {
1534 tags := make(map[string]string)
1535
1536 // Backwards compatibility mode
1537 if len(buf) == 0 || buf[0] != tagMagicByte {
1538 tags["role"] = string(buf)
1539 return tags
1540 }
1541
1542 // Decode the tags
1543 r := bytes.NewReader(buf[1:])
1544 dec := codec.NewDecoder(r, &codec.MsgpackHandle{})
1545 if err := dec.Decode(&tags); err != nil {
1546 s.logger.Printf("[ERR] serf: Failed to decode tags: %v", err)
1547 }
1548 return tags
1549 }
1550
1551 // Stats is used to provide operator debugging information
1552 func (s *Serf) Stats() map[string]string {
1553 toString := func(v uint64) string {
1554 return strconv.FormatUint(v, 10)
1555 }
1556 stats := map[string]string{
1557 "members": toString(uint64(len(s.members))),
1558 "failed": toString(uint64(len(s.failedMembers))),
1559 "left": toString(uint64(len(s.leftMembers))),
1560 "member_time": toString(uint64(s.clock.Time())),
1561 "event_time": toString(uint64(s.eventClock.Time())),
1562 "query_time": toString(uint64(s.queryClock.Time())),
1563 "intent_queue": toString(uint64(s.broadcasts.NumQueued())),
1564 "event_queue": toString(uint64(s.eventBroadcasts.NumQueued())),
1565 "query_queue": toString(uint64(s.queryBroadcasts.NumQueued())),
1566 "encrypted": fmt.Sprintf("%v", s.EncryptionEnabled()),
1567 }
1568 return stats
1569 }
1570
1571 // WriteKeyringFile will serialize the current keyring and save it to a file.
1572 func (s *Serf) writeKeyringFile() error {
1573 if len(s.config.KeyringFile) == 0 {
1574 return nil
1575 }
1576
1577 keyring := s.config.MemberlistConfig.Keyring
1578 keysRaw := keyring.GetKeys()
1579 keysEncoded := make([]string, len(keysRaw))
1580
1581 for i, key := range keysRaw {
1582 keysEncoded[i] = base64.StdEncoding.EncodeToString(key)
1583 }
1584
1585 encodedKeys, err := json.MarshalIndent(keysEncoded, "", " ")
1586 if err != nil {
1587 return fmt.Errorf("Failed to encode keys: %s", err)
1588 }
1589
1590 // Use 0600 for permissions because key data is sensitive
1591 if err = ioutil.WriteFile(s.config.KeyringFile, encodedKeys, 0600); err != nil {
1592 return fmt.Errorf("Failed to write keyring file: %s", err)
1593 }
1594
1595 // Success!
1596 return nil
1597 }
0 package serf
1
2 import (
3 "github.com/hashicorp/memberlist"
4 "github.com/hashicorp/serf/testutil"
5 "testing"
6 )
7
8 func TestSerf_joinLeave_ltime(t *testing.T) {
9 s1Config := testConfig()
10 s2Config := testConfig()
11
12 s1, err := Create(s1Config)
13 if err != nil {
14 t.Fatalf("err: %s", err)
15 }
16 defer s1.Shutdown()
17
18 s2, err := Create(s2Config)
19 if err != nil {
20 t.Fatalf("err: %s", err)
21 }
22 defer s2.Shutdown()
23
24 testutil.Yield()
25
26 _, err = s1.Join([]string{s2Config.MemberlistConfig.BindAddr}, false)
27 if err != nil {
28 t.Fatalf("err: %s", err)
29 }
30
31 testutil.Yield()
32
33 if s2.members[s1.config.NodeName].statusLTime != 1 {
34 t.Fatalf("join time is not valid %d",
35 s2.members[s1.config.NodeName].statusLTime)
36 }
37
38 if s2.clock.Time() <= s2.members[s1.config.NodeName].statusLTime {
39 t.Fatalf("join should increment")
40 }
41 oldClock := s2.clock.Time()
42
43 err = s1.Leave()
44 if err != nil {
45 t.Fatalf("err: %s", err)
46 }
47
48 testutil.Yield()
49
50 // s1 clock should exceed s2 due to leave
51 if s2.clock.Time() <= oldClock {
52 t.Fatalf("leave should increment (%d / %d)",
53 s2.clock.Time(), oldClock)
54 }
55 }
56
57 func TestSerf_join_pendingIntent(t *testing.T) {
58 c := testConfig()
59 s, err := Create(c)
60 if err != nil {
61 t.Fatalf("err: %s", err)
62 }
63 defer s.Shutdown()
64
65 s.recentJoin[0] = nodeIntent{5, "test"}
66
67 n := memberlist.Node{Name: "test",
68 Addr: nil,
69 Meta: []byte("test"),
70 }
71
72 s.handleNodeJoin(&n)
73
74 mem := s.members["test"]
75 if mem.statusLTime != 5 {
76 t.Fatalf("bad join time")
77 }
78 if mem.Status != StatusAlive {
79 t.Fatalf("bad status")
80 }
81 }
82
83 func TestSerf_join_pendingIntents(t *testing.T) {
84 c := testConfig()
85 s, err := Create(c)
86 if err != nil {
87 t.Fatalf("err: %s", err)
88 }
89 defer s.Shutdown()
90
91 s.recentJoin[0] = nodeIntent{5, "test"}
92 s.recentLeave[0] = nodeIntent{6, "test"}
93
94 n := memberlist.Node{Name: "test",
95 Addr: nil,
96 Meta: []byte("test"),
97 }
98
99 s.handleNodeJoin(&n)
100
101 mem := s.members["test"]
102 if mem.statusLTime != 6 {
103 t.Fatalf("bad join time")
104 }
105 if mem.Status != StatusLeaving {
106 t.Fatalf("bad status")
107 }
108 }
109
110 func TestSerf_leaveIntent_bufferEarly(t *testing.T) {
111 c := testConfig()
112 s, err := Create(c)
113 if err != nil {
114 t.Fatalf("err: %s", err)
115 }
116 defer s.Shutdown()
117
118 // Deliver a leave intent message early
119 j := messageLeave{LTime: 10, Node: "test"}
120 if !s.handleNodeLeaveIntent(&j) {
121 t.Fatalf("should rebroadcast")
122 }
123 if s.handleNodeLeaveIntent(&j) {
124 t.Fatalf("should not rebroadcast")
125 }
126
127 // Check that we buffered
128 if s.recentLeaveIndex != 1 {
129 t.Fatalf("bad index")
130 }
131 if s.recentLeave[0].Node != "test" || s.recentLeave[0].LTime != 10 {
132 t.Fatalf("bad buffer")
133 }
134 }
135
136 func TestSerf_leaveIntent_oldMessage(t *testing.T) {
137 c := testConfig()
138 s, err := Create(c)
139 if err != nil {
140 t.Fatalf("err: %s", err)
141 }
142 defer s.Shutdown()
143
144 s.members["test"] = &memberState{
145 Member: Member{
146 Status: StatusAlive,
147 },
148 statusLTime: 12,
149 }
150
151 j := messageLeave{LTime: 10, Node: "test"}
152 if s.handleNodeLeaveIntent(&j) {
153 t.Fatalf("should not rebroadcast")
154 }
155
156 if s.recentLeaveIndex != 0 {
157 t.Fatalf("bad index")
158 }
159 }
160
161 func TestSerf_leaveIntent_newer(t *testing.T) {
162 c := testConfig()
163 s, err := Create(c)
164 if err != nil {
165 t.Fatalf("err: %s", err)
166 }
167 defer s.Shutdown()
168
169 s.members["test"] = &memberState{
170 Member: Member{
171 Status: StatusAlive,
172 },
173 statusLTime: 12,
174 }
175
176 j := messageLeave{LTime: 14, Node: "test"}
177 if !s.handleNodeLeaveIntent(&j) {
178 t.Fatalf("should rebroadcast")
179 }
180
181 if s.recentLeaveIndex != 0 {
182 t.Fatalf("bad index")
183 }
184
185 if s.members["test"].Status != StatusLeaving {
186 t.Fatalf("should update status")
187 }
188
189 if s.clock.Time() != 15 {
190 t.Fatalf("should update clock")
191 }
192 }
193
194 func TestSerf_joinIntent_bufferEarly(t *testing.T) {
195 c := testConfig()
196 s, err := Create(c)
197 if err != nil {
198 t.Fatalf("err: %s", err)
199 }
200 defer s.Shutdown()
201
202 // Deliver a join intent message early
203 j := messageJoin{LTime: 10, Node: "test"}
204 if !s.handleNodeJoinIntent(&j) {
205 t.Fatalf("should rebroadcast")
206 }
207 if s.handleNodeJoinIntent(&j) {
208 t.Fatalf("should not rebroadcast")
209 }
210
211 // Check that we buffered
212 if s.recentJoinIndex != 1 {
213 t.Fatalf("bad index")
214 }
215 if s.recentJoin[0].Node != "test" || s.recentJoin[0].LTime != 10 {
216 t.Fatalf("bad buffer")
217 }
218 }
219
220 func TestSerf_joinIntent_oldMessage(t *testing.T) {
221 c := testConfig()
222 s, err := Create(c)
223 if err != nil {
224 t.Fatalf("err: %s", err)
225 }
226 defer s.Shutdown()
227
228 s.members["test"] = &memberState{
229 statusLTime: 12,
230 }
231
232 j := messageJoin{LTime: 10, Node: "test"}
233 if s.handleNodeJoinIntent(&j) {
234 t.Fatalf("should not rebroadcast")
235 }
236
237 if s.recentJoinIndex != 0 {
238 t.Fatalf("bad index")
239 }
240 }
241
242 func TestSerf_joinIntent_newer(t *testing.T) {
243 c := testConfig()
244 s, err := Create(c)
245 if err != nil {
246 t.Fatalf("err: %s", err)
247 }
248 defer s.Shutdown()
249
250 s.members["test"] = &memberState{
251 statusLTime: 12,
252 }
253
254 // Deliver a join intent message early
255 j := messageJoin{LTime: 14, Node: "test"}
256 if !s.handleNodeJoinIntent(&j) {
257 t.Fatalf("should rebroadcast")
258 }
259
260 if s.recentJoinIndex != 0 {
261 t.Fatalf("bad index")
262 }
263
264 if s.members["test"].statusLTime != 14 {
265 t.Fatalf("should update join time")
266 }
267
268 if s.clock.Time() != 15 {
269 t.Fatalf("should update clock")
270 }
271 }
272
273 func TestSerf_joinIntent_resetLeaving(t *testing.T) {
274 c := testConfig()
275 s, err := Create(c)
276 if err != nil {
277 t.Fatalf("err: %s", err)
278 }
279 defer s.Shutdown()
280
281 s.members["test"] = &memberState{
282 Member: Member{
283 Status: StatusLeaving,
284 },
285 statusLTime: 12,
286 }
287
288 j := messageJoin{LTime: 14, Node: "test"}
289 if !s.handleNodeJoinIntent(&j) {
290 t.Fatalf("should rebroadcast")
291 }
292
293 if s.recentJoinIndex != 0 {
294 t.Fatalf("bad index")
295 }
296
297 if s.members["test"].statusLTime != 14 {
298 t.Fatalf("should update join time")
299 }
300 if s.members["test"].Status != StatusAlive {
301 t.Fatalf("should update status")
302 }
303
304 if s.clock.Time() != 15 {
305 t.Fatalf("should update clock")
306 }
307 }
308
309 func TestSerf_userEvent_oldMessage(t *testing.T) {
310 c := testConfig()
311 s, err := Create(c)
312 if err != nil {
313 t.Fatalf("err: %s", err)
314 }
315 defer s.Shutdown()
316
317 // increase the ltime artificially
318 s.eventClock.Witness(LamportTime(c.EventBuffer + 1000))
319
320 msg := messageUserEvent{
321 LTime: 1,
322 Name: "old",
323 Payload: nil,
324 }
325 if s.handleUserEvent(&msg) {
326 t.Fatalf("should not rebroadcast")
327 }
328 }
329
330 func TestSerf_userEvent_sameClock(t *testing.T) {
331 eventCh := make(chan Event, 4)
332 c := testConfig()
333 c.EventCh = eventCh
334 s, err := Create(c)
335 if err != nil {
336 t.Fatalf("err: %s", err)
337 }
338 defer s.Shutdown()
339
340 msg := messageUserEvent{
341 LTime: 1,
342 Name: "first",
343 Payload: []byte("test"),
344 }
345 if !s.handleUserEvent(&msg) {
346 t.Fatalf("should rebroadcast")
347 }
348 msg = messageUserEvent{
349 LTime: 1,
350 Name: "first",
351 Payload: []byte("newpayload"),
352 }
353 if !s.handleUserEvent(&msg) {
354 t.Fatalf("should rebroadcast")
355 }
356 msg = messageUserEvent{
357 LTime: 1,
358 Name: "second",
359 Payload: []byte("other"),
360 }
361 if !s.handleUserEvent(&msg) {
362 t.Fatalf("should rebroadcast")
363 }
364
365 testUserEvents(t, eventCh,
366 []string{"first", "first", "second"},
367 [][]byte{[]byte("test"), []byte("newpayload"), []byte("other")})
368 }
369
370 func TestSerf_query_oldMessage(t *testing.T) {
371 c := testConfig()
372 s, err := Create(c)
373 if err != nil {
374 t.Fatalf("err: %s", err)
375 }
376 defer s.Shutdown()
377
378 // increase the ltime artificially
379 s.queryClock.Witness(LamportTime(c.QueryBuffer + 1000))
380
381 msg := messageQuery{
382 LTime: 1,
383 Name: "old",
384 Payload: nil,
385 }
386 if s.handleQuery(&msg) {
387 t.Fatalf("should not rebroadcast")
388 }
389 }
390
391 func TestSerf_query_sameClock(t *testing.T) {
392 eventCh := make(chan Event, 4)
393 c := testConfig()
394 c.EventCh = eventCh
395 s, err := Create(c)
396 if err != nil {
397 t.Fatalf("err: %s", err)
398 }
399 defer s.Shutdown()
400
401 msg := messageQuery{
402 LTime: 1,
403 ID: 1,
404 Name: "foo",
405 Payload: []byte("test"),
406 }
407 if !s.handleQuery(&msg) {
408 t.Fatalf("should rebroadcast")
409 }
410 if s.handleQuery(&msg) {
411 t.Fatalf("should not rebroadcast")
412 }
413 msg = messageQuery{
414 LTime: 1,
415 ID: 2,
416 Name: "bar",
417 Payload: []byte("newpayload"),
418 }
419 if !s.handleQuery(&msg) {
420 t.Fatalf("should rebroadcast")
421 }
422 if s.handleQuery(&msg) {
423 t.Fatalf("should not rebroadcast")
424 }
425 msg = messageQuery{
426 LTime: 1,
427 ID: 3,
428 Name: "baz",
429 Payload: []byte("other"),
430 }
431 if !s.handleQuery(&msg) {
432 t.Fatalf("should rebroadcast")
433 }
434 if s.handleQuery(&msg) {
435 t.Fatalf("should not rebroadcast")
436 }
437
438 testQueryEvents(t, eventCh,
439 []string{"foo", "bar", "baz"},
440 [][]byte{[]byte("test"), []byte("newpayload"), []byte("other")})
441 }
0 package serf
1
2 import (
3 "encoding/base64"
4 "fmt"
5 "io/ioutil"
6 "log"
7 "os"
8 "path/filepath"
9 "reflect"
10 "strings"
11 "testing"
12 "time"
13
14 "github.com/hashicorp/memberlist"
15 "github.com/hashicorp/serf/testutil"
16 )
17
18 func testConfig() *Config {
19 config := DefaultConfig()
20 config.Init()
21 config.MemberlistConfig.BindAddr = testutil.GetBindAddr().String()
22
23 // Set probe intervals that are aggressive for finding bad nodes
24 config.MemberlistConfig.GossipInterval = 5 * time.Millisecond
25 config.MemberlistConfig.ProbeInterval = 50 * time.Millisecond
26 config.MemberlistConfig.ProbeTimeout = 25 * time.Millisecond
27 config.MemberlistConfig.SuspicionMult = 1
28
29 config.NodeName = fmt.Sprintf("Node %s", config.MemberlistConfig.BindAddr)
30
31 // Set a short reap interval so that it can run during the test
32 config.ReapInterval = 1 * time.Second
33
34 // Set a short reconnect interval so that it can run a lot during tests
35 config.ReconnectInterval = 100 * time.Millisecond
36
37 // Set basically zero on the reconnect/tombstone timeouts so that
38 // they're removed on the first ReapInterval.
39 config.ReconnectTimeout = 1 * time.Microsecond
40 config.TombstoneTimeout = 1 * time.Microsecond
41
42 return config
43 }
44
45 // testMember tests that a member in a list is in a given state.
46 func testMember(t *testing.T, members []Member, name string, status MemberStatus) {
47 for _, m := range members {
48 if m.Name == name {
49 if m.Status != status {
50 panic(fmt.Sprintf("bad state for %s: %d", name, m.Status))
51 }
52 return
53 }
54 }
55
56 if status == StatusNone {
57 // We didn't expect to find it
58 return
59 }
60
61 panic(fmt.Sprintf("node not found: %s", name))
62 }
63
64 func TestCreate_badProtocolVersion(t *testing.T) {
65 cases := []struct {
66 version uint8
67 err bool
68 }{
69 {ProtocolVersionMin, false},
70 {ProtocolVersionMax, false},
71 // TODO(mitchellh): uncommon when we're over 0
72 //{ProtocolVersionMin - 1, true},
73 {ProtocolVersionMax + 1, true},
74 {ProtocolVersionMax - 1, false},
75 }
76
77 for _, tc := range cases {
78 c := testConfig()
79 c.ProtocolVersion = tc.version
80 s, err := Create(c)
81 if tc.err && err == nil {
82 t.Errorf("Should've failed with version: %d", tc.version)
83 } else if !tc.err && err != nil {
84 t.Errorf("Version '%d' error: %s", tc.version, err)
85 }
86
87 if err == nil {
88 s.Shutdown()
89 }
90 }
91 }
92
93 func TestSerf_eventsFailed(t *testing.T) {
94 // Create the s1 config with an event channel so we can listen
95 eventCh := make(chan Event, 4)
96 s1Config := testConfig()
97 s1Config.EventCh = eventCh
98
99 s2Config := testConfig()
100
101 s1, err := Create(s1Config)
102 if err != nil {
103 t.Fatalf("err: %s", err)
104 }
105
106 s2, err := Create(s2Config)
107 if err != nil {
108 t.Fatalf("err: %s", err)
109 }
110
111 defer s1.Shutdown()
112 defer s2.Shutdown()
113
114 testutil.Yield()
115
116 _, err = s1.Join([]string{s2Config.MemberlistConfig.BindAddr}, false)
117 if err != nil {
118 t.Fatalf("err: %s", err)
119 }
120
121 testutil.Yield()
122
123 if err := s2.Shutdown(); err != nil {
124 t.Fatalf("err: %s", err)
125 }
126
127 time.Sleep(1 * time.Second)
128
129 // Since s2 shutdown, we check the events to make sure we got failures.
130 testEvents(t, eventCh, s2Config.NodeName,
131 []EventType{EventMemberJoin, EventMemberFailed, EventMemberReap})
132 }
133
134 func TestSerf_eventsJoin(t *testing.T) {
135 // Create the s1 config with an event channel so we can listen
136 eventCh := make(chan Event, 4)
137 s1Config := testConfig()
138 s1Config.EventCh = eventCh
139
140 s2Config := testConfig()
141
142 s1, err := Create(s1Config)
143 if err != nil {
144 t.Fatalf("err: %s", err)
145 }
146
147 s2, err := Create(s2Config)
148 if err != nil {
149 t.Fatalf("err: %s", err)
150 }
151
152 defer s1.Shutdown()
153 defer s2.Shutdown()
154
155 testutil.Yield()
156
157 _, err = s1.Join([]string{s2Config.MemberlistConfig.BindAddr}, false)
158 if err != nil {
159 t.Fatalf("err: %s", err)
160 }
161
162 testutil.Yield()
163
164 testEvents(t, eventCh, s2Config.NodeName,
165 []EventType{EventMemberJoin})
166 }
167
168 func TestSerf_eventsLeave(t *testing.T) {
169 // Create the s1 config with an event channel so we can listen
170 eventCh := make(chan Event, 4)
171 s1Config := testConfig()
172 s1Config.EventCh = eventCh
173
174 s2Config := testConfig()
175
176 s1, err := Create(s1Config)
177 if err != nil {
178 t.Fatalf("err: %s", err)
179 }
180
181 s2, err := Create(s2Config)
182 if err != nil {
183 t.Fatalf("err: %s", err)
184 }
185
186 defer s1.Shutdown()
187 defer s2.Shutdown()
188
189 testutil.Yield()
190
191 _, err = s1.Join([]string{s2Config.MemberlistConfig.BindAddr}, false)
192 if err != nil {
193 t.Fatalf("err: %s", err)
194 }
195
196 testutil.Yield()
197
198 if err := s2.Leave(); err != nil {
199 t.Fatalf("err: %s", err)
200 }
201
202 testutil.Yield()
203
204 // Now that s2 has left, we check the events to make sure we got
205 // a leave event in s1 about the leave.
206 testEvents(t, eventCh, s2Config.NodeName,
207 []EventType{EventMemberJoin, EventMemberLeave})
208 }
209
210 func TestSerf_eventsUser(t *testing.T) {
211 // Create the s1 config with an event channel so we can listen
212 eventCh := make(chan Event, 4)
213 s1Config := testConfig()
214 s2Config := testConfig()
215 s2Config.EventCh = eventCh
216
217 s1, err := Create(s1Config)
218 if err != nil {
219 t.Fatalf("err: %s", err)
220 }
221
222 s2, err := Create(s2Config)
223 if err != nil {
224 t.Fatalf("err: %s", err)
225 }
226
227 defer s1.Shutdown()
228 defer s2.Shutdown()
229
230 testutil.Yield()
231
232 _, err = s1.Join([]string{s2Config.MemberlistConfig.BindAddr}, false)
233 if err != nil {
234 t.Fatalf("err: %s", err)
235 }
236
237 testutil.Yield()
238
239 // Fire a user event
240 if err := s1.UserEvent("event!", []byte("test"), false); err != nil {
241 t.Fatalf("err: %s", err)
242 }
243
244 testutil.Yield()
245
246 // Fire a user event
247 if err := s1.UserEvent("second", []byte("foobar"), false); err != nil {
248 t.Fatalf("err: %s", err)
249 }
250
251 testutil.Yield()
252
253 // check the events to make sure we got
254 // a leave event in s1 about the leave.
255 testUserEvents(t, eventCh,
256 []string{"event!", "second"},
257 [][]byte{[]byte("test"), []byte("foobar")})
258 }
259
260 func TestSerf_eventsUser_sizeLimit(t *testing.T) {
261 // Create the s1 config with an event channel so we can listen
262 s1Config := testConfig()
263 s1, err := Create(s1Config)
264 if err != nil {
265 t.Fatalf("err: %s", err)
266 }
267 defer s1.Shutdown()
268
269 name := "this is too large an event"
270 payload := make([]byte, UserEventSizeLimit)
271 err = s1.UserEvent(name, payload, false)
272 if err == nil {
273 t.Fatalf("expect error")
274 }
275 if !strings.HasPrefix(err.Error(), "user event exceeds limit of ") {
276 t.Fatalf("should get size limit error")
277 }
278 }
279
280 func TestSerf_joinLeave(t *testing.T) {
281 s1Config := testConfig()
282 s2Config := testConfig()
283
284 s1, err := Create(s1Config)
285 if err != nil {
286 t.Fatalf("err: %s", err)
287 }
288
289 s2, err := Create(s2Config)
290 if err != nil {
291 t.Fatalf("err: %s", err)
292 }
293
294 defer s1.Shutdown()
295 defer s2.Shutdown()
296
297 testutil.Yield()
298
299 if len(s1.Members()) != 1 {
300 t.Fatalf("s1 members: %d", len(s1.Members()))
301 }
302
303 if len(s2.Members()) != 1 {
304 t.Fatalf("s2 members: %d", len(s2.Members()))
305 }
306
307 _, err = s1.Join([]string{s2Config.MemberlistConfig.BindAddr}, false)
308 if err != nil {
309 t.Fatalf("err: %s", err)
310 }
311
312 testutil.Yield()
313
314 if len(s1.Members()) != 2 {
315 t.Fatalf("s1 members: %d", len(s1.Members()))
316 }
317
318 if len(s2.Members()) != 2 {
319 t.Fatalf("s2 members: %d", len(s2.Members()))
320 }
321
322 err = s1.Leave()
323 if err != nil {
324 t.Fatalf("err: %s", err)
325 }
326
327 // Give the reaper time to reap nodes
328 time.Sleep(s1Config.ReapInterval * 2)
329
330 if len(s1.Members()) != 1 {
331 t.Fatalf("s1 members: %d", len(s1.Members()))
332 }
333
334 if len(s2.Members()) != 1 {
335 t.Fatalf("s2 members: %d", len(s2.Members()))
336 }
337 }
338
339 // Bug: GH-58
340 func TestSerf_leaveRejoinDifferentRole(t *testing.T) {
341 s1Config := testConfig()
342 s2Config := testConfig()
343 s2Config.Tags["role"] = "foo"
344
345 s1, err := Create(s1Config)
346 if err != nil {
347 t.Fatalf("err: %s", err)
348 }
349
350 s2, err := Create(s2Config)
351 if err != nil {
352 t.Fatalf("err: %s", err)
353 }
354
355 defer s1.Shutdown()
356 defer s2.Shutdown()
357
358 testutil.Yield()
359
360 _, err = s1.Join([]string{s2Config.MemberlistConfig.BindAddr}, false)
361 if err != nil {
362 t.Fatalf("err: %s", err)
363 }
364
365 testutil.Yield()
366
367 err = s2.Leave()
368 if err != nil {
369 t.Fatalf("err: %s", err)
370 }
371
372 if err := s2.Shutdown(); err != nil {
373 t.Fatalf("err: %s", err)
374 }
375
376 testutil.Yield()
377
378 // Make s3 look just like s2, but create a new node with a new role
379 s3Config := testConfig()
380 s3Config.MemberlistConfig.BindAddr = s2Config.MemberlistConfig.BindAddr
381 s3Config.NodeName = s2Config.NodeName
382 s3Config.Tags["role"] = "bar"
383
384 s3, err := Create(s3Config)
385 if err != nil {
386 t.Fatalf("err: %s", err)
387 }
388 defer s3.Shutdown()
389
390 _, err = s3.Join([]string{s1Config.MemberlistConfig.BindAddr}, false)
391 if err != nil {
392 t.Fatalf("err: %s", err)
393 }
394
395 testutil.Yield()
396
397 members := s1.Members()
398 if len(members) != 2 {
399 t.Fatalf("s1 members: %d", len(s1.Members()))
400 }
401
402 var member *Member = nil
403 for _, m := range members {
404 if m.Name == s3Config.NodeName {
405 member = &m
406 break
407 }
408 }
409
410 if member == nil {
411 t.Fatalf("couldn't find member")
412 }
413
414 if member.Tags["role"] != s3Config.Tags["role"] {
415 t.Fatalf("bad role: %s", member.Tags["role"])
416 }
417 }
418
419 func TestSerf_reconnect(t *testing.T) {
420 eventCh := make(chan Event, 64)
421 s1Config := testConfig()
422 s1Config.EventCh = eventCh
423
424 s2Config := testConfig()
425 s2Addr := s2Config.MemberlistConfig.BindAddr
426 s2Name := s2Config.NodeName
427
428 s1, err := Create(s1Config)
429 if err != nil {
430 t.Fatalf("err: %s", err)
431 }
432
433 s2, err := Create(s2Config)
434 if err != nil {
435 t.Fatalf("err: %s", err)
436 }
437
438 defer s1.Shutdown()
439 defer s2.Shutdown()
440
441 testutil.Yield()
442
443 _, err = s1.Join([]string{s2Config.MemberlistConfig.BindAddr}, false)
444 if err != nil {
445 t.Fatalf("err: %s", err)
446 }
447
448 testutil.Yield()
449
450 // Now force the shutdown of s2 so it appears to fail.
451 if err := s2.Shutdown(); err != nil {
452 t.Fatalf("err: %s", err)
453 }
454
455 time.Sleep(s2Config.MemberlistConfig.ProbeInterval * 5)
456
457 // Bring back s2 by mimicking its name and address
458 s2Config = testConfig()
459 s2Config.MemberlistConfig.BindAddr = s2Addr
460 s2Config.NodeName = s2Name
461 s2, err = Create(s2Config)
462 if err != nil {
463 t.Fatalf("err: %s", err)
464 }
465
466 time.Sleep(s1Config.ReconnectInterval * 5)
467
468 testEvents(t, eventCh, s2Name,
469 []EventType{EventMemberJoin, EventMemberFailed, EventMemberJoin})
470 }
471
472 func TestSerf_reconnect_sameIP(t *testing.T) {
473 eventCh := make(chan Event, 64)
474 s1Config := testConfig()
475 s1Config.EventCh = eventCh
476
477 s2Config := testConfig()
478 s2Config.MemberlistConfig.BindAddr = s1Config.MemberlistConfig.BindAddr
479 s2Config.MemberlistConfig.BindPort = s1Config.MemberlistConfig.BindPort + 1
480
481 s2Addr := fmt.Sprintf("%s:%d",
482 s2Config.MemberlistConfig.BindAddr,
483 s2Config.MemberlistConfig.BindPort)
484 s2Name := s2Config.NodeName
485
486 s1, err := Create(s1Config)
487 if err != nil {
488 t.Fatalf("err: %s", err)
489 }
490
491 s2, err := Create(s2Config)
492 if err != nil {
493 t.Fatalf("err: %s", err)
494 }
495
496 defer s1.Shutdown()
497 defer s2.Shutdown()
498
499 testutil.Yield()
500
501 _, err = s1.Join([]string{s2Addr}, false)
502 if err != nil {
503 t.Fatalf("err: %s", err)
504 }
505
506 testutil.Yield()
507
508 // Now force the shutdown of s2 so it appears to fail.
509 if err := s2.Shutdown(); err != nil {
510 t.Fatalf("err: %s", err)
511 }
512
513 time.Sleep(s2Config.MemberlistConfig.ProbeInterval * 5)
514
515 // Bring back s2 by mimicking its name and address
516 s2Config = testConfig()
517 s2Config.MemberlistConfig.BindAddr = s1Config.MemberlistConfig.BindAddr
518 s2Config.MemberlistConfig.BindPort = s1Config.MemberlistConfig.BindPort + 1
519 s2Config.NodeName = s2Name
520 s2, err = Create(s2Config)
521 if err != nil {
522 t.Fatalf("err: %s", err)
523 }
524
525 time.Sleep(s1Config.ReconnectInterval * 5)
526
527 testEvents(t, eventCh, s2Name,
528 []EventType{EventMemberJoin, EventMemberFailed, EventMemberJoin})
529 }
530
531 func TestSerf_role(t *testing.T) {
532 s1Config := testConfig()
533 s2Config := testConfig()
534
535 s1Config.Tags["role"] = "web"
536 s2Config.Tags["role"] = "lb"
537
538 s1, err := Create(s1Config)
539 if err != nil {
540 t.Fatalf("err: %s", err)
541 }
542
543 s2, err := Create(s2Config)
544 if err != nil {
545 t.Fatalf("err: %s", err)
546 }
547
548 defer s1.Shutdown()
549 defer s2.Shutdown()
550
551 _, err = s1.Join([]string{s2Config.MemberlistConfig.BindAddr}, false)
552 if err != nil {
553 t.Fatalf("err: %s", err)
554 }
555
556 testutil.Yield()
557
558 members := s1.Members()
559 if len(members) != 2 {
560 t.Fatalf("should have 2 members")
561 }
562
563 roles := make(map[string]string)
564 for _, m := range members {
565 roles[m.Name] = m.Tags["role"]
566 }
567
568 if roles[s1Config.NodeName] != "web" {
569 t.Fatalf("bad role for web: %s", roles[s1Config.NodeName])
570 }
571
572 if roles[s2Config.NodeName] != "lb" {
573 t.Fatalf("bad role for lb: %s", roles[s2Config.NodeName])
574 }
575 }
576
577 func TestSerfProtocolVersion(t *testing.T) {
578 config := testConfig()
579 config.ProtocolVersion = ProtocolVersionMax
580
581 s1, err := Create(config)
582 if err != nil {
583 t.Fatalf("err: %s", err)
584 }
585 defer s1.Shutdown()
586
587 actual := s1.ProtocolVersion()
588 if actual != ProtocolVersionMax {
589 t.Fatalf("bad: %#v", actual)
590 }
591 }
592
593 func TestSerfRemoveFailedNode(t *testing.T) {
594 s1Config := testConfig()
595 s2Config := testConfig()
596 s3Config := testConfig()
597
598 s1, err := Create(s1Config)
599 if err != nil {
600 t.Fatalf("err: %s", err)
601 }
602
603 s2, err := Create(s2Config)
604 if err != nil {
605 t.Fatalf("err: %s", err)
606 }
607
608 s3, err := Create(s3Config)
609 if err != nil {
610 t.Fatalf("err: %s", err)
611 }
612
613 defer s1.Shutdown()
614 defer s2.Shutdown()
615 defer s3.Shutdown()
616
617 _, err = s1.Join([]string{s2Config.MemberlistConfig.BindAddr}, false)
618 if err != nil {
619 t.Fatalf("err: %s", err)
620 }
621
622 _, err = s1.Join([]string{s3Config.MemberlistConfig.BindAddr}, false)
623 if err != nil {
624 t.Fatalf("err: %s", err)
625 }
626
627 testutil.Yield()
628
629 // Now force the shutdown of s2 so it appears to fail.
630 if err := s2.Shutdown(); err != nil {
631 t.Fatalf("err: %s", err)
632 }
633
634 time.Sleep(s2Config.MemberlistConfig.ProbeInterval * 5)
635
636 // Verify that s2 is "failed"
637 testMember(t, s1.Members(), s2Config.NodeName, StatusFailed)
638
639 // Now remove the failed node
640 if err := s1.RemoveFailedNode(s2Config.NodeName); err != nil {
641 t.Fatalf("err: %s", err)
642 }
643
644 // Verify that s2 is gone
645 testMember(t, s1.Members(), s2Config.NodeName, StatusLeft)
646 testMember(t, s3.Members(), s2Config.NodeName, StatusLeft)
647 }
648
649 func TestSerfRemoveFailedNode_ourself(t *testing.T) {
650 s1Config := testConfig()
651 s1, err := Create(s1Config)
652 if err != nil {
653 t.Fatalf("err: %s", err)
654 }
655 defer s1.Shutdown()
656
657 testutil.Yield()
658
659 if err := s1.RemoveFailedNode("somebody"); err != nil {
660 t.Fatalf("err: %s", err)
661 }
662 }
663
664 func TestSerfState(t *testing.T) {
665 s1, err := Create(testConfig())
666 if err != nil {
667 t.Fatalf("err: %s", err)
668 }
669 defer s1.Shutdown()
670
671 if s1.State() != SerfAlive {
672 t.Fatalf("bad state: %d", s1.State())
673 }
674
675 if err := s1.Leave(); err != nil {
676 t.Fatalf("err: %s", err)
677 }
678
679 if s1.State() != SerfLeft {
680 t.Fatalf("bad state: %d", s1.State())
681 }
682
683 if err := s1.Shutdown(); err != nil {
684 t.Fatalf("err: %s", err)
685 }
686
687 if s1.State() != SerfShutdown {
688 t.Fatalf("bad state: %d", s1.State())
689 }
690 }
691
692 func TestSerf_ReapHandler_Shutdown(t *testing.T) {
693 s, err := Create(testConfig())
694 if err != nil {
695 t.Fatalf("err: %s", err)
696 }
697
698 go func() {
699 s.Shutdown()
700 time.Sleep(time.Millisecond)
701 t.Fatalf("timeout")
702 }()
703 s.handleReap()
704 }
705
706 func TestSerf_ReapHandler(t *testing.T) {
707 c := testConfig()
708 c.ReapInterval = time.Nanosecond
709 c.TombstoneTimeout = time.Second * 6
710 s, err := Create(c)
711 if err != nil {
712 t.Fatalf("err: %s", err)
713 }
714 defer s.Shutdown()
715
716 m := Member{}
717 s.leftMembers = []*memberState{
718 &memberState{m, 0, time.Now()},
719 &memberState{m, 0, time.Now().Add(-5 * time.Second)},
720 &memberState{m, 0, time.Now().Add(-10 * time.Second)},
721 }
722
723 go func() {
724 time.Sleep(time.Millisecond)
725 s.Shutdown()
726 }()
727
728 s.handleReap()
729
730 if len(s.leftMembers) != 2 {
731 t.Fatalf("should be shorter")
732 }
733 }
734
735 func TestSerf_Reap(t *testing.T) {
736 s, err := Create(testConfig())
737 if err != nil {
738 t.Fatalf("err: %s", err)
739 }
740 defer s.Shutdown()
741
742 m := Member{}
743 old := []*memberState{
744 &memberState{m, 0, time.Now()},
745 &memberState{m, 0, time.Now().Add(-5 * time.Second)},
746 &memberState{m, 0, time.Now().Add(-10 * time.Second)},
747 }
748
749 old = s.reap(old, time.Second*6)
750 if len(old) != 2 {
751 t.Fatalf("should be shorter")
752 }
753 }
754
755 func TestRemoveOldMember(t *testing.T) {
756 old := []*memberState{
757 &memberState{Member: Member{Name: "foo"}},
758 &memberState{Member: Member{Name: "bar"}},
759 &memberState{Member: Member{Name: "baz"}},
760 }
761
762 old = removeOldMember(old, "bar")
763 if len(old) != 2 {
764 t.Fatalf("should be shorter")
765 }
766 if old[1].Name == "bar" {
767 t.Fatalf("should remove old member")
768 }
769 }
770
771 func TestRecentIntent(t *testing.T) {
772 if recentIntent(nil, "foo") != nil {
773 t.Fatalf("should get nil on empty recent")
774 }
775 if recentIntent([]nodeIntent{}, "foo") != nil {
776 t.Fatalf("should get nil on empty recent")
777 }
778
779 recent := []nodeIntent{
780 nodeIntent{1, "foo"},
781 nodeIntent{2, "bar"},
782 nodeIntent{3, "baz"},
783 nodeIntent{4, "bar"},
784 nodeIntent{0, "bar"},
785 nodeIntent{5, "bar"},
786 }
787
788 if r := recentIntent(recent, "bar"); r.LTime != 4 {
789 t.Fatalf("bad time for bar")
790 }
791
792 if r := recentIntent(recent, "tubez"); r != nil {
793 t.Fatalf("got result for tubez")
794 }
795 }
796
797 func TestMemberStatus_String(t *testing.T) {
798 status := []MemberStatus{StatusNone, StatusAlive, StatusLeaving, StatusLeft, StatusFailed}
799 expect := []string{"none", "alive", "leaving", "left", "failed"}
800
801 for idx, s := range status {
802 if s.String() != expect[idx] {
803 t.Fatalf("got string %v, expected %v", s.String(), expect[idx])
804 }
805 }
806
807 other := MemberStatus(100)
808 defer func() {
809 if r := recover(); r == nil {
810 t.Fatalf("expected panic")
811 }
812 }()
813 other.String()
814 }
815
816 func TestSerf_joinLeaveJoin(t *testing.T) {
817 s1Config := testConfig()
818 s1Config.ReapInterval = 10 * time.Second
819 s2Config := testConfig()
820 s2Config.ReapInterval = 10 * time.Second
821
822 s1, err := Create(s1Config)
823 if err != nil {
824 t.Fatalf("err: %s", err)
825 }
826 defer s1.Shutdown()
827
828 s2, err := Create(s2Config)
829 if err != nil {
830 t.Fatalf("err: %s", err)
831 }
832
833 testutil.Yield()
834
835 if len(s1.Members()) != 1 {
836 t.Fatalf("s1 members: %d", len(s1.Members()))
837 }
838
839 if len(s2.Members()) != 1 {
840 t.Fatalf("s2 members: %d", len(s2.Members()))
841 }
842
843 _, err = s1.Join([]string{s2Config.MemberlistConfig.BindAddr}, false)
844 if err != nil {
845 t.Fatalf("err: %s", err)
846 }
847
848 testutil.Yield()
849
850 if len(s1.Members()) != 2 {
851 t.Fatalf("s1 members: %d", len(s1.Members()))
852 }
853
854 if len(s2.Members()) != 2 {
855 t.Fatalf("s2 members: %d", len(s2.Members()))
856 }
857
858 // Leave and shutdown
859 err = s2.Leave()
860 if err != nil {
861 t.Fatalf("err: %s", err)
862 }
863 s2.Shutdown()
864
865 // Give the reaper time to reap nodes
866 time.Sleep(s1Config.MemberlistConfig.ProbeInterval * 5)
867
868 // s1 should see the node as having left
869 mems := s1.Members()
870 anyLeft := false
871 for _, m := range mems {
872 if m.Status == StatusLeft {
873 anyLeft = true
874 break
875 }
876 }
877 if !anyLeft {
878 t.Fatalf("node should have left!")
879 }
880
881 // Bring node 2 back
882 s2, err = Create(s2Config)
883 if err != nil {
884 t.Fatalf("err: %s", err)
885 }
886 defer s2.Shutdown()
887
888 testutil.Yield()
889
890 // Re-attempt the join
891 _, err = s1.Join([]string{s2Config.MemberlistConfig.BindAddr}, false)
892 if err != nil {
893 t.Fatalf("err: %s", err)
894 }
895
896 testutil.Yield()
897
898 // Should be back to both members
899 if len(s1.Members()) != 2 {
900 t.Fatalf("s1 members: %d", len(s1.Members()))
901 }
902
903 if len(s2.Members()) != 2 {
904 t.Fatalf("s2 members: %d", len(s2.Members()))
905 }
906
907 // s1 should see the node as alive
908 mems = s1.Members()
909 anyLeft = false
910 for _, m := range mems {
911 if m.Status == StatusLeft {
912 anyLeft = true
913 break
914 }
915 }
916 if anyLeft {
917 t.Fatalf("all nodes should be alive!")
918 }
919 }
920
921 func TestSerf_Join_IgnoreOld(t *testing.T) {
922 // Create the s1 config with an event channel so we can listen
923 eventCh := make(chan Event, 4)
924 s1Config := testConfig()
925 s2Config := testConfig()
926 s2Config.EventCh = eventCh
927
928 s1, err := Create(s1Config)
929 if err != nil {
930 t.Fatalf("err: %s", err)
931 }
932
933 s2, err := Create(s2Config)
934 if err != nil {
935 t.Fatalf("err: %s", err)
936 }
937
938 defer s1.Shutdown()
939 defer s2.Shutdown()
940
941 testutil.Yield()
942
943 // Fire a user event
944 if err := s1.UserEvent("event!", []byte("test"), false); err != nil {
945 t.Fatalf("err: %s", err)
946 }
947
948 testutil.Yield()
949
950 // Fire a user event
951 if err := s1.UserEvent("second", []byte("foobar"), false); err != nil {
952 t.Fatalf("err: %s", err)
953 }
954
955 testutil.Yield()
956
957 // join with ignoreOld set to true! should not get events
958 _, err = s2.Join([]string{s1Config.MemberlistConfig.BindAddr}, true)
959 if err != nil {
960 t.Fatalf("err: %s", err)
961 }
962
963 testutil.Yield()
964
965 // check the events to make sure we got nothing
966 testUserEvents(t, eventCh, []string{}, [][]byte{})
967 }
968
969 func TestSerf_SnapshotRecovery(t *testing.T) {
970 td, err := ioutil.TempDir("", "serf")
971 if err != nil {
972 t.Fatalf("err: %v", err)
973 }
974 defer os.RemoveAll(td)
975
976 s1Config := testConfig()
977 s2Config := testConfig()
978 s2Config.SnapshotPath = td + "snap"
979
980 s1, err := Create(s1Config)
981 if err != nil {
982 t.Fatalf("err: %s", err)
983 }
984 defer s1.Shutdown()
985
986 s2, err := Create(s2Config)
987 if err != nil {
988 t.Fatalf("err: %s", err)
989 }
990 defer s2.Shutdown()
991
992 _, err = s1.Join([]string{s2Config.MemberlistConfig.BindAddr}, false)
993 if err != nil {
994 t.Fatalf("err: %s", err)
995 }
996
997 testutil.Yield()
998
999 // Fire a user event
1000 if err := s1.UserEvent("event!", []byte("test"), false); err != nil {
1001 t.Fatalf("err: %s", err)
1002 }
1003
1004 testutil.Yield()
1005
1006 // Now force the shutdown of s2 so it appears to fail.
1007 if err := s2.Shutdown(); err != nil {
1008 t.Fatalf("err: %s", err)
1009 }
1010 time.Sleep(s2Config.MemberlistConfig.ProbeInterval * 10)
1011
1012 // Verify that s2 is "failed"
1013 testMember(t, s1.Members(), s2Config.NodeName, StatusFailed)
1014
1015 // Now remove the failed node
1016 if err := s1.RemoveFailedNode(s2Config.NodeName); err != nil {
1017 t.Fatalf("err: %s", err)
1018 }
1019
1020 // Verify that s2 is gone
1021 testMember(t, s1.Members(), s2Config.NodeName, StatusLeft)
1022
1023 // Listen for events
1024 eventCh := make(chan Event, 4)
1025 s2Config.EventCh = eventCh
1026
1027 // Restart s2 from the snapshot now!
1028 s2, err = Create(s2Config)
1029 if err != nil {
1030 t.Fatalf("err: %s", err)
1031 }
1032 defer s2.Shutdown()
1033
1034 // Wait for the node to auto rejoin
1035 start := time.Now()
1036 for time.Now().Sub(start) < time.Second {
1037 members := s1.Members()
1038 if len(members) == 2 && members[0].Status == StatusAlive && members[1].Status == StatusAlive {
1039 break
1040 }
1041 time.Sleep(10 * time.Millisecond)
1042 }
1043
1044 // Verify that s2 is "alive"
1045 testMember(t, s1.Members(), s2Config.NodeName, StatusAlive)
1046 testMember(t, s2.Members(), s1Config.NodeName, StatusAlive)
1047
1048 // Check the events to make sure we got nothing
1049 testUserEvents(t, eventCh, []string{}, [][]byte{})
1050 }
1051
1052 func TestSerf_Leave_SnapshotRecovery(t *testing.T) {
1053 td, err := ioutil.TempDir("", "serf")
1054 if err != nil {
1055 t.Fatalf("err: %v", err)
1056 }
1057 defer os.RemoveAll(td)
1058
1059 s1Config := testConfig()
1060 s2Config := testConfig()
1061 s2Config.SnapshotPath = td + "snap"
1062
1063 s1, err := Create(s1Config)
1064 if err != nil {
1065 t.Fatalf("err: %s", err)
1066 }
1067 defer s1.Shutdown()
1068
1069 s2, err := Create(s2Config)
1070 if err != nil {
1071 t.Fatalf("err: %s", err)
1072 }
1073 defer s2.Shutdown()
1074
1075 _, err = s1.Join([]string{s2Config.MemberlistConfig.BindAddr}, false)
1076 if err != nil {
1077 t.Fatalf("err: %s", err)
1078 }
1079
1080 testutil.Yield()
1081
1082 if err := s2.Leave(); err != nil {
1083 t.Fatalf("err: %s", err)
1084 }
1085 if err := s2.Shutdown(); err != nil {
1086 t.Fatalf("err: %s", err)
1087 }
1088 time.Sleep(s2Config.MemberlistConfig.ProbeInterval * 5)
1089
1090 // Verify that s2 is "left"
1091 testMember(t, s1.Members(), s2Config.NodeName, StatusLeft)
1092
1093 // Restart s2 from the snapshot now!
1094 s2Config.EventCh = nil
1095 s2, err = Create(s2Config)
1096 if err != nil {
1097 t.Fatalf("err: %s", err)
1098 }
1099 defer s2.Shutdown()
1100
1101 // Wait for the node to auto rejoin
1102 testutil.Yield()
1103
1104 // Verify that s2 is didn't join
1105 testMember(t, s1.Members(), s2Config.NodeName, StatusLeft)
1106 if len(s2.Members()) != 1 {
1107 t.Fatalf("bad members: %#v", s2.Members())
1108 }
1109 }
1110
1111 func TestSerf_SetTags(t *testing.T) {
1112 eventCh := make(chan Event, 4)
1113 s1Config := testConfig()
1114 s1Config.EventCh = eventCh
1115 s2Config := testConfig()
1116
1117 s1, err := Create(s1Config)
1118 if err != nil {
1119 t.Fatalf("err: %s", err)
1120 }
1121 defer s1.Shutdown()
1122
1123 s2, err := Create(s2Config)
1124 if err != nil {
1125 t.Fatalf("err: %s", err)
1126 }
1127 defer s2.Shutdown()
1128 testutil.Yield()
1129
1130 _, err = s1.Join([]string{s2Config.MemberlistConfig.BindAddr}, false)
1131 if err != nil {
1132 t.Fatalf("err: %s", err)
1133 }
1134 testutil.Yield()
1135
1136 // Update the tags
1137 if err := s1.SetTags(map[string]string{"port": "8000"}); err != nil {
1138 t.Fatalf("err: %s", err)
1139 }
1140
1141 if err := s2.SetTags(map[string]string{"datacenter": "east-aws"}); err != nil {
1142 t.Fatalf("err: %s", err)
1143 }
1144 testutil.Yield()
1145
1146 // Since s2 shutdown, we check the events to make sure we got failures.
1147 testEvents(t, eventCh, s2Config.NodeName,
1148 []EventType{EventMemberJoin, EventMemberUpdate})
1149
1150 // Verify the new tags
1151 m1m := s1.Members()
1152 m1m_tags := make(map[string]map[string]string)
1153 for _, m := range m1m {
1154 m1m_tags[m.Name] = m.Tags
1155 }
1156
1157 if m := m1m_tags[s1.config.NodeName]; m["port"] != "8000" {
1158 t.Fatalf("bad: %v", m1m_tags)
1159 }
1160
1161 if m := m1m_tags[s2.config.NodeName]; m["datacenter"] != "east-aws" {
1162 t.Fatalf("bad: %v", m1m_tags)
1163 }
1164
1165 m2m := s2.Members()
1166 m2m_tags := make(map[string]map[string]string)
1167 for _, m := range m2m {
1168 m2m_tags[m.Name] = m.Tags
1169 }
1170
1171 if m := m2m_tags[s1.config.NodeName]; m["port"] != "8000" {
1172 t.Fatalf("bad: %v", m1m_tags)
1173 }
1174
1175 if m := m2m_tags[s2.config.NodeName]; m["datacenter"] != "east-aws" {
1176 t.Fatalf("bad: %v", m1m_tags)
1177 }
1178 }
1179
1180 func TestSerf_Query(t *testing.T) {
1181 eventCh := make(chan Event, 4)
1182 s1Config := testConfig()
1183 s1Config.EventCh = eventCh
1184 s1, err := Create(s1Config)
1185 if err != nil {
1186 t.Fatalf("err: %s", err)
1187 }
1188 defer s1.Shutdown()
1189
1190 // Listen for the query
1191 go func() {
1192 for {
1193 select {
1194 case e := <-eventCh:
1195 if e.EventType() != EventQuery {
1196 continue
1197 }
1198 q := e.(*Query)
1199 if err := q.Respond([]byte("test")); err != nil {
1200 t.Fatalf("err: %s", err)
1201 }
1202 return
1203 case <-time.After(time.Second):
1204 t.Fatalf("timeout")
1205 }
1206 }
1207 }()
1208
1209 s2Config := testConfig()
1210 s2, err := Create(s2Config)
1211 if err != nil {
1212 t.Fatalf("err: %s", err)
1213 }
1214 defer s2.Shutdown()
1215 testutil.Yield()
1216
1217 _, err = s1.Join([]string{s2Config.MemberlistConfig.BindAddr}, false)
1218 if err != nil {
1219 t.Fatalf("err: %s", err)
1220 }
1221 testutil.Yield()
1222
1223 // Start a query from s2
1224 params := s2.DefaultQueryParams()
1225 params.RequestAck = true
1226 resp, err := s2.Query("load", []byte("sup girl"), params)
1227 if err != nil {
1228 t.Fatalf("err: %s", err)
1229 }
1230
1231 var acks []string
1232 var responses []string
1233
1234 ackCh := resp.AckCh()
1235 respCh := resp.ResponseCh()
1236 for i := 0; i < 3; i++ {
1237 select {
1238 case a := <-ackCh:
1239 acks = append(acks, a)
1240
1241 case r := <-respCh:
1242 if r.From != s1Config.NodeName {
1243 t.Fatalf("bad: %v", r)
1244 }
1245 if string(r.Payload) != "test" {
1246 t.Fatalf("bad: %v", r)
1247 }
1248 responses = append(responses, r.From)
1249
1250 case <-time.After(time.Second):
1251 t.Fatalf("timeout")
1252 }
1253 }
1254
1255 if len(acks) != 2 {
1256 t.Fatalf("missing acks: %v", acks)
1257 }
1258 if len(responses) != 1 {
1259 t.Fatalf("missing responses: %v", responses)
1260 }
1261 }
1262
1263 func TestSerf_Query_Filter(t *testing.T) {
1264 eventCh := make(chan Event, 4)
1265 s1Config := testConfig()
1266 s1Config.EventCh = eventCh
1267 s1, err := Create(s1Config)
1268 if err != nil {
1269 t.Fatalf("err: %s", err)
1270 }
1271 defer s1.Shutdown()
1272
1273 // Listen for the query
1274 go func() {
1275 for {
1276 select {
1277 case e := <-eventCh:
1278 if e.EventType() != EventQuery {
1279 continue
1280 }
1281 q := e.(*Query)
1282 if err := q.Respond([]byte("test")); err != nil {
1283 t.Fatalf("err: %s", err)
1284 }
1285 return
1286 case <-time.After(time.Second):
1287 t.Fatalf("timeout")
1288 }
1289 }
1290 }()
1291
1292 s2Config := testConfig()
1293 s2, err := Create(s2Config)
1294 if err != nil {
1295 t.Fatalf("err: %s", err)
1296 }
1297 defer s2.Shutdown()
1298 testutil.Yield()
1299
1300 _, err = s1.Join([]string{s2Config.MemberlistConfig.BindAddr}, false)
1301 if err != nil {
1302 t.Fatalf("err: %s", err)
1303 }
1304 testutil.Yield()
1305
1306 // Filter to only s1!
1307 params := s2.DefaultQueryParams()
1308 params.FilterNodes = []string{s1Config.NodeName}
1309 params.RequestAck = true
1310
1311 // Start a query from s2
1312 resp, err := s2.Query("load", []byte("sup girl"), params)
1313 if err != nil {
1314 t.Fatalf("err: %s", err)
1315 }
1316
1317 var acks []string
1318 var responses []string
1319
1320 ackCh := resp.AckCh()
1321 respCh := resp.ResponseCh()
1322 for i := 0; i < 2; i++ {
1323 select {
1324 case a := <-ackCh:
1325 acks = append(acks, a)
1326
1327 case r := <-respCh:
1328 if r.From != s1Config.NodeName {
1329 t.Fatalf("bad: %v", r)
1330 }
1331 if string(r.Payload) != "test" {
1332 t.Fatalf("bad: %v", r)
1333 }
1334 responses = append(responses, r.From)
1335
1336 case <-time.After(time.Second):
1337 t.Fatalf("timeout")
1338 }
1339 }
1340
1341 if len(acks) != 1 {
1342 t.Fatalf("missing acks: %v", acks)
1343 }
1344 if len(responses) != 1 {
1345 t.Fatalf("missing responses: %v", responses)
1346 }
1347 }
1348
1349 func TestSerf_Query_sizeLimit(t *testing.T) {
1350 // Create the s1 config with an event channel so we can listen
1351 s1Config := testConfig()
1352 s1, err := Create(s1Config)
1353 if err != nil {
1354 t.Fatalf("err: %s", err)
1355 }
1356 defer s1.Shutdown()
1357
1358 name := "this is too large a query"
1359 payload := make([]byte, QuerySizeLimit)
1360 _, err = s1.Query(name, payload, nil)
1361 if err == nil {
1362 t.Fatalf("should get error")
1363 }
1364 if !strings.HasPrefix(err.Error(), "query exceeds limit of ") {
1365 t.Fatalf("should get size limit error: %v", err)
1366 }
1367 }
1368
1369 func TestSerf_NameResolution(t *testing.T) {
1370 // Create the s1 config with an event channel so we can listen
1371 s1Config := testConfig()
1372 s2Config := testConfig()
1373 s3Config := testConfig()
1374
1375 s1, err := Create(s1Config)
1376 if err != nil {
1377 t.Fatalf("err: %s", err)
1378 }
1379 defer s1.Shutdown()
1380
1381 s2, err := Create(s2Config)
1382 if err != nil {
1383 t.Fatalf("err: %s", err)
1384 }
1385 defer s2.Shutdown()
1386
1387 // Create an artificial node name conflict!
1388 s3Config.NodeName = s1Config.NodeName
1389 s3, err := Create(s3Config)
1390 if err != nil {
1391 t.Fatalf("err: %s", err)
1392 }
1393 defer s3.Shutdown()
1394
1395 testutil.Yield()
1396
1397 // Join s1 to s2 first. s2 should vote for s1 in conflict
1398 _, err = s1.Join([]string{s2Config.MemberlistConfig.BindAddr}, false)
1399 if err != nil {
1400 t.Fatalf("err: %s", err)
1401 }
1402
1403 _, err = s1.Join([]string{s3Config.MemberlistConfig.BindAddr}, false)
1404 if err != nil {
1405 t.Fatalf("err: %s", err)
1406 }
1407
1408 testutil.Yield()
1409
1410 // Wait for the query period to end
1411 time.Sleep(s1.DefaultQueryTimeout() * 2)
1412
1413 // s3 should have shutdown, while s1 is running
1414 if s1.State() != SerfAlive {
1415 t.Fatalf("bad: %v", s1.State())
1416 }
1417 if s2.State() != SerfAlive {
1418 t.Fatalf("bad: %v", s2.State())
1419 }
1420 if s3.State() != SerfShutdown {
1421 t.Fatalf("bad: %v", s3.State())
1422 }
1423 }
1424
1425 func TestSerf_LocalMember(t *testing.T) {
1426 s1Config := testConfig()
1427 s1, err := Create(s1Config)
1428 if err != nil {
1429 t.Fatalf("err: %s", err)
1430 }
1431 defer s1.Shutdown()
1432
1433 m := s1.LocalMember()
1434 if m.Name != s1Config.NodeName {
1435 t.Fatalf("bad: %v", m)
1436 }
1437 if !reflect.DeepEqual(m.Tags, s1Config.Tags) {
1438 t.Fatalf("bad: %v", m)
1439 }
1440 if m.Status != StatusAlive {
1441 t.Fatalf("bad: %v", m)
1442 }
1443
1444 newTags := map[string]string{
1445 "foo": "bar",
1446 "test": "ing",
1447 }
1448 if err := s1.SetTags(newTags); err != nil {
1449 t.Fatalf("err: %s", err)
1450 }
1451
1452 m = s1.LocalMember()
1453 if !reflect.DeepEqual(m.Tags, newTags) {
1454 t.Fatalf("bad: %v", m)
1455 }
1456 }
1457
1458 func TestSerf_WriteKeyringFile(t *testing.T) {
1459 existing := "jbuQMI4gMUeh1PPmKOtiBg=="
1460 newKey := "eodFZZjm7pPwIZ0Miy7boQ=="
1461
1462 td, err := ioutil.TempDir("", "serf")
1463 if err != nil {
1464 t.Fatalf("err: %s", err)
1465 }
1466 defer os.RemoveAll(td)
1467
1468 keyringFile := filepath.Join(td, "tags.json")
1469
1470 existingBytes, err := base64.StdEncoding.DecodeString(existing)
1471 if err != nil {
1472 t.Fatalf("err: %s", err)
1473 }
1474
1475 keys := [][]byte{existingBytes}
1476 keyring, err := memberlist.NewKeyring(keys, existingBytes)
1477 if err != nil {
1478 t.Fatalf("err: %s", err)
1479 }
1480
1481 s1Config := testConfig()
1482 s1Config.MemberlistConfig.Keyring = keyring
1483 s1Config.KeyringFile = keyringFile
1484 s1, err := Create(s1Config)
1485 if err != nil {
1486 t.Fatalf("err: %s", err)
1487 }
1488 defer s1.Shutdown()
1489
1490 manager := s1.KeyManager()
1491
1492 if _, err := manager.InstallKey(newKey); err != nil {
1493 t.Fatalf("err: %s", err)
1494 }
1495
1496 content, err := ioutil.ReadFile(keyringFile)
1497 if err != nil {
1498 t.Fatalf("err: %s", err)
1499 }
1500
1501 lines := strings.Split(string(content), "\n")
1502 if len(lines) != 4 {
1503 t.Fatalf("bad: %v", lines)
1504 }
1505
1506 // Ensure both the original key and the new key are present in the file
1507 if !strings.Contains(string(content), existing) {
1508 t.Fatalf("key not found in keyring file: %s", existing)
1509 }
1510 if !strings.Contains(string(content), newKey) {
1511 t.Fatalf("key not found in keyring file: %s", newKey)
1512 }
1513
1514 // Ensure the existing key remains primary. This is in position 1 because
1515 // the file writer will use json.MarshalIndent(), leaving the first line as
1516 // the opening bracket.
1517 if !strings.Contains(lines[1], existing) {
1518 t.Fatalf("expected key to be primary: %s", existing)
1519 }
1520
1521 // Swap primary keys
1522 if _, err := manager.UseKey(newKey); err != nil {
1523 t.Fatalf("err: %s", err)
1524 }
1525
1526 content, err = ioutil.ReadFile(keyringFile)
1527 if err != nil {
1528 t.Fatalf("err: %s", err)
1529 }
1530
1531 lines = strings.Split(string(content), "\n")
1532 if len(lines) != 4 {
1533 t.Fatalf("bad: %v", lines)
1534 }
1535
1536 // Key order should have changed in keyring file
1537 if !strings.Contains(lines[1], newKey) {
1538 t.Fatalf("expected key to be primary: %s", newKey)
1539 }
1540
1541 // Remove the old key
1542 if _, err := manager.RemoveKey(existing); err != nil {
1543 t.Fatalf("err: %s", err)
1544 }
1545
1546 content, err = ioutil.ReadFile(keyringFile)
1547 if err != nil {
1548 t.Fatalf("err: %s", err)
1549 }
1550
1551 lines = strings.Split(string(content), "\n")
1552 if len(lines) != 3 {
1553 t.Fatalf("bad: %v", lines)
1554 }
1555
1556 // Only the new key should now be present in the keyring file
1557 if len(lines) != 3 {
1558 t.Fatalf("bad: %v", lines)
1559 }
1560 if !strings.Contains(lines[1], newKey) {
1561 t.Fatalf("expected key to be primary: %s", newKey)
1562 }
1563 }
1564
1565 func TestSerfStats(t *testing.T) {
1566 config := testConfig()
1567 s1, err := Create(config)
1568 if err != nil {
1569 t.Fatalf("err: %s", err)
1570 }
1571 defer s1.Shutdown()
1572
1573 stats := s1.Stats()
1574
1575 expected := map[string]string{
1576 "event_queue": "0",
1577 "event_time": "1",
1578 "failed": "0",
1579 "intent_queue": "0",
1580 "left": "0",
1581 "member_time": "1",
1582 "members": "1",
1583 "query_queue": "0",
1584 "query_time": "1",
1585 "encrypted": "false",
1586 }
1587
1588 for key, val := range expected {
1589 v, ok := stats[key]
1590 if !ok {
1591 t.Fatalf("key not found in stats: %s", key)
1592 }
1593 if v != val {
1594 t.Fatalf("bad: %s = %s", key, val)
1595 }
1596 }
1597 }
1598
1599 type CancelMergeDelegate struct {
1600 invoked bool
1601 }
1602
1603 func (c *CancelMergeDelegate) NotifyMerge(members []*Member) (cancel bool) {
1604 log.Printf("Merge canceled")
1605 c.invoked = true
1606 return true
1607 }
1608
1609 func TestSerf_Join_Cancel(t *testing.T) {
1610 s1Config := testConfig()
1611 merge1 := &CancelMergeDelegate{}
1612 s1Config.Merge = merge1
1613
1614 s2Config := testConfig()
1615 merge2 := &CancelMergeDelegate{}
1616 s2Config.Merge = merge2
1617
1618 s1, err := Create(s1Config)
1619 if err != nil {
1620 t.Fatalf("err: %s", err)
1621 }
1622
1623 s2, err := Create(s2Config)
1624 if err != nil {
1625 t.Fatalf("err: %s", err)
1626 }
1627
1628 defer s1.Shutdown()
1629 defer s2.Shutdown()
1630
1631 testutil.Yield()
1632
1633 _, err = s1.Join([]string{s2Config.MemberlistConfig.BindAddr}, false)
1634 if err.Error() != "Merge canceled" {
1635 t.Fatalf("err: %s", err)
1636 }
1637
1638 testutil.Yield()
1639
1640 if !merge1.invoked {
1641 t.Fatalf("should invoke")
1642 }
1643 if !merge2.invoked {
1644 t.Fatalf("should invoke")
1645 }
1646 }
0 package serf
1
2 import (
3 "bufio"
4 "fmt"
5 "log"
6 "math/rand"
7 "net"
8 "os"
9 "strconv"
10 "strings"
11 "time"
12
13 "github.com/armon/go-metrics"
14 )
15
16 /*
17 Serf supports using a "snapshot" file that contains various
18 transactional data that is used to help Serf recover quickly
19 and gracefully from a failure. We append member events, as well
20 as the latest clock values to the file during normal operation,
21 and periodically checkpoint and roll over the file. During a restore,
22 we can replay the various member events to recall a list of known
23 nodes to re-join, as well as restore our clock values to avoid replaying
24 old events.
25 */
26
27 const flushInterval = 500 * time.Millisecond
28 const clockUpdateInterval = 500 * time.Millisecond
29 const tmpExt = ".compact"
30
31 // Snapshotter is responsible for ingesting events and persisting
32 // them to disk, and providing a recovery mechanism at start time.
33 type Snapshotter struct {
34 aliveNodes map[string]string
35 clock *LamportClock
36 fh *os.File
37 buffered *bufio.Writer
38 inCh <-chan Event
39 lastFlush time.Time
40 lastClock LamportTime
41 lastEventClock LamportTime
42 lastQueryClock LamportTime
43 leaveCh chan struct{}
44 leaving bool
45 logger *log.Logger
46 maxSize int64
47 path string
48 offset int64
49 outCh chan<- Event
50 rejoinAfterLeave bool
51 shutdownCh <-chan struct{}
52 waitCh chan struct{}
53 }
54
55 // PreviousNode is used to represent the previously known alive nodes
56 type PreviousNode struct {
57 Name string
58 Addr string
59 }
60
61 func (p PreviousNode) String() string {
62 return fmt.Sprintf("%s: %s", p.Name, p.Addr)
63 }
64
65 // NewSnapshotter creates a new Snapshotter that records events up to a
66 // max byte size before rotating the file. It can also be used to
67 // recover old state. Snapshotter works by reading an event channel it returns,
68 // passing through to an output channel, and persisting relevant events to disk.
69 // Setting rejoinAfterLeave makes leave not clear the state, and can be used
70 // if you intend to rejoin the same cluster after a leave.
71 func NewSnapshotter(path string,
72 maxSize int,
73 rejoinAfterLeave bool,
74 logger *log.Logger,
75 clock *LamportClock,
76 outCh chan<- Event,
77 shutdownCh <-chan struct{}) (chan<- Event, *Snapshotter, error) {
78 inCh := make(chan Event, 1024)
79
80 // Try to open the file
81 fh, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0755)
82 if err != nil {
83 return nil, nil, fmt.Errorf("failed to open snapshot: %v", err)
84 }
85
86 // Determine the offset
87 info, err := fh.Stat()
88 if err != nil {
89 fh.Close()
90 return nil, nil, fmt.Errorf("failed to stat snapshot: %v", err)
91 }
92 offset := info.Size()
93
94 // Create the snapshotter
95 snap := &Snapshotter{
96 aliveNodes: make(map[string]string),
97 clock: clock,
98 fh: fh,
99 buffered: bufio.NewWriter(fh),
100 inCh: inCh,
101 lastClock: 0,
102 lastEventClock: 0,
103 lastQueryClock: 0,
104 leaveCh: make(chan struct{}),
105 logger: logger,
106 maxSize: int64(maxSize),
107 path: path,
108 offset: offset,
109 outCh: outCh,
110 rejoinAfterLeave: rejoinAfterLeave,
111 shutdownCh: shutdownCh,
112 waitCh: make(chan struct{}),
113 }
114
115 // Recover the last known state
116 if err := snap.replay(); err != nil {
117 fh.Close()
118 return nil, nil, err
119 }
120
121 // Start handling new commands
122 go snap.stream()
123 return inCh, snap, nil
124 }
125
126 // LastClock returns the last known clock time
127 func (s *Snapshotter) LastClock() LamportTime {
128 return s.lastClock
129 }
130
131 // LastEventClock returns the last known event clock time
132 func (s *Snapshotter) LastEventClock() LamportTime {
133 return s.lastEventClock
134 }
135
136 // LastQueryClock returns the last known query clock time
137 func (s *Snapshotter) LastQueryClock() LamportTime {
138 return s.lastQueryClock
139 }
140
141 // AliveNodes returns the last known alive nodes
142 func (s *Snapshotter) AliveNodes() []*PreviousNode {
143 // Copy the previously known
144 previous := make([]*PreviousNode, 0, len(s.aliveNodes))
145 for name, addr := range s.aliveNodes {
146 previous = append(previous, &PreviousNode{name, addr})
147 }
148
149 // Randomize the order, prevents hot shards
150 for i := range previous {
151 j := rand.Intn(i + 1)
152 previous[i], previous[j] = previous[j], previous[i]
153 }
154 return previous
155 }
156
157 // Wait is used to wait until the snapshotter finishes shut down
158 func (s *Snapshotter) Wait() {
159 <-s.waitCh
160 }
161
162 // Leave is used to remove known nodes to prevent a restart from
163 // causing a join. Otherwise nodes will re-join after leaving!
164 func (s *Snapshotter) Leave() {
165 select {
166 case s.leaveCh <- struct{}{}:
167 case <-s.shutdownCh:
168 }
169 }
170
171 // stream is a long running routine that is used to handle events
172 func (s *Snapshotter) stream() {
173 for {
174 select {
175 case <-s.leaveCh:
176 s.leaving = true
177
178 // If we plan to re-join, keep our state
179 if !s.rejoinAfterLeave {
180 s.aliveNodes = make(map[string]string)
181 }
182 s.tryAppend("leave\n")
183 if err := s.buffered.Flush(); err != nil {
184 s.logger.Printf("[ERR] serf: failed to flush leave to snapshot: %v", err)
185 }
186 if err := s.fh.Sync(); err != nil {
187 s.logger.Printf("[ERR] serf: failed to sync leave to snapshot: %v", err)
188 }
189
190 case e := <-s.inCh:
191 // Forward the event immediately
192 if s.outCh != nil {
193 s.outCh <- e
194 }
195
196 // Stop recording events after a leave is issued
197 if s.leaving {
198 continue
199 }
200 switch typed := e.(type) {
201 case MemberEvent:
202 s.processMemberEvent(typed)
203 case UserEvent:
204 s.processUserEvent(typed)
205 case *Query:
206 s.processQuery(typed)
207 default:
208 s.logger.Printf("[ERR] serf: Unknown event to snapshot: %#v", e)
209 }
210
211 case <-time.After(clockUpdateInterval):
212 s.updateClock()
213
214 case <-s.shutdownCh:
215 if err := s.buffered.Flush(); err != nil {
216 s.logger.Printf("[ERR] serf: failed to flush snapshot: %v", err)
217 }
218 if err := s.fh.Sync(); err != nil {
219 s.logger.Printf("[ERR] serf: failed to sync snapshot: %v", err)
220 }
221 s.fh.Close()
222 close(s.waitCh)
223 return
224 }
225 }
226 }
227
228 // processMemberEvent is used to handle a single member event
229 func (s *Snapshotter) processMemberEvent(e MemberEvent) {
230 switch e.Type {
231 case EventMemberJoin:
232 for _, mem := range e.Members {
233 addr := net.TCPAddr{IP: mem.Addr, Port: int(mem.Port)}
234 s.aliveNodes[mem.Name] = addr.String()
235 s.tryAppend(fmt.Sprintf("alive: %s %s\n", mem.Name, addr.String()))
236 }
237
238 case EventMemberLeave:
239 fallthrough
240 case EventMemberFailed:
241 for _, mem := range e.Members {
242 delete(s.aliveNodes, mem.Name)
243 s.tryAppend(fmt.Sprintf("not-alive: %s\n", mem.Name))
244 }
245 }
246 s.updateClock()
247 }
248
249 // updateClock is called periodically to check if we should udpate our
250 // clock value. This is done after member events but should also be done
251 // periodically due to race conditions with join and leave intents
252 func (s *Snapshotter) updateClock() {
253 lastSeen := s.clock.Time() - 1
254 if lastSeen > s.lastClock {
255 s.lastClock = lastSeen
256 s.tryAppend(fmt.Sprintf("clock: %d\n", s.lastClock))
257 }
258 }
259
260 // processUserEvent is used to handle a single user event
261 func (s *Snapshotter) processUserEvent(e UserEvent) {
262 // Ignore old clocks
263 if e.LTime <= s.lastEventClock {
264 return
265 }
266 s.lastEventClock = e.LTime
267 s.tryAppend(fmt.Sprintf("event-clock: %d\n", e.LTime))
268 }
269
270 // processQuery is used to handle a single query event
271 func (s *Snapshotter) processQuery(q *Query) {
272 // Ignore old clocks
273 if q.LTime <= s.lastQueryClock {
274 return
275 }
276 s.lastQueryClock = q.LTime
277 s.tryAppend(fmt.Sprintf("query-clock: %d\n", q.LTime))
278 }
279
280 // tryAppend will invoke append line but will not return an error
281 func (s *Snapshotter) tryAppend(l string) {
282 if err := s.appendLine(l); err != nil {
283 s.logger.Printf("[ERR] serf: Failed to update snapshot: %v", err)
284 }
285 }
286
287 // appendLine is used to append a line to the existing log
288 func (s *Snapshotter) appendLine(l string) error {
289 defer metrics.MeasureSince([]string{"serf", "snapshot", "appendLine"}, time.Now())
290
291 n, err := s.buffered.WriteString(l)
292 if err != nil {
293 return err
294 }
295
296 // Check if we should flush
297 now := time.Now()
298 if now.Sub(s.lastFlush) > flushInterval {
299 s.lastFlush = now
300 if err := s.buffered.Flush(); err != nil {
301 return err
302 }
303 }
304
305 // Check if a compaction is necessary
306 s.offset += int64(n)
307 if s.offset > s.maxSize {
308 return s.compact()
309 }
310 return nil
311 }
312
313 // Compact is used to compact the snapshot once it is too large
314 func (s *Snapshotter) compact() error {
315 defer metrics.MeasureSince([]string{"serf", "snapshot", "compact"}, time.Now())
316
317 // Try to open the file to new fiel
318 newPath := s.path + tmpExt
319 fh, err := os.OpenFile(newPath, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0755)
320 if err != nil {
321 return fmt.Errorf("failed to open new snapshot: %v", err)
322 }
323
324 // Create a buffered writer
325 buf := bufio.NewWriter(fh)
326
327 // Write out the live nodes
328 var offset int64
329 for name, addr := range s.aliveNodes {
330 line := fmt.Sprintf("alive: %s %s\n", name, addr)
331 n, err := buf.WriteString(line)
332 if err != nil {
333 fh.Close()
334 return err
335 }
336 offset += int64(n)
337 }
338
339 // Write out the clocks
340 line := fmt.Sprintf("clock: %d\n", s.lastClock)
341 n, err := buf.WriteString(line)
342 if err != nil {
343 fh.Close()
344 return err
345 }
346 offset += int64(n)
347
348 line = fmt.Sprintf("event-clock: %d\n", s.lastEventClock)
349 n, err = buf.WriteString(line)
350 if err != nil {
351 fh.Close()
352 return err
353 }
354 offset += int64(n)
355
356 line = fmt.Sprintf("query-clock: %d\n", s.lastQueryClock)
357 n, err = buf.WriteString(line)
358 if err != nil {
359 fh.Close()
360 return err
361 }
362 offset += int64(n)
363
364 // Flush the new snapshot
365 err = buf.Flush()
366 fh.Close()
367 if err != nil {
368 return fmt.Errorf("failed to flush new snapshot: %v", err)
369 }
370
371 // We now need to swap the old snapshot file with the new snapshot.
372 // Turns out, Windows won't let us rename the files if we have
373 // open handles to them or if the destination already exists. This
374 // means we are forced to close the existing handles, delete the
375 // old file, move the new one in place, and then re-open the file
376 // handles.
377
378 // Flush the existing snapshot, ignoring errors since we will
379 // delete it momentarily.
380 s.buffered.Flush()
381 s.buffered = nil
382
383 // Close the file handle to the old snapshot
384 s.fh.Close()
385 s.fh = nil
386
387 // Delete the old file
388 if err := os.Remove(s.path); err != nil {
389 return fmt.Errorf("failed to remove old snapshot: %v", err)
390 }
391
392 // Move the new file into place
393 if err := os.Rename(newPath, s.path); err != nil {
394 return fmt.Errorf("failed to install new snapshot: %v", err)
395 }
396
397 // Open the new snapshot
398 fh, err = os.OpenFile(s.path, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0755)
399 if err != nil {
400 return fmt.Errorf("failed to open snapshot: %v", err)
401 }
402 buf = bufio.NewWriter(fh)
403
404 // Rotate our handles
405 s.fh = fh
406 s.buffered = buf
407 s.offset = offset
408 s.lastFlush = time.Now()
409 return nil
410 }
411
412 // replay is used to seek to reset our internal state by replaying
413 // the snapshot file. It is used at initialization time to read old
414 // state
415 func (s *Snapshotter) replay() error {
416 // Seek to the beginning
417 if _, err := s.fh.Seek(0, os.SEEK_SET); err != nil {
418 return err
419 }
420
421 // Read each line
422 reader := bufio.NewReader(s.fh)
423 for {
424 line, err := reader.ReadString('\n')
425 if err != nil {
426 break
427 }
428
429 // Skip the newline
430 line = line[:len(line)-1]
431
432 // Switch on the prefix
433 if strings.HasPrefix(line, "alive: ") {
434 info := strings.TrimPrefix(line, "alive: ")
435 addrIdx := strings.LastIndex(info, " ")
436 if addrIdx == -1 {
437 s.logger.Printf("[WARN] serf: Failed to parse address: %v", line)
438 continue
439 }
440 addr := info[addrIdx+1:]
441 name := info[:addrIdx]
442 s.aliveNodes[name] = addr
443
444 } else if strings.HasPrefix(line, "not-alive: ") {
445 name := strings.TrimPrefix(line, "not-alive: ")
446 delete(s.aliveNodes, name)
447
448 } else if strings.HasPrefix(line, "clock: ") {
449 timeStr := strings.TrimPrefix(line, "clock: ")
450 timeInt, err := strconv.ParseUint(timeStr, 10, 64)
451 if err != nil {
452 s.logger.Printf("[WARN] serf: Failed to convert clock time: %v", err)
453 continue
454 }
455 s.lastClock = LamportTime(timeInt)
456
457 } else if strings.HasPrefix(line, "event-clock: ") {
458 timeStr := strings.TrimPrefix(line, "event-clock: ")
459 timeInt, err := strconv.ParseUint(timeStr, 10, 64)
460 if err != nil {
461 s.logger.Printf("[WARN] serf: Failed to convert event clock time: %v", err)
462 continue
463 }
464 s.lastEventClock = LamportTime(timeInt)
465
466 } else if strings.HasPrefix(line, "query-clock: ") {
467 timeStr := strings.TrimPrefix(line, "query-clock: ")
468 timeInt, err := strconv.ParseUint(timeStr, 10, 64)
469 if err != nil {
470 s.logger.Printf("[WARN] serf: Failed to convert query clock time: %v", err)
471 continue
472 }
473 s.lastQueryClock = LamportTime(timeInt)
474
475 } else if line == "leave" {
476 // Ignore a leave if we plan on re-joining
477 if s.rejoinAfterLeave {
478 s.logger.Printf("[INFO] serf: Ignoring previous leave in snapshot")
479 continue
480 }
481 s.aliveNodes = make(map[string]string)
482 s.lastClock = 0
483 s.lastEventClock = 0
484 s.lastQueryClock = 0
485
486 } else if strings.HasPrefix(line, "#") {
487 // Skip comment lines
488
489 } else {
490 s.logger.Printf("[WARN] serf: Unrecognized snapshot line: %v", line)
491 }
492 }
493
494 // Seek to the end
495 if _, err := s.fh.Seek(0, os.SEEK_END); err != nil {
496 return err
497 }
498 return nil
499 }
0 package serf
1
2 import (
3 "io/ioutil"
4 "log"
5 "os"
6 "reflect"
7 "testing"
8 "time"
9 )
10
11 func TestSnapshoter(t *testing.T) {
12 td, err := ioutil.TempDir("", "serf")
13 if err != nil {
14 t.Fatalf("err: %v", err)
15 }
16 defer os.RemoveAll(td)
17
18 clock := new(LamportClock)
19 outCh := make(chan Event, 64)
20 stopCh := make(chan struct{})
21 logger := log.New(os.Stderr, "", log.LstdFlags)
22 inCh, snap, err := NewSnapshotter(td+"snap", snapshotSizeLimit, false,
23 logger, clock, outCh, stopCh)
24 if err != nil {
25 t.Fatalf("err: %v", err)
26 }
27
28 // Write some user events
29 ue := UserEvent{
30 LTime: 42,
31 Name: "bar",
32 }
33 inCh <- ue
34
35 // Write some queries
36 q := &Query{
37 LTime: 50,
38 Name: "uptime",
39 }
40 inCh <- q
41
42 // Write some member events
43 clock.Witness(100)
44 meJoin := MemberEvent{
45 Type: EventMemberJoin,
46 Members: []Member{
47 Member{
48 Name: "foo",
49 Addr: []byte{127, 0, 0, 1},
50 Port: 5000,
51 },
52 },
53 }
54 meFail := MemberEvent{
55 Type: EventMemberFailed,
56 Members: []Member{
57 Member{
58 Name: "foo",
59 Addr: []byte{127, 0, 0, 1},
60 Port: 5000,
61 },
62 },
63 }
64 inCh <- meJoin
65 inCh <- meFail
66 inCh <- meJoin
67
68 // Check these get passed through
69 select {
70 case e := <-outCh:
71 if !reflect.DeepEqual(e, ue) {
72 t.Fatalf("expected user event: %#v", e)
73 }
74 case <-time.After(200 * time.Millisecond):
75 t.Fatalf("timeout")
76 }
77
78 select {
79 case e := <-outCh:
80 if !reflect.DeepEqual(e, q) {
81 t.Fatalf("expected query event: %#v", e)
82 }
83 case <-time.After(200 * time.Millisecond):
84 t.Fatalf("timeout")
85 }
86
87 select {
88 case e := <-outCh:
89 if !reflect.DeepEqual(e, meJoin) {
90 t.Fatalf("expected member event: %#v", e)
91 }
92 case <-time.After(200 * time.Millisecond):
93 t.Fatalf("timeout")
94 }
95
96 select {
97 case e := <-outCh:
98 if !reflect.DeepEqual(e, meFail) {
99 t.Fatalf("expected member event: %#v", e)
100 }
101 case <-time.After(200 * time.Millisecond):
102 t.Fatalf("timeout")
103 }
104
105 select {
106 case e := <-outCh:
107 if !reflect.DeepEqual(e, meJoin) {
108 t.Fatalf("expected member event: %#v", e)
109 }
110 case <-time.After(200 * time.Millisecond):
111 t.Fatalf("timeout")
112 }
113
114 // Close the snapshoter
115 close(stopCh)
116 snap.Wait()
117
118 // Open the snapshoter
119 stopCh = make(chan struct{})
120 _, snap, err = NewSnapshotter(td+"snap", snapshotSizeLimit, false,
121 logger, clock, outCh, stopCh)
122 if err != nil {
123 t.Fatalf("err: %v", err)
124 }
125
126 // Check the values
127 if snap.LastClock() != 100 {
128 t.Fatalf("bad clock %d", snap.LastClock())
129 }
130 if snap.LastEventClock() != 42 {
131 t.Fatalf("bad clock %d", snap.LastEventClock())
132 }
133 if snap.LastQueryClock() != 50 {
134 t.Fatalf("bad clock %d", snap.LastQueryClock())
135 }
136
137 prev := snap.AliveNodes()
138 if len(prev) != 1 {
139 t.Fatalf("expected alive: %#v", prev)
140 }
141 if prev[0].Name != "foo" {
142 t.Fatalf("bad name: %#v", prev[0])
143 }
144 if prev[0].Addr != "127.0.0.1:5000" {
145 t.Fatalf("bad addr: %#v", prev[0])
146 }
147 }
148
149 func TestSnapshoter_forceCompact(t *testing.T) {
150 td, err := ioutil.TempDir("", "serf")
151 if err != nil {
152 t.Fatalf("err: %v", err)
153 }
154 defer os.RemoveAll(td)
155
156 clock := new(LamportClock)
157 stopCh := make(chan struct{})
158 logger := log.New(os.Stderr, "", log.LstdFlags)
159
160 // Create a very low limit
161 inCh, snap, err := NewSnapshotter(td+"snap", 1024, false,
162 logger, clock, nil, stopCh)
163 if err != nil {
164 t.Fatalf("err: %v", err)
165 }
166
167 // Write lots of user events
168 for i := 0; i < 1024; i++ {
169 ue := UserEvent{
170 LTime: LamportTime(i),
171 }
172 inCh <- ue
173 }
174
175 // Write lots of queries
176 for i := 0; i < 1024; i++ {
177 q := &Query{
178 LTime: LamportTime(i),
179 }
180 inCh <- q
181 }
182
183 // Wait for drain
184 for len(inCh) > 0 {
185 time.Sleep(20 * time.Millisecond)
186 }
187
188 // Close the snapshoter
189 close(stopCh)
190 snap.Wait()
191
192 // Open the snapshoter
193 stopCh = make(chan struct{})
194 _, snap, err = NewSnapshotter(td+"snap", snapshotSizeLimit, false,
195 logger, clock, nil, stopCh)
196 if err != nil {
197 t.Fatalf("err: %v", err)
198 }
199
200 // Check the values
201 if snap.LastEventClock() != 1023 {
202 t.Fatalf("bad clock %d", snap.LastEventClock())
203 }
204
205 if snap.LastQueryClock() != 1023 {
206 t.Fatalf("bad clock %d", snap.LastQueryClock())
207 }
208
209 close(stopCh)
210 snap.Wait()
211 }
212
213 func TestSnapshoter_leave(t *testing.T) {
214 td, err := ioutil.TempDir("", "serf")
215 if err != nil {
216 t.Fatalf("err: %v", err)
217 }
218 defer os.RemoveAll(td)
219
220 clock := new(LamportClock)
221 stopCh := make(chan struct{})
222 logger := log.New(os.Stderr, "", log.LstdFlags)
223 inCh, snap, err := NewSnapshotter(td+"snap", snapshotSizeLimit, false,
224 logger, clock, nil, stopCh)
225 if err != nil {
226 t.Fatalf("err: %v", err)
227 }
228
229 // Write a user event
230 ue := UserEvent{
231 LTime: 42,
232 Name: "bar",
233 }
234 inCh <- ue
235
236 // Write a query
237 q := &Query{
238 LTime: 50,
239 Name: "uptime",
240 }
241 inCh <- q
242
243 // Write some member events
244 clock.Witness(100)
245 meJoin := MemberEvent{
246 Type: EventMemberJoin,
247 Members: []Member{
248 Member{
249 Name: "foo",
250 Addr: []byte{127, 0, 0, 1},
251 Port: 5000,
252 },
253 },
254 }
255 inCh <- meJoin
256
257 // Wait for drain
258 for len(inCh) > 0 {
259 time.Sleep(20 * time.Millisecond)
260 }
261
262 // Leave the cluster!
263 snap.Leave()
264
265 // Close the snapshoter
266 close(stopCh)
267 snap.Wait()
268
269 // Open the snapshoter
270 stopCh = make(chan struct{})
271 _, snap, err = NewSnapshotter(td+"snap", snapshotSizeLimit, false,
272 logger, clock, nil, stopCh)
273 if err != nil {
274 t.Fatalf("err: %v", err)
275 }
276
277 // Check the values
278 if snap.LastClock() != 0 {
279 t.Fatalf("bad clock %d", snap.LastClock())
280 }
281 if snap.LastEventClock() != 0 {
282 t.Fatalf("bad clock %d", snap.LastEventClock())
283 }
284 if snap.LastQueryClock() != 0 {
285 t.Fatalf("bad clock %d", snap.LastQueryClock())
286 }
287
288 prev := snap.AliveNodes()
289 if len(prev) != 0 {
290 t.Fatalf("expected none alive: %#v", prev)
291 }
292 }
293
294 func TestSnapshoter_leave_rejoin(t *testing.T) {
295 td, err := ioutil.TempDir("", "serf")
296 if err != nil {
297 t.Fatalf("err: %v", err)
298 }
299 defer os.RemoveAll(td)
300
301 clock := new(LamportClock)
302 stopCh := make(chan struct{})
303 logger := log.New(os.Stderr, "", log.LstdFlags)
304 inCh, snap, err := NewSnapshotter(td+"snap", snapshotSizeLimit, true,
305 logger, clock, nil, stopCh)
306 if err != nil {
307 t.Fatalf("err: %v", err)
308 }
309
310 // Write a user event
311 ue := UserEvent{
312 LTime: 42,
313 Name: "bar",
314 }
315 inCh <- ue
316
317 // Write a query
318 q := &Query{
319 LTime: 50,
320 Name: "uptime",
321 }
322 inCh <- q
323
324 // Write some member events
325 clock.Witness(100)
326 meJoin := MemberEvent{
327 Type: EventMemberJoin,
328 Members: []Member{
329 Member{
330 Name: "foo",
331 Addr: []byte{127, 0, 0, 1},
332 Port: 5000,
333 },
334 },
335 }
336 inCh <- meJoin
337
338 // Wait for drain
339 for len(inCh) > 0 {
340 time.Sleep(20 * time.Millisecond)
341 }
342
343 // Leave the cluster!
344 snap.Leave()
345
346 // Close the snapshoter
347 close(stopCh)
348 snap.Wait()
349
350 // Open the snapshoter
351 stopCh = make(chan struct{})
352 _, snap, err = NewSnapshotter(td+"snap", snapshotSizeLimit, true,
353 logger, clock, nil, stopCh)
354 if err != nil {
355 t.Fatalf("err: %v", err)
356 }
357
358 // Check the values
359 if snap.LastClock() != 100 {
360 t.Fatalf("bad clock %d", snap.LastClock())
361 }
362 if snap.LastEventClock() != 42 {
363 t.Fatalf("bad clock %d", snap.LastEventClock())
364 }
365 if snap.LastQueryClock() != 50 {
366 t.Fatalf("bad clock %d", snap.LastQueryClock())
367 }
368
369 prev := snap.AliveNodes()
370 if len(prev) == 0 {
371 t.Fatalf("expected alive: %#v", prev)
372 }
373 }
0 package testutil
1
2 import (
3 "net"
4 "sync"
5 )
6
7 var bindLock sync.Mutex
8 var bindNum byte = 10
9
10 // Returns an unused address for binding to for tests.
11 func GetBindAddr() net.IP {
12 bindLock.Lock()
13 defer bindLock.Unlock()
14
15 result := net.IPv4(127, 0, 0, bindNum)
16 bindNum++
17 if bindNum > 255 {
18 bindNum = 10
19 }
20
21 return result
22 }
0 package testutil
0 package testutil
1
2 import (
3 "time"
4 )
5
6 func Yield() {
7 time.Sleep(10 * time.Millisecond)
8 }
0 package main
1
2 // The git commit that was compiled. This will be filled in by the compiler.
3 var GitCommit string
4
5 // The main version number that is being run at the moment.
6 const Version = "0.6.4"
7
8 // A pre-release marker for the version. If this is "" (empty string)
9 // then it means that it is a final release. Otherwise, this is a pre-release
10 // such as "dev" (in development), "beta", "rc1", etc.
11 const VersionPrerelease = ""
0 https://github.com/heroku/heroku-buildpack-ruby.git
1 https://github.com/hashicorp/heroku-buildpack-middleman.git
0 source "https://rubygems.org"
1
2 ruby "2.1.2"
3
4 gem "middleman-hashicorp", github: "hashicorp/middleman-hashicorp"
0 GIT
1 remote: git://github.com/hashicorp/middleman-hashicorp.git
2 revision: e5ea74bf1269c053cd62578f80967af4ad63798f
3 specs:
4 middleman-hashicorp (0.1.0)
5 bootstrap-sass (~> 3.2)
6 builder (~> 3.2)
7 less (~> 2.6)
8 middleman (~> 3.3)
9 middleman-livereload (~> 3.3)
10 middleman-minify-html (~> 3.4)
11 middleman-syntax (~> 2.0)
12 rack-contrib (~> 1.1)
13 rack-ssl-enforcer (~> 0.2)
14 redcarpet (~> 3.1)
15 therubyracer (~> 0.12)
16 thin (~> 1.6)
17
18 GEM
19 remote: https://rubygems.org/
20 specs:
21 activesupport (4.1.6)
22 i18n (~> 0.6, >= 0.6.9)
23 json (~> 1.7, >= 1.7.7)
24 minitest (~> 5.1)
25 thread_safe (~> 0.1)
26 tzinfo (~> 1.1)
27 bootstrap-sass (3.2.0.2)
28 sass (~> 3.2)
29 builder (3.2.2)
30 celluloid (0.16.0)
31 timers (~> 4.0.0)
32 chunky_png (1.3.2)
33 coffee-script (2.3.0)
34 coffee-script-source
35 execjs
36 coffee-script-source (1.8.0)
37 commonjs (0.2.7)
38 compass (1.0.1)
39 chunky_png (~> 1.2)
40 compass-core (~> 1.0.1)
41 compass-import-once (~> 1.0.5)
42 rb-fsevent (>= 0.9.3)
43 rb-inotify (>= 0.9)
44 sass (>= 3.3.13, < 3.5)
45 compass-core (1.0.1)
46 multi_json (~> 1.0)
47 sass (>= 3.3.0, < 3.5)
48 compass-import-once (1.0.5)
49 sass (>= 3.2, < 3.5)
50 daemons (1.1.9)
51 em-websocket (0.5.1)
52 eventmachine (>= 0.12.9)
53 http_parser.rb (~> 0.6.0)
54 erubis (2.7.0)
55 eventmachine (1.0.3)
56 execjs (2.2.2)
57 ffi (1.9.6)
58 haml (4.0.5)
59 tilt
60 hike (1.2.3)
61 hitimes (1.2.2)
62 hooks (0.4.0)
63 uber (~> 0.0.4)
64 htmlcompressor (0.1.2)
65 http_parser.rb (0.6.0)
66 i18n (0.6.11)
67 json (1.8.1)
68 kramdown (1.4.2)
69 less (2.6.0)
70 commonjs (~> 0.2.7)
71 libv8 (3.16.14.7)
72 listen (2.7.11)
73 celluloid (>= 0.15.2)
74 rb-fsevent (>= 0.9.3)
75 rb-inotify (>= 0.9)
76 middleman (3.3.6)
77 coffee-script (~> 2.2)
78 compass (>= 1.0.0, < 2.0.0)
79 compass-import-once (= 1.0.5)
80 execjs (~> 2.0)
81 haml (>= 4.0.5)
82 kramdown (~> 1.2)
83 middleman-core (= 3.3.6)
84 middleman-sprockets (>= 3.1.2)
85 sass (>= 3.4.0, < 4.0)
86 uglifier (~> 2.5)
87 middleman-core (3.3.6)
88 activesupport (~> 4.1.0)
89 bundler (~> 1.1)
90 erubis
91 hooks (~> 0.3)
92 i18n (~> 0.6.9)
93 listen (>= 2.7.9, < 3.0)
94 padrino-helpers (~> 0.12.3)
95 rack (>= 1.4.5, < 2.0)
96 rack-test (~> 0.6.2)
97 thor (>= 0.15.2, < 2.0)
98 tilt (~> 1.4.1, < 2.0)
99 middleman-livereload (3.3.4)
100 em-websocket (~> 0.5.1)
101 middleman-core (~> 3.2)
102 rack-livereload (~> 0.3.15)
103 middleman-minify-html (3.4.0)
104 htmlcompressor (~> 0.1.0)
105 middleman-core (>= 3.2)
106 middleman-sprockets (3.3.10)
107 middleman-core (~> 3.3)
108 sprockets (~> 2.12.1)
109 sprockets-helpers (~> 1.1.0)
110 sprockets-sass (~> 1.2.0)
111 middleman-syntax (2.0.0)
112 middleman-core (~> 3.2)
113 rouge (~> 1.0)
114 minitest (5.4.2)
115 multi_json (1.10.1)
116 padrino-helpers (0.12.4)
117 i18n (~> 0.6, >= 0.6.7)
118 padrino-support (= 0.12.4)
119 tilt (~> 1.4.1)
120 padrino-support (0.12.4)
121 activesupport (>= 3.1)
122 rack (1.5.2)
123 rack-contrib (1.1.0)
124 rack (>= 0.9.1)
125 rack-livereload (0.3.15)
126 rack
127 rack-ssl-enforcer (0.2.8)
128 rack-test (0.6.2)
129 rack (>= 1.0)
130 rb-fsevent (0.9.4)
131 rb-inotify (0.9.5)
132 ffi (>= 0.5.0)
133 redcarpet (3.2.0)
134 ref (1.0.5)
135 rouge (1.7.2)
136 sass (3.4.6)
137 sprockets (2.12.2)
138 hike (~> 1.2)
139 multi_json (~> 1.0)
140 rack (~> 1.0)
141 tilt (~> 1.1, != 1.3.0)
142 sprockets-helpers (1.1.0)
143 sprockets (~> 2.0)
144 sprockets-sass (1.2.0)
145 sprockets (~> 2.0)
146 tilt (~> 1.1)
147 therubyracer (0.12.1)
148 libv8 (~> 3.16.14.0)
149 ref
150 thin (1.6.3)
151 daemons (~> 1.0, >= 1.0.9)
152 eventmachine (~> 1.0)
153 rack (~> 1.0)
154 thor (0.19.1)
155 thread_safe (0.3.4)
156 tilt (1.4.1)
157 timers (4.0.1)
158 hitimes
159 tzinfo (1.2.2)
160 thread_safe (~> 0.1)
161 uber (0.0.10)
162 uglifier (2.5.3)
163 execjs (>= 0.3.0)
164 json (>= 1.8.0)
165
166 PLATFORMS
167 ruby
168
169 DEPENDENCIES
170 middleman-hashicorp!
0 # Proprietary License
1
2 This license is temporary while a more official one is drafted. However,
3 this should make it clear:
4
5 * The text contents of this website are MPL 2.0 licensed.
6
7 * The design contents of this website are proprietary and may not be reproduced
8 or reused in any way other than to run the Serf website locally. The license
9 for the design is owned solely by HashiCorp, Inc.
0 web: bundle exec thin start -p $PORT
0 # Serf Website
1
2 This subdirectory contains the entire source for the [Serf website](http://www.serfdom.io).
3 This is a [Middleman](http://middlemanapp.com) project, which builds a static
4 site from these source files.
5
6 ## Contributions Welcome!
7
8 If you find a typo or you feel like you can improve the HTML, CSS, or
9 JavaScript, we welcome contributions. Feel free to open issues or pull
10 requests like any normal GitHub project, and we'll merge it in.
11
12 ## Running the Site Locally
13
14 Running the site locally is simple. Clone this repo and run the following
15 commands:
16
17 ```
18 $ bundle
19 $ bundle exec middleman server
20 ```
21
22 Then open up `localhost:4567`. Note that some URLs you may need to append
23 ".html" to make them work (in the navigation and such).
0 #-------------------------------------------------------------------------
1 # Configure Middleman
2 #-------------------------------------------------------------------------
3
4 set :base_url, "https://www.serfdom.io/"
5
6 activate :hashicorp do |h|
7 h.version = '0.6.4'
8 h.bintray_repo = 'mitchellh/serf'
9 h.bintray_user = 'mitchellh'
10 h.bintray_key = ENV['BINTRAY_API_KEY']
11
12 # Currently, Serf builds are not prefixed with serf_*
13 h.bintray_prefixed = false
14 end
15
16 helpers do
17 # This helps by setting the "active" class for sidebar nav elements
18 # if the YAML frontmatter matches the expected value.
19 def sidebar_current(expected)
20 current = current_page.data.sidebar_current
21 if current.start_with?(expected)
22 return " class=\"active\""
23 else
24 return ""
25 end
26 end
27 end
0 require "rack"
1 require "rack/contrib/not_found"
2 require "rack/contrib/response_headers"
3 require "rack/contrib/static_cache"
4 require "rack/contrib/try_static"
5
6 # Properly compress the output if the client can handle it.
7 use Rack::Deflater
8
9 # Set the "forever expire" cache headers for these static assets. Since
10 # we hash the contents of the assets to determine filenames, this is safe
11 # to do.
12 use Rack::StaticCache,
13 :root => "build",
14 :urls => ["/images", "/javascripts", "/stylesheets"],
15 :duration => 2,
16 :versioning => false
17
18 # Try to find a static file that matches our request, since Middleman
19 # statically generates everything.
20 use Rack::TryStatic,
21 :root => "build",
22 :urls => ["/"],
23 :try => [".html", "index.html", "/index.html"]
24
25 # 404 if we reached this point. Sad times.
26 run Rack::NotFound.new(File.expand_path("../build/404.html", __FILE__))
0 ---
1 noindex: true
2 ---
3
4 <h2>Page Not Found</h2>
0 //= require jquery
1 //= require bootstrap
2
3 //= require highcharts
4 //= require classy
5 //= require simulator
6
7 //= require d3
8 //= require serf
0 /**
1 * Classy - classy classes for JavaScript
2 *
3 * :copyright: (c) 2011 by Armin Ronacher.
4 * :license: BSD.
5 */
6
7 ;(function(undefined) {
8 var
9 CLASSY_VERSION = '1.4',
10 root = this,
11 old_class = root.Class,
12 disable_constructor = false;
13
14 /* we check if $super is in use by a class if we can. But first we have to
15 check if the JavaScript interpreter supports that. This also matches
16 to false positives later, but that does not do any harm besides slightly
17 slowing calls down. */
18 var probe_super = (function(){$super();}).toString().indexOf('$super') > 0;
19 function usesSuper(obj) {
20 return !probe_super || /\B\$super\b/.test(obj.toString());
21 }
22
23 /* helper function to set the attribute of something to a value or
24 removes it if the value is undefined. */
25 function setOrUnset(obj, key, value) {
26 if (value === undefined)
27 delete obj[key];
28 else
29 obj[key] = value;
30 }
31
32 /* gets the own property of an object */
33 function getOwnProperty(obj, name) {
34 return Object.prototype.hasOwnProperty.call(obj, name)
35 ? obj[name] : undefined;
36 }
37
38 /* instanciate a class without calling the constructor */
39 function cheapNew(cls) {
40 disable_constructor = true;
41 var rv = new cls;
42 disable_constructor = false;
43 return rv;
44 }
45
46 /* the base class we export */
47 var Class = function() {};
48
49 /* restore the global Class name and pass it to a function. This allows
50 different versions of the classy library to be used side by side and
51 in combination with other libraries. */
52 Class.$noConflict = function() {
53 try {
54 setOrUnset(root, 'Class', old_class);
55 }
56 catch (e) {
57 // fix for IE that does not support delete on window
58 root.Class = old_class;
59 }
60 return Class;
61 };
62
63 /* what version of classy are we using? */
64 Class.$classyVersion = CLASSY_VERSION;
65
66 /* extend functionality */
67 Class.$extend = function(properties) {
68 var super_prototype = this.prototype;
69
70 /* disable constructors and instanciate prototype. Because the
71 prototype can't raise an exception when created, we are safe
72 without a try/finally here. */
73 var prototype = cheapNew(this);
74
75 /* copy all properties of the includes over if there are any */
76 if (properties.__include__)
77 for (var i = 0, n = properties.__include__.length; i != n; ++i) {
78 var mixin = properties.__include__[i];
79 for (var name in mixin) {
80 var value = getOwnProperty(mixin, name);
81 if (value !== undefined)
82 prototype[name] = mixin[name];
83 }
84 }
85
86 /* copy class vars from the superclass */
87 properties.__classvars__ = properties.__classvars__ || {};
88 if (prototype.__classvars__)
89 for (var key in prototype.__classvars__)
90 if (!properties.__classvars__[key]) {
91 var value = getOwnProperty(prototype.__classvars__, key);
92 properties.__classvars__[key] = value;
93 }
94
95 /* copy all properties over to the new prototype */
96 for (var name in properties) {
97 var value = getOwnProperty(properties, name);
98 if (name === '__include__' ||
99 value === undefined)
100 continue;
101
102 prototype[name] = typeof value === 'function' && usesSuper(value) ?
103 (function(meth, name) {
104 return function() {
105 var old_super = getOwnProperty(this, '$super');
106 this.$super = super_prototype[name];
107 try {
108 return meth.apply(this, arguments);
109 }
110 finally {
111 setOrUnset(this, '$super', old_super);
112 }
113 };
114 })(value, name) : value
115 }
116
117 /* dummy constructor */
118 var rv = function() {
119 if (disable_constructor)
120 return;
121 var proper_this = root === this ? cheapNew(arguments.callee) : this;
122 if (proper_this.__init__)
123 proper_this.__init__.apply(proper_this, arguments);
124 proper_this.$class = rv;
125 return proper_this;
126 }
127
128 /* copy all class vars over of any */
129 for (var key in properties.__classvars__) {
130 var value = getOwnProperty(properties.__classvars__, key);
131 if (value !== undefined)
132 rv[key] = value;
133 }
134
135 /* copy prototype and constructor over, reattach $extend and
136 return the class */
137 rv.prototype = prototype;
138 rv.constructor = rv;
139 rv.$extend = Class.$extend;
140 rv.$withData = Class.$withData;
141 return rv;
142 };
143
144 /* instanciate with data functionality */
145 Class.$withData = function(data) {
146 var rv = cheapNew(this);
147 for (var key in data) {
148 var value = getOwnProperty(data, key);
149 if (value !== undefined)
150 rv[key] = value;
151 }
152 return rv;
153 };
154
155 /* export the class */
156 root.Class = Class;
157 })();
0 !function() {
1 var d3 = {
2 version: "3.4.13"
3 };
4 if (!Date.now) Date.now = function() {
5 return +new Date();
6 };
7 var d3_arraySlice = [].slice, d3_array = function(list) {
8 return d3_arraySlice.call(list);
9 };
10 var d3_document = document, d3_documentElement = d3_document.documentElement, d3_window = window;
11 try {
12 d3_array(d3_documentElement.childNodes)[0].nodeType;
13 } catch (e) {
14 d3_array = function(list) {
15 var i = list.length, array = new Array(i);
16 while (i--) array[i] = list[i];
17 return array;
18 };
19 }
20 try {
21 d3_document.createElement("div").style.setProperty("opacity", 0, "");
22 } catch (error) {
23 var d3_element_prototype = d3_window.Element.prototype, d3_element_setAttribute = d3_element_prototype.setAttribute, d3_element_setAttributeNS = d3_element_prototype.setAttributeNS, d3_style_prototype = d3_window.CSSStyleDeclaration.prototype, d3_style_setProperty = d3_style_prototype.setProperty;
24 d3_element_prototype.setAttribute = function(name, value) {
25 d3_element_setAttribute.call(this, name, value + "");
26 };
27 d3_element_prototype.setAttributeNS = function(space, local, value) {
28 d3_element_setAttributeNS.call(this, space, local, value + "");
29 };
30 d3_style_prototype.setProperty = function(name, value, priority) {
31 d3_style_setProperty.call(this, name, value + "", priority);
32 };
33 }
34 d3.ascending = d3_ascending;
35 function d3_ascending(a, b) {
36 return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
37 }
38 d3.descending = function(a, b) {
39 return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN;
40 };
41 d3.min = function(array, f) {
42 var i = -1, n = array.length, a, b;
43 if (arguments.length === 1) {
44 while (++i < n && !((a = array[i]) != null && a <= a)) a = undefined;
45 while (++i < n) if ((b = array[i]) != null && a > b) a = b;
46 } else {
47 while (++i < n && !((a = f.call(array, array[i], i)) != null && a <= a)) a = undefined;
48 while (++i < n) if ((b = f.call(array, array[i], i)) != null && a > b) a = b;
49 }
50 return a;
51 };
52 d3.max = function(array, f) {
53 var i = -1, n = array.length, a, b;
54 if (arguments.length === 1) {
55 while (++i < n && !((a = array[i]) != null && a <= a)) a = undefined;
56 while (++i < n) if ((b = array[i]) != null && b > a) a = b;
57 } else {
58 while (++i < n && !((a = f.call(array, array[i], i)) != null && a <= a)) a = undefined;
59 while (++i < n) if ((b = f.call(array, array[i], i)) != null && b > a) a = b;
60 }
61 return a;
62 };
63 d3.extent = function(array, f) {
64 var i = -1, n = array.length, a, b, c;
65 if (arguments.length === 1) {
66 while (++i < n && !((a = c = array[i]) != null && a <= a)) a = c = undefined;
67 while (++i < n) if ((b = array[i]) != null) {
68 if (a > b) a = b;
69 if (c < b) c = b;
70 }
71 } else {
72 while (++i < n && !((a = c = f.call(array, array[i], i)) != null && a <= a)) a = undefined;
73 while (++i < n) if ((b = f.call(array, array[i], i)) != null) {
74 if (a > b) a = b;
75 if (c < b) c = b;
76 }
77 }
78 return [ a, c ];
79 };
80 function d3_number(x) {
81 return x === null ? NaN : +x;
82 }
83 function d3_numeric(x) {
84 return !isNaN(x);
85 }
86 d3.sum = function(array, f) {
87 var s = 0, n = array.length, a, i = -1;
88 if (arguments.length === 1) {
89 while (++i < n) if (d3_numeric(a = +array[i])) s += a;
90 } else {
91 while (++i < n) if (d3_numeric(a = +f.call(array, array[i], i))) s += a;
92 }
93 return s;
94 };
95 d3.mean = function(array, f) {
96 var s = 0, n = array.length, a, i = -1, j = n;
97 if (arguments.length === 1) {
98 while (++i < n) if (d3_numeric(a = d3_number(array[i]))) s += a; else --j;
99 } else {
100 while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) s += a; else --j;
101 }
102 return j ? s / j : undefined;
103 };
104 d3.quantile = function(values, p) {
105 var H = (values.length - 1) * p + 1, h = Math.floor(H), v = +values[h - 1], e = H - h;
106 return e ? v + e * (values[h] - v) : v;
107 };
108 d3.median = function(array, f) {
109 var numbers = [], n = array.length, a, i = -1;
110 if (arguments.length === 1) {
111 while (++i < n) if (d3_numeric(a = d3_number(array[i]))) numbers.push(a);
112 } else {
113 while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) numbers.push(a);
114 }
115 return numbers.length ? d3.quantile(numbers.sort(d3_ascending), .5) : undefined;
116 };
117 function d3_bisector(compare) {
118 return {
119 left: function(a, x, lo, hi) {
120 if (arguments.length < 3) lo = 0;
121 if (arguments.length < 4) hi = a.length;
122 while (lo < hi) {
123 var mid = lo + hi >>> 1;
124 if (compare(a[mid], x) < 0) lo = mid + 1; else hi = mid;
125 }
126 return lo;
127 },
128 right: function(a, x, lo, hi) {
129 if (arguments.length < 3) lo = 0;
130 if (arguments.length < 4) hi = a.length;
131 while (lo < hi) {
132 var mid = lo + hi >>> 1;
133 if (compare(a[mid], x) > 0) hi = mid; else lo = mid + 1;
134 }
135 return lo;
136 }
137 };
138 }
139 var d3_bisect = d3_bisector(d3_ascending);
140 d3.bisectLeft = d3_bisect.left;
141 d3.bisect = d3.bisectRight = d3_bisect.right;
142 d3.bisector = function(f) {
143 return d3_bisector(f.length === 1 ? function(d, x) {
144 return d3_ascending(f(d), x);
145 } : f);
146 };
147 d3.shuffle = function(array) {
148 var m = array.length, t, i;
149 while (m) {
150 i = Math.random() * m-- | 0;
151 t = array[m], array[m] = array[i], array[i] = t;
152 }
153 return array;
154 };
155 d3.permute = function(array, indexes) {
156 var i = indexes.length, permutes = new Array(i);
157 while (i--) permutes[i] = array[indexes[i]];
158 return permutes;
159 };
160 d3.pairs = function(array) {
161 var i = 0, n = array.length - 1, p0, p1 = array[0], pairs = new Array(n < 0 ? 0 : n);
162 while (i < n) pairs[i] = [ p0 = p1, p1 = array[++i] ];
163 return pairs;
164 };
165 d3.zip = function() {
166 if (!(n = arguments.length)) return [];
167 for (var i = -1, m = d3.min(arguments, d3_zipLength), zips = new Array(m); ++i < m; ) {
168 for (var j = -1, n, zip = zips[i] = new Array(n); ++j < n; ) {
169 zip[j] = arguments[j][i];
170 }
171 }
172 return zips;
173 };
174 function d3_zipLength(d) {
175 return d.length;
176 }
177 d3.transpose = function(matrix) {
178 return d3.zip.apply(d3, matrix);
179 };
180 d3.keys = function(map) {
181 var keys = [];
182 for (var key in map) keys.push(key);
183 return keys;
184 };
185 d3.values = function(map) {
186 var values = [];
187 for (var key in map) values.push(map[key]);
188 return values;
189 };
190 d3.entries = function(map) {
191 var entries = [];
192 for (var key in map) entries.push({
193 key: key,
194 value: map[key]
195 });
196 return entries;
197 };
198 d3.merge = function(arrays) {
199 var n = arrays.length, m, i = -1, j = 0, merged, array;
200 while (++i < n) j += arrays[i].length;
201 merged = new Array(j);
202 while (--n >= 0) {
203 array = arrays[n];
204 m = array.length;
205 while (--m >= 0) {
206 merged[--j] = array[m];
207 }
208 }
209 return merged;
210 };
211 var abs = Math.abs;
212 d3.range = function(start, stop, step) {
213 if (arguments.length < 3) {
214 step = 1;
215 if (arguments.length < 2) {
216 stop = start;
217 start = 0;
218 }
219 }
220 if ((stop - start) / step === Infinity) throw new Error("infinite range");
221 var range = [], k = d3_range_integerScale(abs(step)), i = -1, j;
222 start *= k, stop *= k, step *= k;
223 if (step < 0) while ((j = start + step * ++i) > stop) range.push(j / k); else while ((j = start + step * ++i) < stop) range.push(j / k);
224 return range;
225 };
226 function d3_range_integerScale(x) {
227 var k = 1;
228 while (x * k % 1) k *= 10;
229 return k;
230 }
231 function d3_class(ctor, properties) {
232 for (var key in properties) {
233 Object.defineProperty(ctor.prototype, key, {
234 value: properties[key],
235 enumerable: false
236 });
237 }
238 }
239 d3.map = function(object) {
240 var map = new d3_Map();
241 if (object instanceof d3_Map) object.forEach(function(key, value) {
242 map.set(key, value);
243 }); else for (var key in object) map.set(key, object[key]);
244 return map;
245 };
246 function d3_Map() {
247 this._ = Object.create(null);
248 }
249 var d3_map_proto = "__proto__", d3_map_zero = "\x00";
250 d3_class(d3_Map, {
251 has: d3_map_has,
252 get: function(key) {
253 return this._[d3_map_escape(key)];
254 },
255 set: function(key, value) {
256 return this._[d3_map_escape(key)] = value;
257 },
258 remove: d3_map_remove,
259 keys: d3_map_keys,
260 values: function() {
261 var values = [];
262 for (var key in this._) values.push(this._[key]);
263 return values;
264 },
265 entries: function() {
266 var entries = [];
267 for (var key in this._) entries.push({
268 key: d3_map_unescape(key),
269 value: this._[key]
270 });
271 return entries;
272 },
273 size: d3_map_size,
274 empty: d3_map_empty,
275 forEach: function(f) {
276 for (var key in this._) f.call(this, d3_map_unescape(key), this._[key]);
277 }
278 });
279 function d3_map_escape(key) {
280 return (key += "") === d3_map_proto || key[0] === d3_map_zero ? d3_map_zero + key : key;
281 }
282 function d3_map_unescape(key) {
283 return (key += "")[0] === d3_map_zero ? key.slice(1) : key;
284 }
285 function d3_map_has(key) {
286 return d3_map_escape(key) in this._;
287 }
288 function d3_map_remove(key) {
289 return (key = d3_map_escape(key)) in this._ && delete this._[key];
290 }
291 function d3_map_keys() {
292 var keys = [];
293 for (var key in this._) keys.push(d3_map_unescape(key));
294 return keys;
295 }
296 function d3_map_size() {
297 var size = 0;
298 for (var key in this._) ++size;
299 return size;
300 }
301 function d3_map_empty() {
302 for (var key in this._) return false;
303 return true;
304 }
305 d3.nest = function() {
306 var nest = {}, keys = [], sortKeys = [], sortValues, rollup;
307 function map(mapType, array, depth) {
308 if (depth >= keys.length) return rollup ? rollup.call(nest, array) : sortValues ? array.sort(sortValues) : array;
309 var i = -1, n = array.length, key = keys[depth++], keyValue, object, setter, valuesByKey = new d3_Map(), values;
310 while (++i < n) {
311 if (values = valuesByKey.get(keyValue = key(object = array[i]))) {
312 values.push(object);
313 } else {
314 valuesByKey.set(keyValue, [ object ]);
315 }
316 }
317 if (mapType) {
318 object = mapType();
319 setter = function(keyValue, values) {
320 object.set(keyValue, map(mapType, values, depth));
321 };
322 } else {
323 object = {};
324 setter = function(keyValue, values) {
325 object[keyValue] = map(mapType, values, depth);
326 };
327 }
328 valuesByKey.forEach(setter);
329 return object;
330 }
331 function entries(map, depth) {
332 if (depth >= keys.length) return map;
333 var array = [], sortKey = sortKeys[depth++];
334 map.forEach(function(key, keyMap) {
335 array.push({
336 key: key,
337 values: entries(keyMap, depth)
338 });
339 });
340 return sortKey ? array.sort(function(a, b) {
341 return sortKey(a.key, b.key);
342 }) : array;
343 }
344 nest.map = function(array, mapType) {
345 return map(mapType, array, 0);
346 };
347 nest.entries = function(array) {
348 return entries(map(d3.map, array, 0), 0);
349 };
350 nest.key = function(d) {
351 keys.push(d);
352 return nest;
353 };
354 nest.sortKeys = function(order) {
355 sortKeys[keys.length - 1] = order;
356 return nest;
357 };
358 nest.sortValues = function(order) {
359 sortValues = order;
360 return nest;
361 };
362 nest.rollup = function(f) {
363 rollup = f;
364 return nest;
365 };
366 return nest;
367 };
368 d3.set = function(array) {
369 var set = new d3_Set();
370 if (array) for (var i = 0, n = array.length; i < n; ++i) set.add(array[i]);
371 return set;
372 };
373 function d3_Set() {
374 this._ = Object.create(null);
375 }
376 d3_class(d3_Set, {
377 has: d3_map_has,
378 add: function(key) {
379 this._[d3_map_escape(key += "")] = true;
380 return key;
381 },
382 remove: d3_map_remove,
383 values: d3_map_keys,
384 size: d3_map_size,
385 empty: d3_map_empty,
386 forEach: function(f) {
387 for (var key in this._) f.call(this, d3_map_unescape(key));
388 }
389 });
390 d3.behavior = {};
391 d3.rebind = function(target, source) {
392 var i = 1, n = arguments.length, method;
393 while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]);
394 return target;
395 };
396 function d3_rebind(target, source, method) {
397 return function() {
398 var value = method.apply(source, arguments);
399 return value === source ? target : value;
400 };
401 }
402 function d3_vendorSymbol(object, name) {
403 if (name in object) return name;
404 name = name.charAt(0).toUpperCase() + name.slice(1);
405 for (var i = 0, n = d3_vendorPrefixes.length; i < n; ++i) {
406 var prefixName = d3_vendorPrefixes[i] + name;
407 if (prefixName in object) return prefixName;
408 }
409 }
410 var d3_vendorPrefixes = [ "webkit", "ms", "moz", "Moz", "o", "O" ];
411 function d3_noop() {}
412 d3.dispatch = function() {
413 var dispatch = new d3_dispatch(), i = -1, n = arguments.length;
414 while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch);
415 return dispatch;
416 };
417 function d3_dispatch() {}
418 d3_dispatch.prototype.on = function(type, listener) {
419 var i = type.indexOf("."), name = "";
420 if (i >= 0) {
421 name = type.slice(i + 1);
422 type = type.slice(0, i);
423 }
424 if (type) return arguments.length < 2 ? this[type].on(name) : this[type].on(name, listener);
425 if (arguments.length === 2) {
426 if (listener == null) for (type in this) {
427 if (this.hasOwnProperty(type)) this[type].on(name, null);
428 }
429 return this;
430 }
431 };
432 function d3_dispatch_event(dispatch) {
433 var listeners = [], listenerByName = new d3_Map();
434 function event() {
435 var z = listeners, i = -1, n = z.length, l;
436 while (++i < n) if (l = z[i].on) l.apply(this, arguments);
437 return dispatch;
438 }
439 event.on = function(name, listener) {
440 var l = listenerByName.get(name), i;
441 if (arguments.length < 2) return l && l.on;
442 if (l) {
443 l.on = null;
444 listeners = listeners.slice(0, i = listeners.indexOf(l)).concat(listeners.slice(i + 1));
445 listenerByName.remove(name);
446 }
447 if (listener) listeners.push(listenerByName.set(name, {
448 on: listener
449 }));
450 return dispatch;
451 };
452 return event;
453 }
454 d3.event = null;
455 function d3_eventPreventDefault() {
456 d3.event.preventDefault();
457 }
458 function d3_eventSource() {
459 var e = d3.event, s;
460 while (s = e.sourceEvent) e = s;
461 return e;
462 }
463 function d3_eventDispatch(target) {
464 var dispatch = new d3_dispatch(), i = 0, n = arguments.length;
465 while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch);
466 dispatch.of = function(thiz, argumentz) {
467 return function(e1) {
468 try {
469 var e0 = e1.sourceEvent = d3.event;
470 e1.target = target;
471 d3.event = e1;
472 dispatch[e1.type].apply(thiz, argumentz);
473 } finally {
474 d3.event = e0;
475 }
476 };
477 };
478 return dispatch;
479 }
480 d3.requote = function(s) {
481 return s.replace(d3_requote_re, "\\$&");
482 };
483 var d3_requote_re = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
484 var d3_subclass = {}.__proto__ ? function(object, prototype) {
485 object.__proto__ = prototype;
486 } : function(object, prototype) {
487 for (var property in prototype) object[property] = prototype[property];
488 };
489 function d3_selection(groups) {
490 d3_subclass(groups, d3_selectionPrototype);
491 return groups;
492 }
493 var d3_select = function(s, n) {
494 return n.querySelector(s);
495 }, d3_selectAll = function(s, n) {
496 return n.querySelectorAll(s);
497 }, d3_selectMatcher = d3_documentElement.matches || d3_documentElement[d3_vendorSymbol(d3_documentElement, "matchesSelector")], d3_selectMatches = function(n, s) {
498 return d3_selectMatcher.call(n, s);
499 };
500 if (typeof Sizzle === "function") {
501 d3_select = function(s, n) {
502 return Sizzle(s, n)[0] || null;
503 };
504 d3_selectAll = Sizzle;
505 d3_selectMatches = Sizzle.matchesSelector;
506 }
507 d3.selection = function() {
508 return d3_selectionRoot;
509 };
510 var d3_selectionPrototype = d3.selection.prototype = [];
511 d3_selectionPrototype.select = function(selector) {
512 var subgroups = [], subgroup, subnode, group, node;
513 selector = d3_selection_selector(selector);
514 for (var j = -1, m = this.length; ++j < m; ) {
515 subgroups.push(subgroup = []);
516 subgroup.parentNode = (group = this[j]).parentNode;
517 for (var i = -1, n = group.length; ++i < n; ) {
518 if (node = group[i]) {
519 subgroup.push(subnode = selector.call(node, node.__data__, i, j));
520 if (subnode && "__data__" in node) subnode.__data__ = node.__data__;
521 } else {
522 subgroup.push(null);
523 }
524 }
525 }
526 return d3_selection(subgroups);
527 };
528 function d3_selection_selector(selector) {
529 return typeof selector === "function" ? selector : function() {
530 return d3_select(selector, this);
531 };
532 }
533 d3_selectionPrototype.selectAll = function(selector) {
534 var subgroups = [], subgroup, node;
535 selector = d3_selection_selectorAll(selector);
536 for (var j = -1, m = this.length; ++j < m; ) {
537 for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
538 if (node = group[i]) {
539 subgroups.push(subgroup = d3_array(selector.call(node, node.__data__, i, j)));
540 subgroup.parentNode = node;
541 }
542 }
543 }
544 return d3_selection(subgroups);
545 };
546 function d3_selection_selectorAll(selector) {
547 return typeof selector === "function" ? selector : function() {
548 return d3_selectAll(selector, this);
549 };
550 }
551 var d3_nsPrefix = {
552 svg: "http://www.w3.org/2000/svg",
553 xhtml: "http://www.w3.org/1999/xhtml",
554 xlink: "http://www.w3.org/1999/xlink",
555 xml: "http://www.w3.org/XML/1998/namespace",
556 xmlns: "http://www.w3.org/2000/xmlns/"
557 };
558 d3.ns = {
559 prefix: d3_nsPrefix,
560 qualify: function(name) {
561 var i = name.indexOf(":"), prefix = name;
562 if (i >= 0) {
563 prefix = name.slice(0, i);
564 name = name.slice(i + 1);
565 }
566 return d3_nsPrefix.hasOwnProperty(prefix) ? {
567 space: d3_nsPrefix[prefix],
568 local: name
569 } : name;
570 }
571 };
572 d3_selectionPrototype.attr = function(name, value) {
573 if (arguments.length < 2) {
574 if (typeof name === "string") {
575 var node = this.node();
576 name = d3.ns.qualify(name);
577 return name.local ? node.getAttributeNS(name.space, name.local) : node.getAttribute(name);
578 }
579 for (value in name) this.each(d3_selection_attr(value, name[value]));
580 return this;
581 }
582 return this.each(d3_selection_attr(name, value));
583 };
584 function d3_selection_attr(name, value) {
585 name = d3.ns.qualify(name);
586 function attrNull() {
587 this.removeAttribute(name);
588 }
589 function attrNullNS() {
590 this.removeAttributeNS(name.space, name.local);
591 }
592 function attrConstant() {
593 this.setAttribute(name, value);
594 }
595 function attrConstantNS() {
596 this.setAttributeNS(name.space, name.local, value);
597 }
598 function attrFunction() {
599 var x = value.apply(this, arguments);
600 if (x == null) this.removeAttribute(name); else this.setAttribute(name, x);
601 }
602 function attrFunctionNS() {
603 var x = value.apply(this, arguments);
604 if (x == null) this.removeAttributeNS(name.space, name.local); else this.setAttributeNS(name.space, name.local, x);
605 }
606 return value == null ? name.local ? attrNullNS : attrNull : typeof value === "function" ? name.local ? attrFunctionNS : attrFunction : name.local ? attrConstantNS : attrConstant;
607 }
608 function d3_collapse(s) {
609 return s.trim().replace(/\s+/g, " ");
610 }
611 d3_selectionPrototype.classed = function(name, value) {
612 if (arguments.length < 2) {
613 if (typeof name === "string") {
614 var node = this.node(), n = (name = d3_selection_classes(name)).length, i = -1;
615 if (value = node.classList) {
616 while (++i < n) if (!value.contains(name[i])) return false;
617 } else {
618 value = node.getAttribute("class");
619 while (++i < n) if (!d3_selection_classedRe(name[i]).test(value)) return false;
620 }
621 return true;
622 }
623 for (value in name) this.each(d3_selection_classed(value, name[value]));
624 return this;
625 }
626 return this.each(d3_selection_classed(name, value));
627 };
628 function d3_selection_classedRe(name) {
629 return new RegExp("(?:^|\\s+)" + d3.requote(name) + "(?:\\s+|$)", "g");
630 }
631 function d3_selection_classes(name) {
632 return (name + "").trim().split(/^|\s+/);
633 }
634 function d3_selection_classed(name, value) {
635 name = d3_selection_classes(name).map(d3_selection_classedName);
636 var n = name.length;
637 function classedConstant() {
638 var i = -1;
639 while (++i < n) name[i](this, value);
640 }
641 function classedFunction() {
642 var i = -1, x = value.apply(this, arguments);
643 while (++i < n) name[i](this, x);
644 }
645 return typeof value === "function" ? classedFunction : classedConstant;
646 }
647 function d3_selection_classedName(name) {
648 var re = d3_selection_classedRe(name);
649 return function(node, value) {
650 if (c = node.classList) return value ? c.add(name) : c.remove(name);
651 var c = node.getAttribute("class") || "";
652 if (value) {
653 re.lastIndex = 0;
654 if (!re.test(c)) node.setAttribute("class", d3_collapse(c + " " + name));
655 } else {
656 node.setAttribute("class", d3_collapse(c.replace(re, " ")));
657 }
658 };
659 }
660 d3_selectionPrototype.style = function(name, value, priority) {
661 var n = arguments.length;
662 if (n < 3) {
663 if (typeof name !== "string") {
664 if (n < 2) value = "";
665 for (priority in name) this.each(d3_selection_style(priority, name[priority], value));
666 return this;
667 }
668 if (n < 2) return d3_window.getComputedStyle(this.node(), null).getPropertyValue(name);
669 priority = "";
670 }
671 return this.each(d3_selection_style(name, value, priority));
672 };
673 function d3_selection_style(name, value, priority) {
674 function styleNull() {
675 this.style.removeProperty(name);
676 }
677 function styleConstant() {
678 this.style.setProperty(name, value, priority);
679 }
680 function styleFunction() {
681 var x = value.apply(this, arguments);
682 if (x == null) this.style.removeProperty(name); else this.style.setProperty(name, x, priority);
683 }
684 return value == null ? styleNull : typeof value === "function" ? styleFunction : styleConstant;
685 }
686 d3_selectionPrototype.property = function(name, value) {
687 if (arguments.length < 2) {
688 if (typeof name === "string") return this.node()[name];
689 for (value in name) this.each(d3_selection_property(value, name[value]));
690 return this;
691 }
692 return this.each(d3_selection_property(name, value));
693 };
694 function d3_selection_property(name, value) {
695 function propertyNull() {
696 delete this[name];
697 }
698 function propertyConstant() {
699 this[name] = value;
700 }
701 function propertyFunction() {
702 var x = value.apply(this, arguments);
703 if (x == null) delete this[name]; else this[name] = x;
704 }
705 return value == null ? propertyNull : typeof value === "function" ? propertyFunction : propertyConstant;
706 }
707 d3_selectionPrototype.text = function(value) {
708 return arguments.length ? this.each(typeof value === "function" ? function() {
709 var v = value.apply(this, arguments);
710 this.textContent = v == null ? "" : v;
711 } : value == null ? function() {
712 this.textContent = "";
713 } : function() {
714 this.textContent = value;
715 }) : this.node().textContent;
716 };
717 d3_selectionPrototype.html = function(value) {
718 return arguments.length ? this.each(typeof value === "function" ? function() {
719 var v = value.apply(this, arguments);
720 this.innerHTML = v == null ? "" : v;
721 } : value == null ? function() {
722 this.innerHTML = "";
723 } : function() {
724 this.innerHTML = value;
725 }) : this.node().innerHTML;
726 };
727 d3_selectionPrototype.append = function(name) {
728 name = d3_selection_creator(name);
729 return this.select(function() {
730 return this.appendChild(name.apply(this, arguments));
731 });
732 };
733 function d3_selection_creator(name) {
734 return typeof name === "function" ? name : (name = d3.ns.qualify(name)).local ? function() {
735 return this.ownerDocument.createElementNS(name.space, name.local);
736 } : function() {
737 return this.ownerDocument.createElementNS(this.namespaceURI, name);
738 };
739 }
740 d3_selectionPrototype.insert = function(name, before) {
741 name = d3_selection_creator(name);
742 before = d3_selection_selector(before);
743 return this.select(function() {
744 return this.insertBefore(name.apply(this, arguments), before.apply(this, arguments) || null);
745 });
746 };
747 d3_selectionPrototype.remove = function() {
748 return this.each(function() {
749 var parent = this.parentNode;
750 if (parent) parent.removeChild(this);
751 });
752 };
753 d3_selectionPrototype.data = function(value, key) {
754 var i = -1, n = this.length, group, node;
755 if (!arguments.length) {
756 value = new Array(n = (group = this[0]).length);
757 while (++i < n) {
758 if (node = group[i]) {
759 value[i] = node.__data__;
760 }
761 }
762 return value;
763 }
764 function bind(group, groupData) {
765 var i, n = group.length, m = groupData.length, n0 = Math.min(n, m), updateNodes = new Array(m), enterNodes = new Array(m), exitNodes = new Array(n), node, nodeData;
766 if (key) {
767 var nodeByKeyValue = new d3_Map(), keyValues = new Array(n), keyValue;
768 for (i = -1; ++i < n; ) {
769 if (nodeByKeyValue.has(keyValue = key.call(node = group[i], node.__data__, i))) {
770 exitNodes[i] = node;
771 } else {
772 nodeByKeyValue.set(keyValue, node);
773 }
774 keyValues[i] = keyValue;
775 }
776 for (i = -1; ++i < m; ) {
777 if (!(node = nodeByKeyValue.get(keyValue = key.call(groupData, nodeData = groupData[i], i)))) {
778 enterNodes[i] = d3_selection_dataNode(nodeData);
779 } else if (node !== true) {
780 updateNodes[i] = node;
781 node.__data__ = nodeData;
782 }
783 nodeByKeyValue.set(keyValue, true);
784 }
785 for (i = -1; ++i < n; ) {
786 if (nodeByKeyValue.get(keyValues[i]) !== true) {
787 exitNodes[i] = group[i];
788 }
789 }
790 } else {
791 for (i = -1; ++i < n0; ) {
792 node = group[i];
793 nodeData = groupData[i];
794 if (node) {
795 node.__data__ = nodeData;
796 updateNodes[i] = node;
797 } else {
798 enterNodes[i] = d3_selection_dataNode(nodeData);
799 }
800 }
801 for (;i < m; ++i) {
802 enterNodes[i] = d3_selection_dataNode(groupData[i]);
803 }
804 for (;i < n; ++i) {
805 exitNodes[i] = group[i];
806 }
807 }
808 enterNodes.update = updateNodes;
809 enterNodes.parentNode = updateNodes.parentNode = exitNodes.parentNode = group.parentNode;
810 enter.push(enterNodes);
811 update.push(updateNodes);
812 exit.push(exitNodes);
813 }
814 var enter = d3_selection_enter([]), update = d3_selection([]), exit = d3_selection([]);
815 if (typeof value === "function") {
816 while (++i < n) {
817 bind(group = this[i], value.call(group, group.parentNode.__data__, i));
818 }
819 } else {
820 while (++i < n) {
821 bind(group = this[i], value);
822 }
823 }
824 update.enter = function() {
825 return enter;
826 };
827 update.exit = function() {
828 return exit;
829 };
830 return update;
831 };
832 function d3_selection_dataNode(data) {
833 return {
834 __data__: data
835 };
836 }
837 d3_selectionPrototype.datum = function(value) {
838 return arguments.length ? this.property("__data__", value) : this.property("__data__");
839 };
840 d3_selectionPrototype.filter = function(filter) {
841 var subgroups = [], subgroup, group, node;
842 if (typeof filter !== "function") filter = d3_selection_filter(filter);
843 for (var j = 0, m = this.length; j < m; j++) {
844 subgroups.push(subgroup = []);
845 subgroup.parentNode = (group = this[j]).parentNode;
846 for (var i = 0, n = group.length; i < n; i++) {
847 if ((node = group[i]) && filter.call(node, node.__data__, i, j)) {
848 subgroup.push(node);
849 }
850 }
851 }
852 return d3_selection(subgroups);
853 };
854 function d3_selection_filter(selector) {
855 return function() {
856 return d3_selectMatches(this, selector);
857 };
858 }
859 d3_selectionPrototype.order = function() {
860 for (var j = -1, m = this.length; ++j < m; ) {
861 for (var group = this[j], i = group.length - 1, next = group[i], node; --i >= 0; ) {
862 if (node = group[i]) {
863 if (next && next !== node.nextSibling) next.parentNode.insertBefore(node, next);
864 next = node;
865 }
866 }
867 }
868 return this;
869 };
870 d3_selectionPrototype.sort = function(comparator) {
871 comparator = d3_selection_sortComparator.apply(this, arguments);
872 for (var j = -1, m = this.length; ++j < m; ) this[j].sort(comparator);
873 return this.order();
874 };
875 function d3_selection_sortComparator(comparator) {
876 if (!arguments.length) comparator = d3_ascending;
877 return function(a, b) {
878 return a && b ? comparator(a.__data__, b.__data__) : !a - !b;
879 };
880 }
881 d3_selectionPrototype.each = function(callback) {
882 return d3_selection_each(this, function(node, i, j) {
883 callback.call(node, node.__data__, i, j);
884 });
885 };
886 function d3_selection_each(groups, callback) {
887 for (var j = 0, m = groups.length; j < m; j++) {
888 for (var group = groups[j], i = 0, n = group.length, node; i < n; i++) {
889 if (node = group[i]) callback(node, i, j);
890 }
891 }
892 return groups;
893 }
894 d3_selectionPrototype.call = function(callback) {
895 var args = d3_array(arguments);
896 callback.apply(args[0] = this, args);
897 return this;
898 };
899 d3_selectionPrototype.empty = function() {
900 return !this.node();
901 };
902 d3_selectionPrototype.node = function() {
903 for (var j = 0, m = this.length; j < m; j++) {
904 for (var group = this[j], i = 0, n = group.length; i < n; i++) {
905 var node = group[i];
906 if (node) return node;
907 }
908 }
909 return null;
910 };
911 d3_selectionPrototype.size = function() {
912 var n = 0;
913 d3_selection_each(this, function() {
914 ++n;
915 });
916 return n;
917 };
918 function d3_selection_enter(selection) {
919 d3_subclass(selection, d3_selection_enterPrototype);
920 return selection;
921 }
922 var d3_selection_enterPrototype = [];
923 d3.selection.enter = d3_selection_enter;
924 d3.selection.enter.prototype = d3_selection_enterPrototype;
925 d3_selection_enterPrototype.append = d3_selectionPrototype.append;
926 d3_selection_enterPrototype.empty = d3_selectionPrototype.empty;
927 d3_selection_enterPrototype.node = d3_selectionPrototype.node;
928 d3_selection_enterPrototype.call = d3_selectionPrototype.call;
929 d3_selection_enterPrototype.size = d3_selectionPrototype.size;
930 d3_selection_enterPrototype.select = function(selector) {
931 var subgroups = [], subgroup, subnode, upgroup, group, node;
932 for (var j = -1, m = this.length; ++j < m; ) {
933 upgroup = (group = this[j]).update;
934 subgroups.push(subgroup = []);
935 subgroup.parentNode = group.parentNode;
936 for (var i = -1, n = group.length; ++i < n; ) {
937 if (node = group[i]) {
938 subgroup.push(upgroup[i] = subnode = selector.call(group.parentNode, node.__data__, i, j));
939 subnode.__data__ = node.__data__;
940 } else {
941 subgroup.push(null);
942 }
943 }
944 }
945 return d3_selection(subgroups);
946 };
947 d3_selection_enterPrototype.insert = function(name, before) {
948 if (arguments.length < 2) before = d3_selection_enterInsertBefore(this);
949 return d3_selectionPrototype.insert.call(this, name, before);
950 };
951 function d3_selection_enterInsertBefore(enter) {
952 var i0, j0;
953 return function(d, i, j) {
954 var group = enter[j].update, n = group.length, node;
955 if (j != j0) j0 = j, i0 = 0;
956 if (i >= i0) i0 = i + 1;
957 while (!(node = group[i0]) && ++i0 < n) ;
958 return node;
959 };
960 }
961 d3_selectionPrototype.transition = function() {
962 var id = d3_transitionInheritId || ++d3_transitionId, subgroups = [], subgroup, node, transition = d3_transitionInherit || {
963 time: Date.now(),
964 ease: d3_ease_cubicInOut,
965 delay: 0,
966 duration: 250
967 };
968 for (var j = -1, m = this.length; ++j < m; ) {
969 subgroups.push(subgroup = []);
970 for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
971 if (node = group[i]) d3_transitionNode(node, i, id, transition);
972 subgroup.push(node);
973 }
974 }
975 return d3_transition(subgroups, id);
976 };
977 d3_selectionPrototype.interrupt = function() {
978 return this.each(d3_selection_interrupt);
979 };
980 function d3_selection_interrupt() {
981 var lock = this.__transition__;
982 if (lock) ++lock.active;
983 }
984 d3.select = function(node) {
985 var group = [ typeof node === "string" ? d3_select(node, d3_document) : node ];
986 group.parentNode = d3_documentElement;
987 return d3_selection([ group ]);
988 };
989 d3.selectAll = function(nodes) {
990 var group = d3_array(typeof nodes === "string" ? d3_selectAll(nodes, d3_document) : nodes);
991 group.parentNode = d3_documentElement;
992 return d3_selection([ group ]);
993 };
994 var d3_selectionRoot = d3.select(d3_documentElement);
995 d3_selectionPrototype.on = function(type, listener, capture) {
996 var n = arguments.length;
997 if (n < 3) {
998 if (typeof type !== "string") {
999 if (n < 2) listener = false;
1000 for (capture in type) this.each(d3_selection_on(capture, type[capture], listener));
1001 return this;
1002 }
1003 if (n < 2) return (n = this.node()["__on" + type]) && n._;
1004 capture = false;
1005 }
1006 return this.each(d3_selection_on(type, listener, capture));
1007 };
1008 function d3_selection_on(type, listener, capture) {
1009 var name = "__on" + type, i = type.indexOf("."), wrap = d3_selection_onListener;
1010 if (i > 0) type = type.slice(0, i);
1011 var filter = d3_selection_onFilters.get(type);
1012 if (filter) type = filter, wrap = d3_selection_onFilter;
1013 function onRemove() {
1014 var l = this[name];
1015 if (l) {
1016 this.removeEventListener(type, l, l.$);
1017 delete this[name];
1018 }
1019 }
1020 function onAdd() {
1021 var l = wrap(listener, d3_array(arguments));
1022 onRemove.call(this);
1023 this.addEventListener(type, this[name] = l, l.$ = capture);
1024 l._ = listener;
1025 }
1026 function removeAll() {
1027 var re = new RegExp("^__on([^.]+)" + d3.requote(type) + "$"), match;
1028 for (var name in this) {
1029 if (match = name.match(re)) {
1030 var l = this[name];
1031 this.removeEventListener(match[1], l, l.$);
1032 delete this[name];
1033 }
1034 }
1035 }
1036 return i ? listener ? onAdd : onRemove : listener ? d3_noop : removeAll;
1037 }
1038 var d3_selection_onFilters = d3.map({
1039 mouseenter: "mouseover",
1040 mouseleave: "mouseout"
1041 });
1042 d3_selection_onFilters.forEach(function(k) {
1043 if ("on" + k in d3_document) d3_selection_onFilters.remove(k);
1044 });
1045 function d3_selection_onListener(listener, argumentz) {
1046 return function(e) {
1047 var o = d3.event;
1048 d3.event = e;
1049 argumentz[0] = this.__data__;
1050 try {
1051 listener.apply(this, argumentz);
1052 } finally {
1053 d3.event = o;
1054 }
1055 };
1056 }
1057 function d3_selection_onFilter(listener, argumentz) {
1058 var l = d3_selection_onListener(listener, argumentz);
1059 return function(e) {
1060 var target = this, related = e.relatedTarget;
1061 if (!related || related !== target && !(related.compareDocumentPosition(target) & 8)) {
1062 l.call(target, e);
1063 }
1064 };
1065 }
1066 var d3_event_dragSelect = "onselectstart" in d3_document ? null : d3_vendorSymbol(d3_documentElement.style, "userSelect"), d3_event_dragId = 0;
1067 function d3_event_dragSuppress() {
1068 var name = ".dragsuppress-" + ++d3_event_dragId, click = "click" + name, w = d3.select(d3_window).on("touchmove" + name, d3_eventPreventDefault).on("dragstart" + name, d3_eventPreventDefault).on("selectstart" + name, d3_eventPreventDefault);
1069 if (d3_event_dragSelect) {
1070 var style = d3_documentElement.style, select = style[d3_event_dragSelect];
1071 style[d3_event_dragSelect] = "none";
1072 }
1073 return function(suppressClick) {
1074 w.on(name, null);
1075 if (d3_event_dragSelect) style[d3_event_dragSelect] = select;
1076 if (suppressClick) {
1077 function off() {
1078 w.on(click, null);
1079 }
1080 w.on(click, function() {
1081 d3_eventPreventDefault();
1082 off();
1083 }, true);
1084 setTimeout(off, 0);
1085 }
1086 };
1087 }
1088 d3.mouse = function(container) {
1089 return d3_mousePoint(container, d3_eventSource());
1090 };
1091 var d3_mouse_bug44083 = /WebKit/.test(d3_window.navigator.userAgent) ? -1 : 0;
1092 function d3_mousePoint(container, e) {
1093 if (e.changedTouches) e = e.changedTouches[0];
1094 var svg = container.ownerSVGElement || container;
1095 if (svg.createSVGPoint) {
1096 var point = svg.createSVGPoint();
1097 if (d3_mouse_bug44083 < 0 && (d3_window.scrollX || d3_window.scrollY)) {
1098 svg = d3.select("body").append("svg").style({
1099 position: "absolute",
1100 top: 0,
1101 left: 0,
1102 margin: 0,
1103 padding: 0,
1104 border: "none"
1105 }, "important");
1106 var ctm = svg[0][0].getScreenCTM();
1107 d3_mouse_bug44083 = !(ctm.f || ctm.e);
1108 svg.remove();
1109 }
1110 if (d3_mouse_bug44083) point.x = e.pageX, point.y = e.pageY; else point.x = e.clientX,
1111 point.y = e.clientY;
1112 point = point.matrixTransform(container.getScreenCTM().inverse());
1113 return [ point.x, point.y ];
1114 }
1115 var rect = container.getBoundingClientRect();
1116 return [ e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop ];
1117 }
1118 d3.touch = function(container, touches, identifier) {
1119 if (arguments.length < 3) identifier = touches, touches = d3_eventSource().changedTouches;
1120 if (touches) for (var i = 0, n = touches.length, touch; i < n; ++i) {
1121 if ((touch = touches[i]).identifier === identifier) {
1122 return d3_mousePoint(container, touch);
1123 }
1124 }
1125 };
1126 d3.behavior.drag = function() {
1127 var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"), origin = null, mousedown = dragstart(d3_noop, d3.mouse, d3_behavior_dragMouseSubject, "mousemove", "mouseup"), touchstart = dragstart(d3_behavior_dragTouchId, d3.touch, d3_behavior_dragTouchSubject, "touchmove", "touchend");
1128 function drag() {
1129 this.on("mousedown.drag", mousedown).on("touchstart.drag", touchstart);
1130 }
1131 function dragstart(id, position, subject, move, end) {
1132 return function() {
1133 var that = this, target = d3.event.target, parent = that.parentNode, dispatch = event.of(that, arguments), dragged = 0, dragId = id(), dragName = ".drag" + (dragId == null ? "" : "-" + dragId), dragOffset, dragSubject = d3.select(subject()).on(move + dragName, moved).on(end + dragName, ended), dragRestore = d3_event_dragSuppress(), position0 = position(parent, dragId);
1134 if (origin) {
1135 dragOffset = origin.apply(that, arguments);
1136 dragOffset = [ dragOffset.x - position0[0], dragOffset.y - position0[1] ];
1137 } else {
1138 dragOffset = [ 0, 0 ];
1139 }
1140 dispatch({
1141 type: "dragstart"
1142 });
1143 function moved() {
1144 var position1 = position(parent, dragId), dx, dy;
1145 if (!position1) return;
1146 dx = position1[0] - position0[0];
1147 dy = position1[1] - position0[1];
1148 dragged |= dx | dy;
1149 position0 = position1;
1150 dispatch({
1151 type: "drag",
1152 x: position1[0] + dragOffset[0],
1153 y: position1[1] + dragOffset[1],
1154 dx: dx,
1155 dy: dy
1156 });
1157 }
1158 function ended() {
1159 if (!position(parent, dragId)) return;
1160 dragSubject.on(move + dragName, null).on(end + dragName, null);
1161 dragRestore(dragged && d3.event.target === target);
1162 dispatch({
1163 type: "dragend"
1164 });
1165 }
1166 };
1167 }
1168 drag.origin = function(x) {
1169 if (!arguments.length) return origin;
1170 origin = x;
1171 return drag;
1172 };
1173 return d3.rebind(drag, event, "on");
1174 };
1175 function d3_behavior_dragTouchId() {
1176 return d3.event.changedTouches[0].identifier;
1177 }
1178 function d3_behavior_dragTouchSubject() {
1179 return d3.event.target;
1180 }
1181 function d3_behavior_dragMouseSubject() {
1182 return d3_window;
1183 }
1184 d3.touches = function(container, touches) {
1185 if (arguments.length < 2) touches = d3_eventSource().touches;
1186 return touches ? d3_array(touches).map(function(touch) {
1187 var point = d3_mousePoint(container, touch);
1188 point.identifier = touch.identifier;
1189 return point;
1190 }) : [];
1191 };
1192 var π = Math.PI, τ = 2 * π, halfπ = π / 2, ε = 1e-6, ε2 = ε * ε, d3_radians = π / 180, d3_degrees = 180 / π;
1193 function d3_sgn(x) {
1194 return x > 0 ? 1 : x < 0 ? -1 : 0;
1195 }
1196 function d3_cross2d(a, b, c) {
1197 return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]);
1198 }
1199 function d3_acos(x) {
1200 return x > 1 ? 0 : x < -1 ? π : Math.acos(x);
1201 }
1202 function d3_asin(x) {
1203 return x > 1 ? halfπ : x < -1 ? -halfπ : Math.asin(x);
1204 }
1205 function d3_sinh(x) {
1206 return ((x = Math.exp(x)) - 1 / x) / 2;
1207 }
1208 function d3_cosh(x) {
1209 return ((x = Math.exp(x)) + 1 / x) / 2;
1210 }
1211 function d3_tanh(x) {
1212 return ((x = Math.exp(2 * x)) - 1) / (x + 1);
1213 }
1214 function d3_haversin(x) {
1215 return (x = Math.sin(x / 2)) * x;
1216 }
1217 var ρ = Math.SQRT2, ρ2 = 2, ρ4 = 4;
1218 d3.interpolateZoom = function(p0, p1) {
1219 var ux0 = p0[0], uy0 = p0[1], w0 = p0[2], ux1 = p1[0], uy1 = p1[1], w1 = p1[2];
1220 var dx = ux1 - ux0, dy = uy1 - uy0, d2 = dx * dx + dy * dy, d1 = Math.sqrt(d2), b0 = (w1 * w1 - w0 * w0 + ρ4 * d2) / (2 * w0 * ρ2 * d1), b1 = (w1 * w1 - w0 * w0 - ρ4 * d2) / (2 * w1 * ρ2 * d1), r0 = Math.log(Math.sqrt(b0 * b0 + 1) - b0), r1 = Math.log(Math.sqrt(b1 * b1 + 1) - b1), dr = r1 - r0, S = (dr || Math.log(w1 / w0)) / ρ;
1221 function interpolate(t) {
1222 var s = t * S;
1223 if (dr) {
1224 var coshr0 = d3_cosh(r0), u = w0 / (ρ2 * d1) * (coshr0 * d3_tanh(ρ * s + r0) - d3_sinh(r0));
1225 return [ ux0 + u * dx, uy0 + u * dy, w0 * coshr0 / d3_cosh(ρ * s + r0) ];
1226 }
1227 return [ ux0 + t * dx, uy0 + t * dy, w0 * Math.exp(ρ * s) ];
1228 }
1229 interpolate.duration = S * 1e3;
1230 return interpolate;
1231 };
1232 d3.behavior.zoom = function() {
1233 var view = {
1234 x: 0,
1235 y: 0,
1236 k: 1
1237 }, translate0, center0, center, size = [ 960, 500 ], scaleExtent = d3_behavior_zoomInfinity, mousedown = "mousedown.zoom", mousemove = "mousemove.zoom", mouseup = "mouseup.zoom", mousewheelTimer, touchstart = "touchstart.zoom", touchtime, event = d3_eventDispatch(zoom, "zoomstart", "zoom", "zoomend"), x0, x1, y0, y1;
1238 function zoom(g) {
1239 g.on(mousedown, mousedowned).on(d3_behavior_zoomWheel + ".zoom", mousewheeled).on("dblclick.zoom", dblclicked).on(touchstart, touchstarted);
1240 }
1241 zoom.event = function(g) {
1242 g.each(function() {
1243 var dispatch = event.of(this, arguments), view1 = view;
1244 if (d3_transitionInheritId) {
1245 d3.select(this).transition().each("start.zoom", function() {
1246 view = this.__chart__ || {
1247 x: 0,
1248 y: 0,
1249 k: 1
1250 };
1251 zoomstarted(dispatch);
1252 }).tween("zoom:zoom", function() {
1253 var dx = size[0], dy = size[1], cx = dx / 2, cy = dy / 2, i = d3.interpolateZoom([ (cx - view.x) / view.k, (cy - view.y) / view.k, dx / view.k ], [ (cx - view1.x) / view1.k, (cy - view1.y) / view1.k, dx / view1.k ]);
1254 return function(t) {
1255 var l = i(t), k = dx / l[2];
1256 this.__chart__ = view = {
1257 x: cx - l[0] * k,
1258 y: cy - l[1] * k,
1259 k: k
1260 };
1261 zoomed(dispatch);
1262 };
1263 }).each("end.zoom", function() {
1264 zoomended(dispatch);
1265 });
1266 } else {
1267 this.__chart__ = view;
1268 zoomstarted(dispatch);
1269 zoomed(dispatch);
1270 zoomended(dispatch);
1271 }
1272 });
1273 };
1274 zoom.translate = function(_) {
1275 if (!arguments.length) return [ view.x, view.y ];
1276 view = {
1277 x: +_[0],
1278 y: +_[1],
1279 k: view.k
1280 };
1281 rescale();
1282 return zoom;
1283 };
1284 zoom.scale = function(_) {
1285 if (!arguments.length) return view.k;
1286 view = {
1287 x: view.x,
1288 y: view.y,
1289 k: +_
1290 };
1291 rescale();
1292 return zoom;
1293 };
1294 zoom.scaleExtent = function(_) {
1295 if (!arguments.length) return scaleExtent;
1296 scaleExtent = _ == null ? d3_behavior_zoomInfinity : [ +_[0], +_[1] ];
1297 return zoom;
1298 };
1299 zoom.center = function(_) {
1300 if (!arguments.length) return center;
1301 center = _ && [ +_[0], +_[1] ];
1302 return zoom;
1303 };
1304 zoom.size = function(_) {
1305 if (!arguments.length) return size;
1306 size = _ && [ +_[0], +_[1] ];
1307 return zoom;
1308 };
1309 zoom.x = function(z) {
1310 if (!arguments.length) return x1;
1311 x1 = z;
1312 x0 = z.copy();
1313 view = {
1314 x: 0,
1315 y: 0,
1316 k: 1
1317 };
1318 return zoom;
1319 };
1320 zoom.y = function(z) {
1321 if (!arguments.length) return y1;
1322 y1 = z;
1323 y0 = z.copy();
1324 view = {
1325 x: 0,
1326 y: 0,
1327 k: 1
1328 };
1329 return zoom;
1330 };
1331 function location(p) {
1332 return [ (p[0] - view.x) / view.k, (p[1] - view.y) / view.k ];
1333 }
1334 function point(l) {
1335 return [ l[0] * view.k + view.x, l[1] * view.k + view.y ];
1336 }
1337 function scaleTo(s) {
1338 view.k = Math.max(scaleExtent[0], Math.min(scaleExtent[1], s));
1339 }
1340 function translateTo(p, l) {
1341 l = point(l);
1342 view.x += p[0] - l[0];
1343 view.y += p[1] - l[1];
1344 }
1345 function rescale() {
1346 if (x1) x1.domain(x0.range().map(function(x) {
1347 return (x - view.x) / view.k;
1348 }).map(x0.invert));
1349 if (y1) y1.domain(y0.range().map(function(y) {
1350 return (y - view.y) / view.k;
1351 }).map(y0.invert));
1352 }
1353 function zoomstarted(dispatch) {
1354 dispatch({
1355 type: "zoomstart"
1356 });
1357 }
1358 function zoomed(dispatch) {
1359 rescale();
1360 dispatch({
1361 type: "zoom",
1362 scale: view.k,
1363 translate: [ view.x, view.y ]
1364 });
1365 }
1366 function zoomended(dispatch) {
1367 dispatch({
1368 type: "zoomend"
1369 });
1370 }
1371 function mousedowned() {
1372 var that = this, target = d3.event.target, dispatch = event.of(that, arguments), dragged = 0, subject = d3.select(d3_window).on(mousemove, moved).on(mouseup, ended), location0 = location(d3.mouse(that)), dragRestore = d3_event_dragSuppress();
1373 d3_selection_interrupt.call(that);
1374 zoomstarted(dispatch);
1375 function moved() {
1376 dragged = 1;
1377 translateTo(d3.mouse(that), location0);
1378 zoomed(dispatch);
1379 }
1380 function ended() {
1381 subject.on(mousemove, null).on(mouseup, null);
1382 dragRestore(dragged && d3.event.target === target);
1383 zoomended(dispatch);
1384 }
1385 }
1386 function touchstarted() {
1387 var that = this, dispatch = event.of(that, arguments), locations0 = {}, distance0 = 0, scale0, zoomName = ".zoom-" + d3.event.changedTouches[0].identifier, touchmove = "touchmove" + zoomName, touchend = "touchend" + zoomName, targets = [], subject = d3.select(that), dragRestore = d3_event_dragSuppress();
1388 d3_selection_interrupt.call(that);
1389 started();
1390 zoomstarted(dispatch);
1391 subject.on(mousedown, null).on(touchstart, started);
1392 function relocate() {
1393 var touches = d3.touches(that);
1394 scale0 = view.k;
1395 touches.forEach(function(t) {
1396 if (t.identifier in locations0) locations0[t.identifier] = location(t);
1397 });
1398 return touches;
1399 }
1400 function started() {
1401 var target = d3.event.target;
1402 d3.select(target).on(touchmove, moved).on(touchend, ended);
1403 targets.push(target);
1404 var changed = d3.event.changedTouches;
1405 for (var i = 0, n = changed.length; i < n; ++i) {
1406 locations0[changed[i].identifier] = null;
1407 }
1408 var touches = relocate(), now = Date.now();
1409 if (touches.length === 1) {
1410 if (now - touchtime < 500) {
1411 var p = touches[0], l = locations0[p.identifier];
1412 scaleTo(view.k * 2);
1413 translateTo(p, l);
1414 d3_eventPreventDefault();
1415 zoomed(dispatch);
1416 }
1417 touchtime = now;
1418 } else if (touches.length > 1) {
1419 var p = touches[0], q = touches[1], dx = p[0] - q[0], dy = p[1] - q[1];
1420 distance0 = dx * dx + dy * dy;
1421 }
1422 }
1423 function moved() {
1424 var touches = d3.touches(that), p0, l0, p1, l1;
1425 for (var i = 0, n = touches.length; i < n; ++i, l1 = null) {
1426 p1 = touches[i];
1427 if (l1 = locations0[p1.identifier]) {
1428 if (l0) break;
1429 p0 = p1, l0 = l1;
1430 }
1431 }
1432 if (l1) {
1433 var distance1 = (distance1 = p1[0] - p0[0]) * distance1 + (distance1 = p1[1] - p0[1]) * distance1, scale1 = distance0 && Math.sqrt(distance1 / distance0);
1434 p0 = [ (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2 ];
1435 l0 = [ (l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2 ];
1436 scaleTo(scale1 * scale0);
1437 }
1438 touchtime = null;
1439 translateTo(p0, l0);
1440 zoomed(dispatch);
1441 }
1442 function ended() {
1443 if (d3.event.touches.length) {
1444 var changed = d3.event.changedTouches;
1445 for (var i = 0, n = changed.length; i < n; ++i) {
1446 delete locations0[changed[i].identifier];
1447 }
1448 for (var identifier in locations0) {
1449 return void relocate();
1450 }
1451 }
1452 d3.selectAll(targets).on(zoomName, null);
1453 subject.on(mousedown, mousedowned).on(touchstart, touchstarted);
1454 dragRestore();
1455 zoomended(dispatch);
1456 }
1457 }
1458 function mousewheeled() {
1459 var dispatch = event.of(this, arguments);
1460 if (mousewheelTimer) clearTimeout(mousewheelTimer); else translate0 = location(center0 = center || d3.mouse(this)),
1461 d3_selection_interrupt.call(this), zoomstarted(dispatch);
1462 mousewheelTimer = setTimeout(function() {
1463 mousewheelTimer = null;
1464 zoomended(dispatch);
1465 }, 50);
1466 d3_eventPreventDefault();
1467 scaleTo(Math.pow(2, d3_behavior_zoomDelta() * .002) * view.k);
1468 translateTo(center0, translate0);
1469 zoomed(dispatch);
1470 }
1471 function dblclicked() {
1472 var dispatch = event.of(this, arguments), p = d3.mouse(this), l = location(p), k = Math.log(view.k) / Math.LN2;
1473 zoomstarted(dispatch);
1474 scaleTo(Math.pow(2, d3.event.shiftKey ? Math.ceil(k) - 1 : Math.floor(k) + 1));
1475 translateTo(p, l);
1476 zoomed(dispatch);
1477 zoomended(dispatch);
1478 }
1479 return d3.rebind(zoom, event, "on");
1480 };
1481 var d3_behavior_zoomInfinity = [ 0, Infinity ];
1482 var d3_behavior_zoomDelta, d3_behavior_zoomWheel = "onwheel" in d3_document ? (d3_behavior_zoomDelta = function() {
1483 return -d3.event.deltaY * (d3.event.deltaMode ? 120 : 1);
1484 }, "wheel") : "onmousewheel" in d3_document ? (d3_behavior_zoomDelta = function() {
1485 return d3.event.wheelDelta;
1486 }, "mousewheel") : (d3_behavior_zoomDelta = function() {
1487 return -d3.event.detail;
1488 }, "MozMousePixelScroll");
1489 d3.color = d3_color;
1490 function d3_color() {}
1491 d3_color.prototype.toString = function() {
1492 return this.rgb() + "";
1493 };
1494 d3.hsl = d3_hsl;
1495 function d3_hsl(h, s, l) {
1496 return this instanceof d3_hsl ? void (this.h = +h, this.s = +s, this.l = +l) : arguments.length < 2 ? h instanceof d3_hsl ? new d3_hsl(h.h, h.s, h.l) : d3_rgb_parse("" + h, d3_rgb_hsl, d3_hsl) : new d3_hsl(h, s, l);
1497 }
1498 var d3_hslPrototype = d3_hsl.prototype = new d3_color();
1499 d3_hslPrototype.brighter = function(k) {
1500 k = Math.pow(.7, arguments.length ? k : 1);
1501 return new d3_hsl(this.h, this.s, this.l / k);
1502 };
1503 d3_hslPrototype.darker = function(k) {
1504 k = Math.pow(.7, arguments.length ? k : 1);
1505 return new d3_hsl(this.h, this.s, k * this.l);
1506 };
1507 d3_hslPrototype.rgb = function() {
1508 return d3_hsl_rgb(this.h, this.s, this.l);
1509 };
1510 function d3_hsl_rgb(h, s, l) {
1511 var m1, m2;
1512 h = isNaN(h) ? 0 : (h %= 360) < 0 ? h + 360 : h;
1513 s = isNaN(s) ? 0 : s < 0 ? 0 : s > 1 ? 1 : s;
1514 l = l < 0 ? 0 : l > 1 ? 1 : l;
1515 m2 = l <= .5 ? l * (1 + s) : l + s - l * s;
1516 m1 = 2 * l - m2;
1517 function v(h) {
1518 if (h > 360) h -= 360; else if (h < 0) h += 360;
1519 if (h < 60) return m1 + (m2 - m1) * h / 60;
1520 if (h < 180) return m2;
1521 if (h < 240) return m1 + (m2 - m1) * (240 - h) / 60;
1522 return m1;
1523 }
1524 function vv(h) {
1525 return Math.round(v(h) * 255);
1526 }
1527 return new d3_rgb(vv(h + 120), vv(h), vv(h - 120));
1528 }
1529 d3.hcl = d3_hcl;
1530 function d3_hcl(h, c, l) {
1531 return this instanceof d3_hcl ? void (this.h = +h, this.c = +c, this.l = +l) : arguments.length < 2 ? h instanceof d3_hcl ? new d3_hcl(h.h, h.c, h.l) : h instanceof d3_lab ? d3_lab_hcl(h.l, h.a, h.b) : d3_lab_hcl((h = d3_rgb_lab((h = d3.rgb(h)).r, h.g, h.b)).l, h.a, h.b) : new d3_hcl(h, c, l);
1532 }
1533 var d3_hclPrototype = d3_hcl.prototype = new d3_color();
1534 d3_hclPrototype.brighter = function(k) {
1535 return new d3_hcl(this.h, this.c, Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)));
1536 };
1537 d3_hclPrototype.darker = function(k) {
1538 return new d3_hcl(this.h, this.c, Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)));
1539 };
1540 d3_hclPrototype.rgb = function() {
1541 return d3_hcl_lab(this.h, this.c, this.l).rgb();
1542 };
1543 function d3_hcl_lab(h, c, l) {
1544 if (isNaN(h)) h = 0;
1545 if (isNaN(c)) c = 0;
1546 return new d3_lab(l, Math.cos(h *= d3_radians) * c, Math.sin(h) * c);
1547 }
1548 d3.lab = d3_lab;
1549 function d3_lab(l, a, b) {
1550 return this instanceof d3_lab ? void (this.l = +l, this.a = +a, this.b = +b) : arguments.length < 2 ? l instanceof d3_lab ? new d3_lab(l.l, l.a, l.b) : l instanceof d3_hcl ? d3_hcl_lab(l.h, l.c, l.l) : d3_rgb_lab((l = d3_rgb(l)).r, l.g, l.b) : new d3_lab(l, a, b);
1551 }
1552 var d3_lab_K = 18;
1553 var d3_lab_X = .95047, d3_lab_Y = 1, d3_lab_Z = 1.08883;
1554 var d3_labPrototype = d3_lab.prototype = new d3_color();
1555 d3_labPrototype.brighter = function(k) {
1556 return new d3_lab(Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)), this.a, this.b);
1557 };
1558 d3_labPrototype.darker = function(k) {
1559 return new d3_lab(Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)), this.a, this.b);
1560 };
1561 d3_labPrototype.rgb = function() {
1562 return d3_lab_rgb(this.l, this.a, this.b);
1563 };
1564 function d3_lab_rgb(l, a, b) {
1565 var y = (l + 16) / 116, x = y + a / 500, z = y - b / 200;
1566 x = d3_lab_xyz(x) * d3_lab_X;
1567 y = d3_lab_xyz(y) * d3_lab_Y;
1568 z = d3_lab_xyz(z) * d3_lab_Z;
1569 return new d3_rgb(d3_xyz_rgb(3.2404542 * x - 1.5371385 * y - .4985314 * z), d3_xyz_rgb(-.969266 * x + 1.8760108 * y + .041556 * z), d3_xyz_rgb(.0556434 * x - .2040259 * y + 1.0572252 * z));
1570 }
1571 function d3_lab_hcl(l, a, b) {
1572 return l > 0 ? new d3_hcl(Math.atan2(b, a) * d3_degrees, Math.sqrt(a * a + b * b), l) : new d3_hcl(NaN, NaN, l);
1573 }
1574 function d3_lab_xyz(x) {
1575 return x > .206893034 ? x * x * x : (x - 4 / 29) / 7.787037;
1576 }
1577 function d3_xyz_lab(x) {
1578 return x > .008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29;
1579 }
1580 function d3_xyz_rgb(r) {
1581 return Math.round(255 * (r <= .00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - .055));
1582 }
1583 d3.rgb = d3_rgb;
1584 function d3_rgb(r, g, b) {
1585 return this instanceof d3_rgb ? void (this.r = ~~r, this.g = ~~g, this.b = ~~b) : arguments.length < 2 ? r instanceof d3_rgb ? new d3_rgb(r.r, r.g, r.b) : d3_rgb_parse("" + r, d3_rgb, d3_hsl_rgb) : new d3_rgb(r, g, b);
1586 }
1587 function d3_rgbNumber(value) {
1588 return new d3_rgb(value >> 16, value >> 8 & 255, value & 255);
1589 }
1590 function d3_rgbString(value) {
1591 return d3_rgbNumber(value) + "";
1592 }
1593 var d3_rgbPrototype = d3_rgb.prototype = new d3_color();
1594 d3_rgbPrototype.brighter = function(k) {
1595 k = Math.pow(.7, arguments.length ? k : 1);
1596 var r = this.r, g = this.g, b = this.b, i = 30;
1597 if (!r && !g && !b) return new d3_rgb(i, i, i);
1598 if (r && r < i) r = i;
1599 if (g && g < i) g = i;
1600 if (b && b < i) b = i;
1601 return new d3_rgb(Math.min(255, r / k), Math.min(255, g / k), Math.min(255, b / k));
1602 };
1603 d3_rgbPrototype.darker = function(k) {
1604 k = Math.pow(.7, arguments.length ? k : 1);
1605 return new d3_rgb(k * this.r, k * this.g, k * this.b);
1606 };
1607 d3_rgbPrototype.hsl = function() {
1608 return d3_rgb_hsl(this.r, this.g, this.b);
1609 };
1610 d3_rgbPrototype.toString = function() {
1611 return "#" + d3_rgb_hex(this.r) + d3_rgb_hex(this.g) + d3_rgb_hex(this.b);
1612 };
1613 function d3_rgb_hex(v) {
1614 return v < 16 ? "0" + Math.max(0, v).toString(16) : Math.min(255, v).toString(16);
1615 }
1616 function d3_rgb_parse(format, rgb, hsl) {
1617 var r = 0, g = 0, b = 0, m1, m2, color;
1618 m1 = /([a-z]+)\((.*)\)/i.exec(format);
1619 if (m1) {
1620 m2 = m1[2].split(",");
1621 switch (m1[1]) {
1622 case "hsl":
1623 {
1624 return hsl(parseFloat(m2[0]), parseFloat(m2[1]) / 100, parseFloat(m2[2]) / 100);
1625 }
1626
1627 case "rgb":
1628 {
1629 return rgb(d3_rgb_parseNumber(m2[0]), d3_rgb_parseNumber(m2[1]), d3_rgb_parseNumber(m2[2]));
1630 }
1631 }
1632 }
1633 if (color = d3_rgb_names.get(format)) return rgb(color.r, color.g, color.b);
1634 if (format != null && format.charAt(0) === "#" && !isNaN(color = parseInt(format.slice(1), 16))) {
1635 if (format.length === 4) {
1636 r = (color & 3840) >> 4;
1637 r = r >> 4 | r;
1638 g = color & 240;
1639 g = g >> 4 | g;
1640 b = color & 15;
1641 b = b << 4 | b;
1642 } else if (format.length === 7) {
1643 r = (color & 16711680) >> 16;
1644 g = (color & 65280) >> 8;
1645 b = color & 255;
1646 }
1647 }
1648 return rgb(r, g, b);
1649 }
1650 function d3_rgb_hsl(r, g, b) {
1651 var min = Math.min(r /= 255, g /= 255, b /= 255), max = Math.max(r, g, b), d = max - min, h, s, l = (max + min) / 2;
1652 if (d) {
1653 s = l < .5 ? d / (max + min) : d / (2 - max - min);
1654 if (r == max) h = (g - b) / d + (g < b ? 6 : 0); else if (g == max) h = (b - r) / d + 2; else h = (r - g) / d + 4;
1655 h *= 60;
1656 } else {
1657 h = NaN;
1658 s = l > 0 && l < 1 ? 0 : h;
1659 }
1660 return new d3_hsl(h, s, l);
1661 }
1662 function d3_rgb_lab(r, g, b) {
1663 r = d3_rgb_xyz(r);
1664 g = d3_rgb_xyz(g);
1665 b = d3_rgb_xyz(b);
1666 var x = d3_xyz_lab((.4124564 * r + .3575761 * g + .1804375 * b) / d3_lab_X), y = d3_xyz_lab((.2126729 * r + .7151522 * g + .072175 * b) / d3_lab_Y), z = d3_xyz_lab((.0193339 * r + .119192 * g + .9503041 * b) / d3_lab_Z);
1667 return d3_lab(116 * y - 16, 500 * (x - y), 200 * (y - z));
1668 }
1669 function d3_rgb_xyz(r) {
1670 return (r /= 255) <= .04045 ? r / 12.92 : Math.pow((r + .055) / 1.055, 2.4);
1671 }
1672 function d3_rgb_parseNumber(c) {
1673 var f = parseFloat(c);
1674 return c.charAt(c.length - 1) === "%" ? Math.round(f * 2.55) : f;
1675 }
1676 var d3_rgb_names = d3.map({
1677 aliceblue: 15792383,
1678 antiquewhite: 16444375,
1679 aqua: 65535,
1680 aquamarine: 8388564,
1681 azure: 15794175,
1682 beige: 16119260,
1683 bisque: 16770244,
1684 black: 0,
1685 blanchedalmond: 16772045,
1686 blue: 255,
1687 blueviolet: 9055202,
1688 brown: 10824234,
1689 burlywood: 14596231,
1690 cadetblue: 6266528,
1691 chartreuse: 8388352,
1692 chocolate: 13789470,
1693 coral: 16744272,
1694 cornflowerblue: 6591981,
1695 cornsilk: 16775388,
1696 crimson: 14423100,
1697 cyan: 65535,
1698 darkblue: 139,
1699 darkcyan: 35723,
1700 darkgoldenrod: 12092939,
1701 darkgray: 11119017,
1702 darkgreen: 25600,
1703 darkgrey: 11119017,
1704 darkkhaki: 12433259,
1705 darkmagenta: 9109643,
1706 darkolivegreen: 5597999,
1707 darkorange: 16747520,
1708 darkorchid: 10040012,
1709 darkred: 9109504,
1710 darksalmon: 15308410,
1711 darkseagreen: 9419919,
1712 darkslateblue: 4734347,
1713 darkslategray: 3100495,
1714 darkslategrey: 3100495,
1715 darkturquoise: 52945,
1716 darkviolet: 9699539,
1717 deeppink: 16716947,
1718 deepskyblue: 49151,
1719 dimgray: 6908265,
1720 dimgrey: 6908265,
1721 dodgerblue: 2003199,
1722 firebrick: 11674146,
1723 floralwhite: 16775920,
1724 forestgreen: 2263842,
1725 fuchsia: 16711935,
1726 gainsboro: 14474460,
1727 ghostwhite: 16316671,
1728 gold: 16766720,
1729 goldenrod: 14329120,
1730 gray: 8421504,
1731 green: 32768,
1732 greenyellow: 11403055,
1733 grey: 8421504,
1734 honeydew: 15794160,
1735 hotpink: 16738740,
1736 indianred: 13458524,
1737 indigo: 4915330,
1738 ivory: 16777200,
1739 khaki: 15787660,
1740 lavender: 15132410,
1741 lavenderblush: 16773365,
1742 lawngreen: 8190976,
1743 lemonchiffon: 16775885,
1744 lightblue: 11393254,
1745 lightcoral: 15761536,
1746 lightcyan: 14745599,
1747 lightgoldenrodyellow: 16448210,
1748 lightgray: 13882323,
1749 lightgreen: 9498256,
1750 lightgrey: 13882323,
1751 lightpink: 16758465,
1752 lightsalmon: 16752762,
1753 lightseagreen: 2142890,
1754 lightskyblue: 8900346,
1755 lightslategray: 7833753,
1756 lightslategrey: 7833753,
1757 lightsteelblue: 11584734,
1758 lightyellow: 16777184,
1759 lime: 65280,
1760 limegreen: 3329330,
1761 linen: 16445670,
1762 magenta: 16711935,
1763 maroon: 8388608,
1764 mediumaquamarine: 6737322,
1765 mediumblue: 205,
1766 mediumorchid: 12211667,
1767 mediumpurple: 9662683,
1768 mediumseagreen: 3978097,
1769 mediumslateblue: 8087790,
1770 mediumspringgreen: 64154,
1771 mediumturquoise: 4772300,
1772 mediumvioletred: 13047173,
1773 midnightblue: 1644912,
1774 mintcream: 16121850,
1775 mistyrose: 16770273,
1776 moccasin: 16770229,
1777 navajowhite: 16768685,
1778 navy: 128,
1779 oldlace: 16643558,
1780 olive: 8421376,
1781 olivedrab: 7048739,
1782 orange: 16753920,
1783 orangered: 16729344,
1784 orchid: 14315734,
1785 palegoldenrod: 15657130,
1786 palegreen: 10025880,
1787 paleturquoise: 11529966,
1788 palevioletred: 14381203,
1789 papayawhip: 16773077,
1790 peachpuff: 16767673,
1791 peru: 13468991,
1792 pink: 16761035,
1793 plum: 14524637,
1794 powderblue: 11591910,
1795 purple: 8388736,
1796 red: 16711680,
1797 rosybrown: 12357519,
1798 royalblue: 4286945,
1799 saddlebrown: 9127187,
1800 salmon: 16416882,
1801 sandybrown: 16032864,
1802 seagreen: 3050327,
1803 seashell: 16774638,
1804 sienna: 10506797,
1805 silver: 12632256,
1806 skyblue: 8900331,
1807 slateblue: 6970061,
1808 slategray: 7372944,
1809 slategrey: 7372944,
1810 snow: 16775930,
1811 springgreen: 65407,
1812 steelblue: 4620980,
1813 tan: 13808780,
1814 teal: 32896,
1815 thistle: 14204888,
1816 tomato: 16737095,
1817 turquoise: 4251856,
1818 violet: 15631086,
1819 wheat: 16113331,
1820 white: 16777215,
1821 whitesmoke: 16119285,
1822 yellow: 16776960,
1823 yellowgreen: 10145074
1824 });
1825 d3_rgb_names.forEach(function(key, value) {
1826 d3_rgb_names.set(key, d3_rgbNumber(value));
1827 });
1828 function d3_functor(v) {
1829 return typeof v === "function" ? v : function() {
1830 return v;
1831 };
1832 }
1833 d3.functor = d3_functor;
1834 function d3_identity(d) {
1835 return d;
1836 }
1837 d3.xhr = d3_xhrType(d3_identity);
1838 function d3_xhrType(response) {
1839 return function(url, mimeType, callback) {
1840 if (arguments.length === 2 && typeof mimeType === "function") callback = mimeType,
1841 mimeType = null;
1842 return d3_xhr(url, mimeType, response, callback);
1843 };
1844 }
1845 function d3_xhr(url, mimeType, response, callback) {
1846 var xhr = {}, dispatch = d3.dispatch("beforesend", "progress", "load", "error"), headers = {}, request = new XMLHttpRequest(), responseType = null;
1847 if (d3_window.XDomainRequest && !("withCredentials" in request) && /^(http(s)?:)?\/\//.test(url)) request = new XDomainRequest();
1848 "onload" in request ? request.onload = request.onerror = respond : request.onreadystatechange = function() {
1849 request.readyState > 3 && respond();
1850 };
1851 function respond() {
1852 var status = request.status, result;
1853 if (!status && d3_xhrHasResponse(request) || status >= 200 && status < 300 || status === 304) {
1854 try {
1855 result = response.call(xhr, request);
1856 } catch (e) {
1857 dispatch.error.call(xhr, e);
1858 return;
1859 }
1860 dispatch.load.call(xhr, result);
1861 } else {
1862 dispatch.error.call(xhr, request);
1863 }
1864 }
1865 request.onprogress = function(event) {
1866 var o = d3.event;
1867 d3.event = event;
1868 try {
1869 dispatch.progress.call(xhr, request);
1870 } finally {
1871 d3.event = o;
1872 }
1873 };
1874 xhr.header = function(name, value) {
1875 name = (name + "").toLowerCase();
1876 if (arguments.length < 2) return headers[name];
1877 if (value == null) delete headers[name]; else headers[name] = value + "";
1878 return xhr;
1879 };
1880 xhr.mimeType = function(value) {
1881 if (!arguments.length) return mimeType;
1882 mimeType = value == null ? null : value + "";
1883 return xhr;
1884 };
1885 xhr.responseType = function(value) {
1886 if (!arguments.length) return responseType;
1887 responseType = value;
1888 return xhr;
1889 };
1890 xhr.response = function(value) {
1891 response = value;
1892 return xhr;
1893 };
1894 [ "get", "post" ].forEach(function(method) {
1895 xhr[method] = function() {
1896 return xhr.send.apply(xhr, [ method ].concat(d3_array(arguments)));
1897 };
1898 });
1899 xhr.send = function(method, data, callback) {
1900 if (arguments.length === 2 && typeof data === "function") callback = data, data = null;
1901 request.open(method, url, true);
1902 if (mimeType != null && !("accept" in headers)) headers["accept"] = mimeType + ",*/*";
1903 if (request.setRequestHeader) for (var name in headers) request.setRequestHeader(name, headers[name]);
1904 if (mimeType != null && request.overrideMimeType) request.overrideMimeType(mimeType);
1905 if (responseType != null) request.responseType = responseType;
1906 if (callback != null) xhr.on("error", callback).on("load", function(request) {
1907 callback(null, request);
1908 });
1909 dispatch.beforesend.call(xhr, request);
1910 request.send(data == null ? null : data);
1911 return xhr;
1912 };
1913 xhr.abort = function() {
1914 request.abort();
1915 return xhr;
1916 };
1917 d3.rebind(xhr, dispatch, "on");
1918 return callback == null ? xhr : xhr.get(d3_xhr_fixCallback(callback));
1919 }
1920 function d3_xhr_fixCallback(callback) {
1921 return callback.length === 1 ? function(error, request) {
1922 callback(error == null ? request : null);
1923 } : callback;
1924 }
1925 function d3_xhrHasResponse(request) {
1926 var type = request.responseType;
1927 return type && type !== "text" ? request.response : request.responseText;
1928 }
1929 d3.dsv = function(delimiter, mimeType) {
1930 var reFormat = new RegExp('["' + delimiter + "\n]"), delimiterCode = delimiter.charCodeAt(0);
1931 function dsv(url, row, callback) {
1932 if (arguments.length < 3) callback = row, row = null;
1933 var xhr = d3_xhr(url, mimeType, row == null ? response : typedResponse(row), callback);
1934 xhr.row = function(_) {
1935 return arguments.length ? xhr.response((row = _) == null ? response : typedResponse(_)) : row;
1936 };
1937 return xhr;
1938 }
1939 function response(request) {
1940 return dsv.parse(request.responseText);
1941 }
1942 function typedResponse(f) {
1943 return function(request) {
1944 return dsv.parse(request.responseText, f);
1945 };
1946 }
1947 dsv.parse = function(text, f) {
1948 var o;
1949 return dsv.parseRows(text, function(row, i) {
1950 if (o) return o(row, i - 1);
1951 var a = new Function("d", "return {" + row.map(function(name, i) {
1952 return JSON.stringify(name) + ": d[" + i + "]";
1953 }).join(",") + "}");
1954 o = f ? function(row, i) {
1955 return f(a(row), i);
1956 } : a;
1957 });
1958 };
1959 dsv.parseRows = function(text, f) {
1960 var EOL = {}, EOF = {}, rows = [], N = text.length, I = 0, n = 0, t, eol;
1961 function token() {
1962 if (I >= N) return EOF;
1963 if (eol) return eol = false, EOL;
1964 var j = I;
1965 if (text.charCodeAt(j) === 34) {
1966 var i = j;
1967 while (i++ < N) {
1968 if (text.charCodeAt(i) === 34) {
1969 if (text.charCodeAt(i + 1) !== 34) break;
1970 ++i;
1971 }
1972 }
1973 I = i + 2;
1974 var c = text.charCodeAt(i + 1);
1975 if (c === 13) {
1976 eol = true;
1977 if (text.charCodeAt(i + 2) === 10) ++I;
1978 } else if (c === 10) {
1979 eol = true;
1980 }
1981 return text.slice(j + 1, i).replace(/""/g, '"');
1982 }
1983 while (I < N) {
1984 var c = text.charCodeAt(I++), k = 1;
1985 if (c === 10) eol = true; else if (c === 13) {
1986 eol = true;
1987 if (text.charCodeAt(I) === 10) ++I, ++k;
1988 } else if (c !== delimiterCode) continue;
1989 return text.slice(j, I - k);
1990 }
1991 return text.slice(j);
1992 }
1993 while ((t = token()) !== EOF) {
1994 var a = [];
1995 while (t !== EOL && t !== EOF) {
1996 a.push(t);
1997 t = token();
1998 }
1999 if (f && (a = f(a, n++)) == null) continue;
2000 rows.push(a);
2001 }
2002 return rows;
2003 };
2004 dsv.format = function(rows) {
2005 if (Array.isArray(rows[0])) return dsv.formatRows(rows);
2006 var fieldSet = new d3_Set(), fields = [];
2007 rows.forEach(function(row) {
2008 for (var field in row) {
2009 if (!fieldSet.has(field)) {
2010 fields.push(fieldSet.add(field));
2011 }
2012 }
2013 });
2014 return [ fields.map(formatValue).join(delimiter) ].concat(rows.map(function(row) {
2015 return fields.map(function(field) {
2016 return formatValue(row[field]);
2017 }).join(delimiter);
2018 })).join("\n");
2019 };
2020 dsv.formatRows = function(rows) {
2021 return rows.map(formatRow).join("\n");
2022 };
2023 function formatRow(row) {
2024 return row.map(formatValue).join(delimiter);
2025 }
2026 function formatValue(text) {
2027 return reFormat.test(text) ? '"' + text.replace(/\"/g, '""') + '"' : text;
2028 }
2029 return dsv;
2030 };
2031 d3.csv = d3.dsv(",", "text/csv");
2032 d3.tsv = d3.dsv(" ", "text/tab-separated-values");
2033 var d3_timer_queueHead, d3_timer_queueTail, d3_timer_interval, d3_timer_timeout, d3_timer_active, d3_timer_frame = d3_window[d3_vendorSymbol(d3_window, "requestAnimationFrame")] || function(callback) {
2034 setTimeout(callback, 17);
2035 };
2036 d3.timer = function(callback, delay, then) {
2037 var n = arguments.length;
2038 if (n < 2) delay = 0;
2039 if (n < 3) then = Date.now();
2040 var time = then + delay, timer = {
2041 c: callback,
2042 t: time,
2043 f: false,
2044 n: null
2045 };
2046 if (d3_timer_queueTail) d3_timer_queueTail.n = timer; else d3_timer_queueHead = timer;
2047 d3_timer_queueTail = timer;
2048 if (!d3_timer_interval) {
2049 d3_timer_timeout = clearTimeout(d3_timer_timeout);
2050 d3_timer_interval = 1;
2051 d3_timer_frame(d3_timer_step);
2052 }
2053 };
2054 function d3_timer_step() {
2055 var now = d3_timer_mark(), delay = d3_timer_sweep() - now;
2056 if (delay > 24) {
2057 if (isFinite(delay)) {
2058 clearTimeout(d3_timer_timeout);
2059 d3_timer_timeout = setTimeout(d3_timer_step, delay);
2060 }
2061 d3_timer_interval = 0;
2062 } else {
2063 d3_timer_interval = 1;
2064 d3_timer_frame(d3_timer_step);
2065 }
2066 }
2067 d3.timer.flush = function() {
2068 d3_timer_mark();
2069 d3_timer_sweep();
2070 };
2071 function d3_timer_mark() {
2072 var now = Date.now();
2073 d3_timer_active = d3_timer_queueHead;
2074 while (d3_timer_active) {
2075 if (now >= d3_timer_active.t) d3_timer_active.f = d3_timer_active.c(now - d3_timer_active.t);
2076 d3_timer_active = d3_timer_active.n;
2077 }
2078 return now;
2079 }
2080 function d3_timer_sweep() {
2081 var t0, t1 = d3_timer_queueHead, time = Infinity;
2082 while (t1) {
2083 if (t1.f) {
2084 t1 = t0 ? t0.n = t1.n : d3_timer_queueHead = t1.n;
2085 } else {
2086 if (t1.t < time) time = t1.t;
2087 t1 = (t0 = t1).n;
2088 }
2089 }
2090 d3_timer_queueTail = t0;
2091 return time;
2092 }
2093 function d3_format_precision(x, p) {
2094 return p - (x ? Math.ceil(Math.log(x) / Math.LN10) : 1);
2095 }
2096 d3.round = function(x, n) {
2097 return n ? Math.round(x * (n = Math.pow(10, n))) / n : Math.round(x);
2098 };
2099 var d3_formatPrefixes = [ "y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" ].map(d3_formatPrefix);
2100 d3.formatPrefix = function(value, precision) {
2101 var i = 0;
2102 if (value) {
2103 if (value < 0) value *= -1;
2104 if (precision) value = d3.round(value, d3_format_precision(value, precision));
2105 i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10);
2106 i = Math.max(-24, Math.min(24, Math.floor((i - 1) / 3) * 3));
2107 }
2108 return d3_formatPrefixes[8 + i / 3];
2109 };
2110 function d3_formatPrefix(d, i) {
2111 var k = Math.pow(10, abs(8 - i) * 3);
2112 return {
2113 scale: i > 8 ? function(d) {
2114 return d / k;
2115 } : function(d) {
2116 return d * k;
2117 },
2118 symbol: d
2119 };
2120 }
2121 function d3_locale_numberFormat(locale) {
2122 var locale_decimal = locale.decimal, locale_thousands = locale.thousands, locale_grouping = locale.grouping, locale_currency = locale.currency, formatGroup = locale_grouping && locale_thousands ? function(value, width) {
2123 var i = value.length, t = [], j = 0, g = locale_grouping[0], length = 0;
2124 while (i > 0 && g > 0) {
2125 if (length + g + 1 > width) g = Math.max(1, width - length);
2126 t.push(value.substring(i -= g, i + g));
2127 if ((length += g + 1) > width) break;
2128 g = locale_grouping[j = (j + 1) % locale_grouping.length];
2129 }
2130 return t.reverse().join(locale_thousands);
2131 } : d3_identity;
2132 return function(specifier) {
2133 var match = d3_format_re.exec(specifier), fill = match[1] || " ", align = match[2] || ">", sign = match[3] || "-", symbol = match[4] || "", zfill = match[5], width = +match[6], comma = match[7], precision = match[8], type = match[9], scale = 1, prefix = "", suffix = "", integer = false, exponent = true;
2134 if (precision) precision = +precision.substring(1);
2135 if (zfill || fill === "0" && align === "=") {
2136 zfill = fill = "0";
2137 align = "=";
2138 }
2139 switch (type) {
2140 case "n":
2141 comma = true;
2142 type = "g";
2143 break;
2144
2145 case "%":
2146 scale = 100;
2147 suffix = "%";
2148 type = "f";
2149 break;
2150
2151 case "p":
2152 scale = 100;
2153 suffix = "%";
2154 type = "r";
2155 break;
2156
2157 case "b":
2158 case "o":
2159 case "x":
2160 case "X":
2161 if (symbol === "#") prefix = "0" + type.toLowerCase();
2162
2163 case "c":
2164 exponent = false;
2165
2166 case "d":
2167 integer = true;
2168 precision = 0;
2169 break;
2170
2171 case "s":
2172 scale = -1;
2173 type = "r";
2174 break;
2175 }
2176 if (symbol === "$") prefix = locale_currency[0], suffix = locale_currency[1];
2177 if (type == "r" && !precision) type = "g";
2178 if (precision != null) {
2179 if (type == "g") precision = Math.max(1, Math.min(21, precision)); else if (type == "e" || type == "f") precision = Math.max(0, Math.min(20, precision));
2180 }
2181 type = d3_format_types.get(type) || d3_format_typeDefault;
2182 var zcomma = zfill && comma;
2183 return function(value) {
2184 var fullSuffix = suffix;
2185 if (integer && value % 1) return "";
2186 var negative = value < 0 || value === 0 && 1 / value < 0 ? (value = -value, "-") : sign === "-" ? "" : sign;
2187 if (scale < 0) {
2188 var unit = d3.formatPrefix(value, precision);
2189 value = unit.scale(value);
2190 fullSuffix = unit.symbol + suffix;
2191 } else {
2192 value *= scale;
2193 }
2194 value = type(value, precision);
2195 var i = value.lastIndexOf("."), before, after;
2196 if (i < 0) {
2197 var j = exponent ? value.lastIndexOf("e") : -1;
2198 if (j < 0) before = value, after = ""; else before = value.substring(0, j), after = value.substring(j);
2199 } else {
2200 before = value.substring(0, i);
2201 after = locale_decimal + value.substring(i + 1);
2202 }
2203 if (!zfill && comma) before = formatGroup(before, Infinity);
2204 var length = prefix.length + before.length + after.length + (zcomma ? 0 : negative.length), padding = length < width ? new Array(length = width - length + 1).join(fill) : "";
2205 if (zcomma) before = formatGroup(padding + before, padding.length ? width - after.length : Infinity);
2206 negative += prefix;
2207 value = before + after;
2208 return (align === "<" ? negative + value + padding : align === ">" ? padding + negative + value : align === "^" ? padding.substring(0, length >>= 1) + negative + value + padding.substring(length) : negative + (zcomma ? value : padding + value)) + fullSuffix;
2209 };
2210 };
2211 }
2212 var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i;
2213 var d3_format_types = d3.map({
2214 b: function(x) {
2215 return x.toString(2);
2216 },
2217 c: function(x) {
2218 return String.fromCharCode(x);
2219 },
2220 o: function(x) {
2221 return x.toString(8);
2222 },
2223 x: function(x) {
2224 return x.toString(16);
2225 },
2226 X: function(x) {
2227 return x.toString(16).toUpperCase();
2228 },
2229 g: function(x, p) {
2230 return x.toPrecision(p);
2231 },
2232 e: function(x, p) {
2233 return x.toExponential(p);
2234 },
2235 f: function(x, p) {
2236 return x.toFixed(p);
2237 },
2238 r: function(x, p) {
2239 return (x = d3.round(x, d3_format_precision(x, p))).toFixed(Math.max(0, Math.min(20, d3_format_precision(x * (1 + 1e-15), p))));
2240 }
2241 });
2242 function d3_format_typeDefault(x) {
2243 return x + "";
2244 }
2245 var d3_time = d3.time = {}, d3_date = Date;
2246 function d3_date_utc() {
2247 this._ = new Date(arguments.length > 1 ? Date.UTC.apply(this, arguments) : arguments[0]);
2248 }
2249 d3_date_utc.prototype = {
2250 getDate: function() {
2251 return this._.getUTCDate();
2252 },
2253 getDay: function() {
2254 return this._.getUTCDay();
2255 },
2256 getFullYear: function() {
2257 return this._.getUTCFullYear();
2258 },
2259 getHours: function() {
2260 return this._.getUTCHours();
2261 },
2262 getMilliseconds: function() {
2263 return this._.getUTCMilliseconds();
2264 },
2265 getMinutes: function() {
2266 return this._.getUTCMinutes();
2267 },
2268 getMonth: function() {
2269 return this._.getUTCMonth();
2270 },
2271 getSeconds: function() {
2272 return this._.getUTCSeconds();
2273 },
2274 getTime: function() {
2275 return this._.getTime();
2276 },
2277 getTimezoneOffset: function() {
2278 return 0;
2279 },
2280 valueOf: function() {
2281 return this._.valueOf();
2282 },
2283 setDate: function() {
2284 d3_time_prototype.setUTCDate.apply(this._, arguments);
2285 },
2286 setDay: function() {
2287 d3_time_prototype.setUTCDay.apply(this._, arguments);
2288 },
2289 setFullYear: function() {
2290 d3_time_prototype.setUTCFullYear.apply(this._, arguments);
2291 },
2292 setHours: function() {
2293 d3_time_prototype.setUTCHours.apply(this._, arguments);
2294 },
2295 setMilliseconds: function() {
2296 d3_time_prototype.setUTCMilliseconds.apply(this._, arguments);
2297 },
2298 setMinutes: function() {
2299 d3_time_prototype.setUTCMinutes.apply(this._, arguments);
2300 },
2301 setMonth: function() {
2302 d3_time_prototype.setUTCMonth.apply(this._, arguments);
2303 },
2304 setSeconds: function() {
2305 d3_time_prototype.setUTCSeconds.apply(this._, arguments);
2306 },
2307 setTime: function() {
2308 d3_time_prototype.setTime.apply(this._, arguments);
2309 }
2310 };
2311 var d3_time_prototype = Date.prototype;
2312 function d3_time_interval(local, step, number) {
2313 function round(date) {
2314 var d0 = local(date), d1 = offset(d0, 1);
2315 return date - d0 < d1 - date ? d0 : d1;
2316 }
2317 function ceil(date) {
2318 step(date = local(new d3_date(date - 1)), 1);
2319 return date;
2320 }
2321 function offset(date, k) {
2322 step(date = new d3_date(+date), k);
2323 return date;
2324 }
2325 function range(t0, t1, dt) {
2326 var time = ceil(t0), times = [];
2327 if (dt > 1) {
2328 while (time < t1) {
2329 if (!(number(time) % dt)) times.push(new Date(+time));
2330 step(time, 1);
2331 }
2332 } else {
2333 while (time < t1) times.push(new Date(+time)), step(time, 1);
2334 }
2335 return times;
2336 }
2337 function range_utc(t0, t1, dt) {
2338 try {
2339 d3_date = d3_date_utc;
2340 var utc = new d3_date_utc();
2341 utc._ = t0;
2342 return range(utc, t1, dt);
2343 } finally {
2344 d3_date = Date;
2345 }
2346 }
2347 local.floor = local;
2348 local.round = round;
2349 local.ceil = ceil;
2350 local.offset = offset;
2351 local.range = range;
2352 var utc = local.utc = d3_time_interval_utc(local);
2353 utc.floor = utc;
2354 utc.round = d3_time_interval_utc(round);
2355 utc.ceil = d3_time_interval_utc(ceil);
2356 utc.offset = d3_time_interval_utc(offset);
2357 utc.range = range_utc;
2358 return local;
2359 }
2360 function d3_time_interval_utc(method) {
2361 return function(date, k) {
2362 try {
2363 d3_date = d3_date_utc;
2364 var utc = new d3_date_utc();
2365 utc._ = date;
2366 return method(utc, k)._;
2367 } finally {
2368 d3_date = Date;
2369 }
2370 };
2371 }
2372 d3_time.year = d3_time_interval(function(date) {
2373 date = d3_time.day(date);
2374 date.setMonth(0, 1);
2375 return date;
2376 }, function(date, offset) {
2377 date.setFullYear(date.getFullYear() + offset);
2378 }, function(date) {
2379 return date.getFullYear();
2380 });
2381 d3_time.years = d3_time.year.range;
2382 d3_time.years.utc = d3_time.year.utc.range;
2383 d3_time.day = d3_time_interval(function(date) {
2384 var day = new d3_date(2e3, 0);
2385 day.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
2386 return day;
2387 }, function(date, offset) {
2388 date.setDate(date.getDate() + offset);
2389 }, function(date) {
2390 return date.getDate() - 1;
2391 });
2392 d3_time.days = d3_time.day.range;
2393 d3_time.days.utc = d3_time.day.utc.range;
2394 d3_time.dayOfYear = function(date) {
2395 var year = d3_time.year(date);
2396 return Math.floor((date - year - (date.getTimezoneOffset() - year.getTimezoneOffset()) * 6e4) / 864e5);
2397 };
2398 [ "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday" ].forEach(function(day, i) {
2399 i = 7 - i;
2400 var interval = d3_time[day] = d3_time_interval(function(date) {
2401 (date = d3_time.day(date)).setDate(date.getDate() - (date.getDay() + i) % 7);
2402 return date;
2403 }, function(date, offset) {
2404 date.setDate(date.getDate() + Math.floor(offset) * 7);
2405 }, function(date) {
2406 var day = d3_time.year(date).getDay();
2407 return Math.floor((d3_time.dayOfYear(date) + (day + i) % 7) / 7) - (day !== i);
2408 });
2409 d3_time[day + "s"] = interval.range;
2410 d3_time[day + "s"].utc = interval.utc.range;
2411 d3_time[day + "OfYear"] = function(date) {
2412 var day = d3_time.year(date).getDay();
2413 return Math.floor((d3_time.dayOfYear(date) + (day + i) % 7) / 7);
2414 };
2415 });
2416 d3_time.week = d3_time.sunday;
2417 d3_time.weeks = d3_time.sunday.range;
2418 d3_time.weeks.utc = d3_time.sunday.utc.range;
2419 d3_time.weekOfYear = d3_time.sundayOfYear;
2420 function d3_locale_timeFormat(locale) {
2421 var locale_dateTime = locale.dateTime, locale_date = locale.date, locale_time = locale.time, locale_periods = locale.periods, locale_days = locale.days, locale_shortDays = locale.shortDays, locale_months = locale.months, locale_shortMonths = locale.shortMonths;
2422 function d3_time_format(template) {
2423 var n = template.length;
2424 function format(date) {
2425 var string = [], i = -1, j = 0, c, p, f;
2426 while (++i < n) {
2427 if (template.charCodeAt(i) === 37) {
2428 string.push(template.slice(j, i));
2429 if ((p = d3_time_formatPads[c = template.charAt(++i)]) != null) c = template.charAt(++i);
2430 if (f = d3_time_formats[c]) c = f(date, p == null ? c === "e" ? " " : "0" : p);
2431 string.push(c);
2432 j = i + 1;
2433 }
2434 }
2435 string.push(template.slice(j, i));
2436 return string.join("");
2437 }
2438 format.parse = function(string) {
2439 var d = {
2440 y: 1900,
2441 m: 0,
2442 d: 1,
2443 H: 0,
2444 M: 0,
2445 S: 0,
2446 L: 0,
2447 Z: null
2448 }, i = d3_time_parse(d, template, string, 0);
2449 if (i != string.length) return null;
2450 if ("p" in d) d.H = d.H % 12 + d.p * 12;
2451 var localZ = d.Z != null && d3_date !== d3_date_utc, date = new (localZ ? d3_date_utc : d3_date)();
2452 if ("j" in d) date.setFullYear(d.y, 0, d.j); else if ("w" in d && ("W" in d || "U" in d)) {
2453 date.setFullYear(d.y, 0, 1);
2454 date.setFullYear(d.y, 0, "W" in d ? (d.w + 6) % 7 + d.W * 7 - (date.getDay() + 5) % 7 : d.w + d.U * 7 - (date.getDay() + 6) % 7);
2455 } else date.setFullYear(d.y, d.m, d.d);
2456 date.setHours(d.H + (d.Z / 100 | 0), d.M + d.Z % 100, d.S, d.L);
2457 return localZ ? date._ : date;
2458 };
2459 format.toString = function() {
2460 return template;
2461 };
2462 return format;
2463 }
2464 function d3_time_parse(date, template, string, j) {
2465 var c, p, t, i = 0, n = template.length, m = string.length;
2466 while (i < n) {
2467 if (j >= m) return -1;
2468 c = template.charCodeAt(i++);
2469 if (c === 37) {
2470 t = template.charAt(i++);
2471 p = d3_time_parsers[t in d3_time_formatPads ? template.charAt(i++) : t];
2472 if (!p || (j = p(date, string, j)) < 0) return -1;
2473 } else if (c != string.charCodeAt(j++)) {
2474 return -1;
2475 }
2476 }
2477 return j;
2478 }
2479 d3_time_format.utc = function(template) {
2480 var local = d3_time_format(template);
2481 function format(date) {
2482 try {
2483 d3_date = d3_date_utc;
2484 var utc = new d3_date();
2485 utc._ = date;
2486 return local(utc);
2487 } finally {
2488 d3_date = Date;
2489 }
2490 }
2491 format.parse = function(string) {
2492 try {
2493 d3_date = d3_date_utc;
2494 var date = local.parse(string);
2495 return date && date._;
2496 } finally {
2497 d3_date = Date;
2498 }
2499 };
2500 format.toString = local.toString;
2501 return format;
2502 };
2503 d3_time_format.multi = d3_time_format.utc.multi = d3_time_formatMulti;
2504 var d3_time_periodLookup = d3.map(), d3_time_dayRe = d3_time_formatRe(locale_days), d3_time_dayLookup = d3_time_formatLookup(locale_days), d3_time_dayAbbrevRe = d3_time_formatRe(locale_shortDays), d3_time_dayAbbrevLookup = d3_time_formatLookup(locale_shortDays), d3_time_monthRe = d3_time_formatRe(locale_months), d3_time_monthLookup = d3_time_formatLookup(locale_months), d3_time_monthAbbrevRe = d3_time_formatRe(locale_shortMonths), d3_time_monthAbbrevLookup = d3_time_formatLookup(locale_shortMonths);
2505 locale_periods.forEach(function(p, i) {
2506 d3_time_periodLookup.set(p.toLowerCase(), i);
2507 });
2508 var d3_time_formats = {
2509 a: function(d) {
2510 return locale_shortDays[d.getDay()];
2511 },
2512 A: function(d) {
2513 return locale_days[d.getDay()];
2514 },
2515 b: function(d) {
2516 return locale_shortMonths[d.getMonth()];
2517 },
2518 B: function(d) {
2519 return locale_months[d.getMonth()];
2520 },
2521 c: d3_time_format(locale_dateTime),
2522 d: function(d, p) {
2523 return d3_time_formatPad(d.getDate(), p, 2);
2524 },
2525 e: function(d, p) {
2526 return d3_time_formatPad(d.getDate(), p, 2);
2527 },
2528 H: function(d, p) {
2529 return d3_time_formatPad(d.getHours(), p, 2);
2530 },
2531 I: function(d, p) {
2532 return d3_time_formatPad(d.getHours() % 12 || 12, p, 2);
2533 },
2534 j: function(d, p) {
2535 return d3_time_formatPad(1 + d3_time.dayOfYear(d), p, 3);
2536 },
2537 L: function(d, p) {
2538 return d3_time_formatPad(d.getMilliseconds(), p, 3);
2539 },
2540 m: function(d, p) {
2541 return d3_time_formatPad(d.getMonth() + 1, p, 2);
2542 },
2543 M: function(d, p) {
2544 return d3_time_formatPad(d.getMinutes(), p, 2);
2545 },
2546 p: function(d) {
2547 return locale_periods[+(d.getHours() >= 12)];
2548 },
2549 S: function(d, p) {
2550 return d3_time_formatPad(d.getSeconds(), p, 2);
2551 },
2552 U: function(d, p) {
2553 return d3_time_formatPad(d3_time.sundayOfYear(d), p, 2);
2554 },
2555 w: function(d) {
2556 return d.getDay();
2557 },
2558 W: function(d, p) {
2559 return d3_time_formatPad(d3_time.mondayOfYear(d), p, 2);
2560 },
2561 x: d3_time_format(locale_date),
2562 X: d3_time_format(locale_time),
2563 y: function(d, p) {
2564 return d3_time_formatPad(d.getFullYear() % 100, p, 2);
2565 },
2566 Y: function(d, p) {
2567 return d3_time_formatPad(d.getFullYear() % 1e4, p, 4);
2568 },
2569 Z: d3_time_zone,
2570 "%": function() {
2571 return "%";
2572 }
2573 };
2574 var d3_time_parsers = {
2575 a: d3_time_parseWeekdayAbbrev,
2576 A: d3_time_parseWeekday,
2577 b: d3_time_parseMonthAbbrev,
2578 B: d3_time_parseMonth,
2579 c: d3_time_parseLocaleFull,
2580 d: d3_time_parseDay,
2581 e: d3_time_parseDay,
2582 H: d3_time_parseHour24,
2583 I: d3_time_parseHour24,
2584 j: d3_time_parseDayOfYear,
2585 L: d3_time_parseMilliseconds,
2586 m: d3_time_parseMonthNumber,
2587 M: d3_time_parseMinutes,
2588 p: d3_time_parseAmPm,
2589 S: d3_time_parseSeconds,
2590 U: d3_time_parseWeekNumberSunday,
2591 w: d3_time_parseWeekdayNumber,
2592 W: d3_time_parseWeekNumberMonday,
2593 x: d3_time_parseLocaleDate,
2594 X: d3_time_parseLocaleTime,
2595 y: d3_time_parseYear,
2596 Y: d3_time_parseFullYear,
2597 Z: d3_time_parseZone,
2598 "%": d3_time_parseLiteralPercent
2599 };
2600 function d3_time_parseWeekdayAbbrev(date, string, i) {
2601 d3_time_dayAbbrevRe.lastIndex = 0;
2602 var n = d3_time_dayAbbrevRe.exec(string.slice(i));
2603 return n ? (date.w = d3_time_dayAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
2604 }
2605 function d3_time_parseWeekday(date, string, i) {
2606 d3_time_dayRe.lastIndex = 0;
2607 var n = d3_time_dayRe.exec(string.slice(i));
2608 return n ? (date.w = d3_time_dayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
2609 }
2610 function d3_time_parseMonthAbbrev(date, string, i) {
2611 d3_time_monthAbbrevRe.lastIndex = 0;
2612 var n = d3_time_monthAbbrevRe.exec(string.slice(i));
2613 return n ? (date.m = d3_time_monthAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
2614 }
2615 function d3_time_parseMonth(date, string, i) {
2616 d3_time_monthRe.lastIndex = 0;
2617 var n = d3_time_monthRe.exec(string.slice(i));
2618 return n ? (date.m = d3_time_monthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
2619 }
2620 function d3_time_parseLocaleFull(date, string, i) {
2621 return d3_time_parse(date, d3_time_formats.c.toString(), string, i);
2622 }
2623 function d3_time_parseLocaleDate(date, string, i) {
2624 return d3_time_parse(date, d3_time_formats.x.toString(), string, i);
2625 }
2626 function d3_time_parseLocaleTime(date, string, i) {
2627 return d3_time_parse(date, d3_time_formats.X.toString(), string, i);
2628 }
2629 function d3_time_parseAmPm(date, string, i) {
2630 var n = d3_time_periodLookup.get(string.slice(i, i += 2).toLowerCase());
2631 return n == null ? -1 : (date.p = n, i);
2632 }
2633 return d3_time_format;
2634 }
2635 var d3_time_formatPads = {
2636 "-": "",
2637 _: " ",
2638 "0": "0"
2639 }, d3_time_numberRe = /^\s*\d+/, d3_time_percentRe = /^%/;
2640 function d3_time_formatPad(value, fill, width) {
2641 var sign = value < 0 ? "-" : "", string = (sign ? -value : value) + "", length = string.length;
2642 return sign + (length < width ? new Array(width - length + 1).join(fill) + string : string);
2643 }
2644 function d3_time_formatRe(names) {
2645 return new RegExp("^(?:" + names.map(d3.requote).join("|") + ")", "i");
2646 }
2647 function d3_time_formatLookup(names) {
2648 var map = new d3_Map(), i = -1, n = names.length;
2649 while (++i < n) map.set(names[i].toLowerCase(), i);
2650 return map;
2651 }
2652 function d3_time_parseWeekdayNumber(date, string, i) {
2653 d3_time_numberRe.lastIndex = 0;
2654 var n = d3_time_numberRe.exec(string.slice(i, i + 1));
2655 return n ? (date.w = +n[0], i + n[0].length) : -1;
2656 }
2657 function d3_time_parseWeekNumberSunday(date, string, i) {
2658 d3_time_numberRe.lastIndex = 0;
2659 var n = d3_time_numberRe.exec(string.slice(i));
2660 return n ? (date.U = +n[0], i + n[0].length) : -1;
2661 }
2662 function d3_time_parseWeekNumberMonday(date, string, i) {
2663 d3_time_numberRe.lastIndex = 0;
2664 var n = d3_time_numberRe.exec(string.slice(i));
2665 return n ? (date.W = +n[0], i + n[0].length) : -1;
2666 }
2667 function d3_time_parseFullYear(date, string, i) {
2668 d3_time_numberRe.lastIndex = 0;
2669 var n = d3_time_numberRe.exec(string.slice(i, i + 4));
2670 return n ? (date.y = +n[0], i + n[0].length) : -1;
2671 }
2672 function d3_time_parseYear(date, string, i) {
2673 d3_time_numberRe.lastIndex = 0;
2674 var n = d3_time_numberRe.exec(string.slice(i, i + 2));
2675 return n ? (date.y = d3_time_expandYear(+n[0]), i + n[0].length) : -1;
2676 }
2677 function d3_time_parseZone(date, string, i) {
2678 return /^[+-]\d{4}$/.test(string = string.slice(i, i + 5)) ? (date.Z = -string,
2679 i + 5) : -1;
2680 }
2681 function d3_time_expandYear(d) {
2682 return d + (d > 68 ? 1900 : 2e3);
2683 }
2684 function d3_time_parseMonthNumber(date, string, i) {
2685 d3_time_numberRe.lastIndex = 0;
2686 var n = d3_time_numberRe.exec(string.slice(i, i + 2));
2687 return n ? (date.m = n[0] - 1, i + n[0].length) : -1;
2688 }
2689 function d3_time_parseDay(date, string, i) {
2690 d3_time_numberRe.lastIndex = 0;
2691 var n = d3_time_numberRe.exec(string.slice(i, i + 2));
2692 return n ? (date.d = +n[0], i + n[0].length) : -1;
2693 }
2694 function d3_time_parseDayOfYear(date, string, i) {
2695 d3_time_numberRe.lastIndex = 0;
2696 var n = d3_time_numberRe.exec(string.slice(i, i + 3));
2697 return n ? (date.j = +n[0], i + n[0].length) : -1;
2698 }
2699 function d3_time_parseHour24(date, string, i) {
2700 d3_time_numberRe.lastIndex = 0;
2701 var n = d3_time_numberRe.exec(string.slice(i, i + 2));
2702 return n ? (date.H = +n[0], i + n[0].length) : -1;
2703 }
2704 function d3_time_parseMinutes(date, string, i) {
2705 d3_time_numberRe.lastIndex = 0;
2706 var n = d3_time_numberRe.exec(string.slice(i, i + 2));
2707 return n ? (date.M = +n[0], i + n[0].length) : -1;
2708 }
2709 function d3_time_parseSeconds(date, string, i) {
2710 d3_time_numberRe.lastIndex = 0;
2711 var n = d3_time_numberRe.exec(string.slice(i, i + 2));
2712 return n ? (date.S = +n[0], i + n[0].length) : -1;
2713 }
2714 function d3_time_parseMilliseconds(date, string, i) {
2715 d3_time_numberRe.lastIndex = 0;
2716 var n = d3_time_numberRe.exec(string.slice(i, i + 3));
2717 return n ? (date.L = +n[0], i + n[0].length) : -1;
2718 }
2719 function d3_time_zone(d) {
2720 var z = d.getTimezoneOffset(), zs = z > 0 ? "-" : "+", zh = abs(z) / 60 | 0, zm = abs(z) % 60;
2721 return zs + d3_time_formatPad(zh, "0", 2) + d3_time_formatPad(zm, "0", 2);
2722 }
2723 function d3_time_parseLiteralPercent(date, string, i) {
2724 d3_time_percentRe.lastIndex = 0;
2725 var n = d3_time_percentRe.exec(string.slice(i, i + 1));
2726 return n ? i + n[0].length : -1;
2727 }
2728 function d3_time_formatMulti(formats) {
2729 var n = formats.length, i = -1;
2730 while (++i < n) formats[i][0] = this(formats[i][0]);
2731 return function(date) {
2732 var i = 0, f = formats[i];
2733 while (!f[1](date)) f = formats[++i];
2734 return f[0](date);
2735 };
2736 }
2737 d3.locale = function(locale) {
2738 return {
2739 numberFormat: d3_locale_numberFormat(locale),
2740 timeFormat: d3_locale_timeFormat(locale)
2741 };
2742 };
2743 var d3_locale_enUS = d3.locale({
2744 decimal: ".",
2745 thousands: ",",
2746 grouping: [ 3 ],
2747 currency: [ "$", "" ],
2748 dateTime: "%a %b %e %X %Y",
2749 date: "%m/%d/%Y",
2750 time: "%H:%M:%S",
2751 periods: [ "AM", "PM" ],
2752 days: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ],
2753 shortDays: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ],
2754 months: [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ],
2755 shortMonths: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ]
2756 });
2757 d3.format = d3_locale_enUS.numberFormat;
2758 d3.geo = {};
2759 function d3_adder() {}
2760 d3_adder.prototype = {
2761 s: 0,
2762 t: 0,
2763 add: function(y) {
2764 d3_adderSum(y, this.t, d3_adderTemp);
2765 d3_adderSum(d3_adderTemp.s, this.s, this);
2766 if (this.s) this.t += d3_adderTemp.t; else this.s = d3_adderTemp.t;
2767 },
2768 reset: function() {
2769 this.s = this.t = 0;
2770 },
2771 valueOf: function() {
2772 return this.s;
2773 }
2774 };
2775 var d3_adderTemp = new d3_adder();
2776 function d3_adderSum(a, b, o) {
2777 var x = o.s = a + b, bv = x - a, av = x - bv;
2778 o.t = a - av + (b - bv);
2779 }
2780 d3.geo.stream = function(object, listener) {
2781 if (object && d3_geo_streamObjectType.hasOwnProperty(object.type)) {
2782 d3_geo_streamObjectType[object.type](object, listener);
2783 } else {
2784 d3_geo_streamGeometry(object, listener);
2785 }
2786 };
2787 function d3_geo_streamGeometry(geometry, listener) {
2788 if (geometry && d3_geo_streamGeometryType.hasOwnProperty(geometry.type)) {
2789 d3_geo_streamGeometryType[geometry.type](geometry, listener);
2790 }
2791 }
2792 var d3_geo_streamObjectType = {
2793 Feature: function(feature, listener) {
2794 d3_geo_streamGeometry(feature.geometry, listener);
2795 },
2796 FeatureCollection: function(object, listener) {
2797 var features = object.features, i = -1, n = features.length;
2798 while (++i < n) d3_geo_streamGeometry(features[i].geometry, listener);
2799 }
2800 };
2801 var d3_geo_streamGeometryType = {
2802 Sphere: function(object, listener) {
2803 listener.sphere();
2804 },
2805 Point: function(object, listener) {
2806 object = object.coordinates;
2807 listener.point(object[0], object[1], object[2]);
2808 },
2809 MultiPoint: function(object, listener) {
2810 var coordinates = object.coordinates, i = -1, n = coordinates.length;
2811 while (++i < n) object = coordinates[i], listener.point(object[0], object[1], object[2]);
2812 },
2813 LineString: function(object, listener) {
2814 d3_geo_streamLine(object.coordinates, listener, 0);
2815 },
2816 MultiLineString: function(object, listener) {
2817 var coordinates = object.coordinates, i = -1, n = coordinates.length;
2818 while (++i < n) d3_geo_streamLine(coordinates[i], listener, 0);
2819 },
2820 Polygon: function(object, listener) {
2821 d3_geo_streamPolygon(object.coordinates, listener);
2822 },
2823 MultiPolygon: function(object, listener) {
2824 var coordinates = object.coordinates, i = -1, n = coordinates.length;
2825 while (++i < n) d3_geo_streamPolygon(coordinates[i], listener);
2826 },
2827 GeometryCollection: function(object, listener) {
2828 var geometries = object.geometries, i = -1, n = geometries.length;
2829 while (++i < n) d3_geo_streamGeometry(geometries[i], listener);
2830 }
2831 };
2832 function d3_geo_streamLine(coordinates, listener, closed) {
2833 var i = -1, n = coordinates.length - closed, coordinate;
2834 listener.lineStart();
2835 while (++i < n) coordinate = coordinates[i], listener.point(coordinate[0], coordinate[1], coordinate[2]);
2836 listener.lineEnd();
2837 }
2838 function d3_geo_streamPolygon(coordinates, listener) {
2839 var i = -1, n = coordinates.length;
2840 listener.polygonStart();
2841 while (++i < n) d3_geo_streamLine(coordinates[i], listener, 1);
2842 listener.polygonEnd();
2843 }
2844 d3.geo.area = function(object) {
2845 d3_geo_areaSum = 0;
2846 d3.geo.stream(object, d3_geo_area);
2847 return d3_geo_areaSum;
2848 };
2849 var d3_geo_areaSum, d3_geo_areaRingSum = new d3_adder();
2850 var d3_geo_area = {
2851 sphere: function() {
2852 d3_geo_areaSum += 4 * π;
2853 },
2854 point: d3_noop,
2855 lineStart: d3_noop,
2856 lineEnd: d3_noop,
2857 polygonStart: function() {
2858 d3_geo_areaRingSum.reset();
2859 d3_geo_area.lineStart = d3_geo_areaRingStart;
2860 },
2861 polygonEnd: function() {
2862 var area = 2 * d3_geo_areaRingSum;
2863 d3_geo_areaSum += area < 0 ? 4 * π + area : area;
2864 d3_geo_area.lineStart = d3_geo_area.lineEnd = d3_geo_area.point = d3_noop;
2865 }
2866 };
2867 function d3_geo_areaRingStart() {
2868 var λ00, φ00, λ0, cosφ0, sinφ0;
2869 d3_geo_area.point = function(λ, φ) {
2870 d3_geo_area.point = nextPoint;
2871 λ0 = (λ00 = λ) * d3_radians, cosφ0 = Math.cos(φ = (φ00 = φ) * d3_radians / 2 + π / 4),
2872 sinφ0 = Math.sin(φ);
2873 };
2874 function nextPoint(λ, φ) {
2875 λ *= d3_radians;
2876 φ = φ * d3_radians / 2 + π / 4;
2877 var dλ = λ - λ0, sdλ = dλ >= 0 ? 1 : -1, adλ = sdλ * dλ, cosφ = Math.cos(φ), sinφ = Math.sin(φ), k = sinφ0 * sinφ, u = cosφ0 * cosφ + k * Math.cos(adλ), v = k * sdλ * Math.sin(adλ);
2878 d3_geo_areaRingSum.add(Math.atan2(v, u));
2879 λ0 = λ, cosφ0 = cosφ, sinφ0 = sinφ;
2880 }
2881 d3_geo_area.lineEnd = function() {
2882 nextPoint(λ00, φ00);
2883 };
2884 }
2885 function d3_geo_cartesian(spherical) {
2886 var λ = spherical[0], φ = spherical[1], cosφ = Math.cos(φ);
2887 return [ cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ) ];
2888 }
2889 function d3_geo_cartesianDot(a, b) {
2890 return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
2891 }
2892 function d3_geo_cartesianCross(a, b) {
2893 return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0] ];
2894 }
2895 function d3_geo_cartesianAdd(a, b) {
2896 a[0] += b[0];
2897 a[1] += b[1];
2898 a[2] += b[2];
2899 }
2900 function d3_geo_cartesianScale(vector, k) {
2901 return [ vector[0] * k, vector[1] * k, vector[2] * k ];
2902 }
2903 function d3_geo_cartesianNormalize(d) {
2904 var l = Math.sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]);
2905 d[0] /= l;
2906 d[1] /= l;
2907 d[2] /= l;
2908 }
2909 function d3_geo_spherical(cartesian) {
2910 return [ Math.atan2(cartesian[1], cartesian[0]), d3_asin(cartesian[2]) ];
2911 }
2912 function d3_geo_sphericalEqual(a, b) {
2913 return abs(a[0] - b[0]) < ε && abs(a[1] - b[1]) < ε;
2914 }
2915 d3.geo.bounds = function() {
2916 var λ0, φ0, λ1, φ1, λ_, λ__, φ__, p0, dλSum, ranges, range;
2917 var bound = {
2918 point: point,
2919 lineStart: lineStart,
2920 lineEnd: lineEnd,
2921 polygonStart: function() {
2922 bound.point = ringPoint;
2923 bound.lineStart = ringStart;
2924 bound.lineEnd = ringEnd;
2925 dλSum = 0;
2926 d3_geo_area.polygonStart();
2927 },
2928 polygonEnd: function() {
2929 d3_geo_area.polygonEnd();
2930 bound.point = point;
2931 bound.lineStart = lineStart;
2932 bound.lineEnd = lineEnd;
2933 if (d3_geo_areaRingSum < 0) λ0 = -(λ1 = 180), φ0 = -(φ1 = 90); else if (dλSum > ε) φ1 = 90; else if (dλSum < -ε) φ0 = -90;
2934 range[0] = λ0, range[1] = λ1;
2935 }
2936 };
2937 function point(λ, φ) {
2938 ranges.push(range = [ λ0 = λ, λ1 = λ ]);
2939 if (φ < φ0) φ0 = φ;
2940 if (φ > φ1) φ1 = φ;
2941 }
2942 function linePoint(λ, φ) {
2943 var p = d3_geo_cartesian([ λ * d3_radians, φ * d3_radians ]);
2944 if (p0) {
2945 var normal = d3_geo_cartesianCross(p0, p), equatorial = [ normal[1], -normal[0], 0 ], inflection = d3_geo_cartesianCross(equatorial, normal);
2946 d3_geo_cartesianNormalize(inflection);
2947 inflection = d3_geo_spherical(inflection);
2948 var dλ = λ - λ_, s = dλ > 0 ? 1 : -1, λi = inflection[0] * d3_degrees * s, antimeridian = abs(dλ) > 180;
2949 if (antimeridian ^ (s * λ_ < λi && λi < s * λ)) {
2950 var φi = inflection[1] * d3_degrees;
2951 if (φi > φ1) φ1 = φi;
2952 } else if (λi = (λi + 360) % 360 - 180, antimeridian ^ (s * λ_ < λi && λi < s * λ)) {
2953 var φi = -inflection[1] * d3_degrees;
2954 if (φi < φ0) φ0 = φi;
2955 } else {
2956 if (φ < φ0) φ0 = φ;
2957 if (φ > φ1) φ1 = φ;
2958 }
2959 if (antimeridian) {
2960 if (λ < λ_) {
2961 if (angle(λ0, λ) > angle(λ0, λ1)) λ1 = λ;
2962 } else {
2963 if (angle(λ, λ1) > angle(λ0, λ1)) λ0 = λ;
2964 }
2965 } else {
2966 if (λ1 >= λ0) {
2967 if (λ < λ0) λ0 = λ;
2968 if (λ > λ1) λ1 = λ;
2969 } else {
2970 if (λ > λ_) {
2971 if (angle(λ0, λ) > angle(λ0, λ1)) λ1 = λ;
2972 } else {
2973 if (angle(λ, λ1) > angle(λ0, λ1)) λ0 = λ;
2974 }
2975 }
2976 }
2977 } else {
2978 point(λ, φ);
2979 }
2980 p0 = p, λ_ = λ;
2981 }
2982 function lineStart() {
2983 bound.point = linePoint;
2984 }
2985 function lineEnd() {
2986 range[0] = λ0, range[1] = λ1;
2987 bound.point = point;
2988 p0 = null;
2989 }
2990 function ringPoint(λ, φ) {
2991 if (p0) {
2992 var dλ = λ - λ_;
2993 dλSum += abs(dλ) > 180 ? dλ + (dλ > 0 ? 360 : -360) : dλ;
2994 } else λ__ = λ, φ__ = φ;
2995 d3_geo_area.point(λ, φ);
2996 linePoint(λ, φ);
2997 }
2998 function ringStart() {
2999 d3_geo_area.lineStart();
3000 }
3001 function ringEnd() {
3002 ringPoint(λ__, φ__);
3003 d3_geo_area.lineEnd();
3004 if (abs(dλSum) > ε) λ0 = -(λ1 = 180);
3005 range[0] = λ0, range[1] = λ1;
3006 p0 = null;
3007 }
3008 function angle(λ0, λ1) {
3009 return (λ1 -= λ0) < 0 ? λ1 + 360 : λ1;
3010 }
3011 function compareRanges(a, b) {
3012 return a[0] - b[0];
3013 }
3014 function withinRange(x, range) {
3015 return range[0] <= range[1] ? range[0] <= x && x <= range[1] : x < range[0] || range[1] < x;
3016 }
3017 return function(feature) {
3018 φ1 = λ1 = -(λ0 = φ0 = Infinity);
3019 ranges = [];
3020 d3.geo.stream(feature, bound);
3021 var n = ranges.length;
3022 if (n) {
3023 ranges.sort(compareRanges);
3024 for (var i = 1, a = ranges[0], b, merged = [ a ]; i < n; ++i) {
3025 b = ranges[i];
3026 if (withinRange(b[0], a) || withinRange(b[1], a)) {
3027 if (angle(a[0], b[1]) > angle(a[0], a[1])) a[1] = b[1];
3028 if (angle(b[0], a[1]) > angle(a[0], a[1])) a[0] = b[0];
3029 } else {
3030 merged.push(a = b);
3031 }
3032 }
3033 var best = -Infinity, dλ;
3034 for (var n = merged.length - 1, i = 0, a = merged[n], b; i <= n; a = b, ++i) {
3035 b = merged[i];
3036 if ((dλ = angle(a[1], b[0])) > best) best = dλ, λ0 = b[0], λ1 = a[1];
3037 }
3038 }
3039 ranges = range = null;
3040 return λ0 === Infinity || φ0 === Infinity ? [ [ NaN, NaN ], [ NaN, NaN ] ] : [ [ λ0, φ0 ], [ λ1, φ1 ] ];
3041 };
3042 }();
3043 d3.geo.centroid = function(object) {
3044 d3_geo_centroidW0 = d3_geo_centroidW1 = d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 = d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 = d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0;
3045 d3.geo.stream(object, d3_geo_centroid);
3046 var x = d3_geo_centroidX2, y = d3_geo_centroidY2, z = d3_geo_centroidZ2, m = x * x + y * y + z * z;
3047 if (m < ε2) {
3048 x = d3_geo_centroidX1, y = d3_geo_centroidY1, z = d3_geo_centroidZ1;
3049 if (d3_geo_centroidW1 < ε) x = d3_geo_centroidX0, y = d3_geo_centroidY0, z = d3_geo_centroidZ0;
3050 m = x * x + y * y + z * z;
3051 if (m < ε2) return [ NaN, NaN ];
3052 }
3053 return [ Math.atan2(y, x) * d3_degrees, d3_asin(z / Math.sqrt(m)) * d3_degrees ];
3054 };
3055 var d3_geo_centroidW0, d3_geo_centroidW1, d3_geo_centroidX0, d3_geo_centroidY0, d3_geo_centroidZ0, d3_geo_centroidX1, d3_geo_centroidY1, d3_geo_centroidZ1, d3_geo_centroidX2, d3_geo_centroidY2, d3_geo_centroidZ2;
3056 var d3_geo_centroid = {
3057 sphere: d3_noop,
3058 point: d3_geo_centroidPoint,
3059 lineStart: d3_geo_centroidLineStart,
3060 lineEnd: d3_geo_centroidLineEnd,
3061 polygonStart: function() {
3062 d3_geo_centroid.lineStart = d3_geo_centroidRingStart;
3063 },
3064 polygonEnd: function() {
3065 d3_geo_centroid.lineStart = d3_geo_centroidLineStart;
3066 }
3067 };
3068 function d3_geo_centroidPoint(λ, φ) {
3069 λ *= d3_radians;
3070 var cosφ = Math.cos(φ *= d3_radians);
3071 d3_geo_centroidPointXYZ(cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ));
3072 }
3073 function d3_geo_centroidPointXYZ(x, y, z) {
3074 ++d3_geo_centroidW0;
3075 d3_geo_centroidX0 += (x - d3_geo_centroidX0) / d3_geo_centroidW0;
3076 d3_geo_centroidY0 += (y - d3_geo_centroidY0) / d3_geo_centroidW0;
3077 d3_geo_centroidZ0 += (z - d3_geo_centroidZ0) / d3_geo_centroidW0;
3078 }
3079 function d3_geo_centroidLineStart() {
3080 var x0, y0, z0;
3081 d3_geo_centroid.point = function(λ, φ) {
3082 λ *= d3_radians;
3083 var cosφ = Math.cos(φ *= d3_radians);
3084 x0 = cosφ * Math.cos(λ);
3085 y0 = cosφ * Math.sin(λ);
3086 z0 = Math.sin(φ);
3087 d3_geo_centroid.point = nextPoint;
3088 d3_geo_centroidPointXYZ(x0, y0, z0);
3089 };
3090 function nextPoint(λ, φ) {
3091 λ *= d3_radians;
3092 var cosφ = Math.cos(φ *= d3_radians), x = cosφ * Math.cos(λ), y = cosφ * Math.sin(λ), z = Math.sin(φ), w = Math.atan2(Math.sqrt((w = y0 * z - z0 * y) * w + (w = z0 * x - x0 * z) * w + (w = x0 * y - y0 * x) * w), x0 * x + y0 * y + z0 * z);
3093 d3_geo_centroidW1 += w;
3094 d3_geo_centroidX1 += w * (x0 + (x0 = x));
3095 d3_geo_centroidY1 += w * (y0 + (y0 = y));
3096 d3_geo_centroidZ1 += w * (z0 + (z0 = z));
3097 d3_geo_centroidPointXYZ(x0, y0, z0);
3098 }
3099 }
3100 function d3_geo_centroidLineEnd() {
3101 d3_geo_centroid.point = d3_geo_centroidPoint;
3102 }
3103 function d3_geo_centroidRingStart() {
3104 var λ00, φ00, x0, y0, z0;
3105 d3_geo_centroid.point = function(λ, φ) {
3106 λ00 = λ, φ00 = φ;
3107 d3_geo_centroid.point = nextPoint;
3108 λ *= d3_radians;
3109 var cosφ = Math.cos(φ *= d3_radians);
3110 x0 = cosφ * Math.cos(λ);
3111 y0 = cosφ * Math.sin(λ);
3112 z0 = Math.sin(φ);
3113 d3_geo_centroidPointXYZ(x0, y0, z0);
3114 };
3115 d3_geo_centroid.lineEnd = function() {
3116 nextPoint(λ00, φ00);
3117 d3_geo_centroid.lineEnd = d3_geo_centroidLineEnd;
3118 d3_geo_centroid.point = d3_geo_centroidPoint;
3119 };
3120 function nextPoint(λ, φ) {
3121 λ *= d3_radians;
3122 var cosφ = Math.cos(φ *= d3_radians), x = cosφ * Math.cos(λ), y = cosφ * Math.sin(λ), z = Math.sin(φ), cx = y0 * z - z0 * y, cy = z0 * x - x0 * z, cz = x0 * y - y0 * x, m = Math.sqrt(cx * cx + cy * cy + cz * cz), u = x0 * x + y0 * y + z0 * z, v = m && -d3_acos(u) / m, w = Math.atan2(m, u);
3123 d3_geo_centroidX2 += v * cx;
3124 d3_geo_centroidY2 += v * cy;
3125 d3_geo_centroidZ2 += v * cz;
3126 d3_geo_centroidW1 += w;
3127 d3_geo_centroidX1 += w * (x0 + (x0 = x));
3128 d3_geo_centroidY1 += w * (y0 + (y0 = y));
3129 d3_geo_centroidZ1 += w * (z0 + (z0 = z));
3130 d3_geo_centroidPointXYZ(x0, y0, z0);
3131 }
3132 }
3133 function d3_true() {
3134 return true;
3135 }
3136 function d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener) {
3137 var subject = [], clip = [];
3138 segments.forEach(function(segment) {
3139 if ((n = segment.length - 1) <= 0) return;
3140 var n, p0 = segment[0], p1 = segment[n];
3141 if (d3_geo_sphericalEqual(p0, p1)) {
3142 listener.lineStart();
3143 for (var i = 0; i < n; ++i) listener.point((p0 = segment[i])[0], p0[1]);
3144 listener.lineEnd();
3145 return;
3146 }
3147 var a = new d3_geo_clipPolygonIntersection(p0, segment, null, true), b = new d3_geo_clipPolygonIntersection(p0, null, a, false);
3148 a.o = b;
3149 subject.push(a);
3150 clip.push(b);
3151 a = new d3_geo_clipPolygonIntersection(p1, segment, null, false);
3152 b = new d3_geo_clipPolygonIntersection(p1, null, a, true);
3153 a.o = b;
3154 subject.push(a);
3155 clip.push(b);
3156 });
3157 clip.sort(compare);
3158 d3_geo_clipPolygonLinkCircular(subject);
3159 d3_geo_clipPolygonLinkCircular(clip);
3160 if (!subject.length) return;
3161 for (var i = 0, entry = clipStartInside, n = clip.length; i < n; ++i) {
3162 clip[i].e = entry = !entry;
3163 }
3164 var start = subject[0], points, point;
3165 while (1) {
3166 var current = start, isSubject = true;
3167 while (current.v) if ((current = current.n) === start) return;
3168 points = current.z;
3169 listener.lineStart();
3170 do {
3171 current.v = current.o.v = true;
3172 if (current.e) {
3173 if (isSubject) {
3174 for (var i = 0, n = points.length; i < n; ++i) listener.point((point = points[i])[0], point[1]);
3175 } else {
3176 interpolate(current.x, current.n.x, 1, listener);
3177 }
3178 current = current.n;
3179 } else {
3180 if (isSubject) {
3181 points = current.p.z;
3182 for (var i = points.length - 1; i >= 0; --i) listener.point((point = points[i])[0], point[1]);
3183 } else {
3184 interpolate(current.x, current.p.x, -1, listener);
3185 }
3186 current = current.p;
3187 }
3188 current = current.o;
3189 points = current.z;
3190 isSubject = !isSubject;
3191 } while (!current.v);
3192 listener.lineEnd();
3193 }
3194 }
3195 function d3_geo_clipPolygonLinkCircular(array) {
3196 if (!(n = array.length)) return;
3197 var n, i = 0, a = array[0], b;
3198 while (++i < n) {
3199 a.n = b = array[i];
3200 b.p = a;
3201 a = b;
3202 }
3203 a.n = b = array[0];
3204 b.p = a;
3205 }
3206 function d3_geo_clipPolygonIntersection(point, points, other, entry) {
3207 this.x = point;
3208 this.z = points;
3209 this.o = other;
3210 this.e = entry;
3211 this.v = false;
3212 this.n = this.p = null;
3213 }
3214 function d3_geo_clip(pointVisible, clipLine, interpolate, clipStart) {
3215 return function(rotate, listener) {
3216 var line = clipLine(listener), rotatedClipStart = rotate.invert(clipStart[0], clipStart[1]);
3217 var clip = {
3218 point: point,
3219 lineStart: lineStart,
3220 lineEnd: lineEnd,
3221 polygonStart: function() {
3222 clip.point = pointRing;
3223 clip.lineStart = ringStart;
3224 clip.lineEnd = ringEnd;
3225 segments = [];
3226 polygon = [];
3227 },
3228 polygonEnd: function() {
3229 clip.point = point;
3230 clip.lineStart = lineStart;
3231 clip.lineEnd = lineEnd;
3232 segments = d3.merge(segments);
3233 var clipStartInside = d3_geo_pointInPolygon(rotatedClipStart, polygon);
3234 if (segments.length) {
3235 if (!polygonStarted) listener.polygonStart(), polygonStarted = true;
3236 d3_geo_clipPolygon(segments, d3_geo_clipSort, clipStartInside, interpolate, listener);
3237 } else if (clipStartInside) {
3238 if (!polygonStarted) listener.polygonStart(), polygonStarted = true;
3239 listener.lineStart();
3240 interpolate(null, null, 1, listener);
3241 listener.lineEnd();
3242 }
3243 if (polygonStarted) listener.polygonEnd(), polygonStarted = false;
3244 segments = polygon = null;
3245 },
3246 sphere: function() {
3247 listener.polygonStart();
3248 listener.lineStart();
3249 interpolate(null, null, 1, listener);
3250 listener.lineEnd();
3251 listener.polygonEnd();
3252 }
3253 };
3254 function point(λ, φ) {
3255 var point = rotate(λ, φ);
3256 if (pointVisible(λ = point[0], φ = point[1])) listener.point(λ, φ);
3257 }
3258 function pointLine(λ, φ) {
3259 var point = rotate(λ, φ);
3260 line.point(point[0], point[1]);
3261 }
3262 function lineStart() {
3263 clip.point = pointLine;
3264 line.lineStart();
3265 }
3266 function lineEnd() {
3267 clip.point = point;
3268 line.lineEnd();
3269 }
3270 var segments;
3271 var buffer = d3_geo_clipBufferListener(), ringListener = clipLine(buffer), polygonStarted = false, polygon, ring;
3272 function pointRing(λ, φ) {
3273 ring.push([ λ, φ ]);
3274 var point = rotate(λ, φ);
3275 ringListener.point(point[0], point[1]);
3276 }
3277 function ringStart() {
3278 ringListener.lineStart();
3279 ring = [];
3280 }
3281 function ringEnd() {
3282 pointRing(ring[0][0], ring[0][1]);
3283 ringListener.lineEnd();
3284 var clean = ringListener.clean(), ringSegments = buffer.buffer(), segment, n = ringSegments.length;
3285 ring.pop();
3286 polygon.push(ring);
3287 ring = null;
3288 if (!n) return;
3289 if (clean & 1) {
3290 segment = ringSegments[0];
3291 var n = segment.length - 1, i = -1, point;
3292 if (n > 0) {
3293 if (!polygonStarted) listener.polygonStart(), polygonStarted = true;
3294 listener.lineStart();
3295 while (++i < n) listener.point((point = segment[i])[0], point[1]);
3296 listener.lineEnd();
3297 }
3298 return;
3299 }
3300 if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift()));
3301 segments.push(ringSegments.filter(d3_geo_clipSegmentLength1));
3302 }
3303 return clip;
3304 };
3305 }
3306 function d3_geo_clipSegmentLength1(segment) {
3307 return segment.length > 1;
3308 }
3309 function d3_geo_clipBufferListener() {
3310 var lines = [], line;
3311 return {
3312 lineStart: function() {
3313 lines.push(line = []);
3314 },
3315 point: function(λ, φ) {
3316 line.push([ λ, φ ]);
3317 },
3318 lineEnd: d3_noop,
3319 buffer: function() {
3320 var buffer = lines;
3321 lines = [];
3322 line = null;
3323 return buffer;
3324 },
3325 rejoin: function() {
3326 if (lines.length > 1) lines.push(lines.pop().concat(lines.shift()));
3327 }
3328 };
3329 }
3330 function d3_geo_clipSort(a, b) {
3331 return ((a = a.x)[0] < 0 ? a[1] - halfπ - ε : halfπ - a[1]) - ((b = b.x)[0] < 0 ? b[1] - halfπ - ε : halfπ - b[1]);
3332 }
3333 var d3_geo_clipAntimeridian = d3_geo_clip(d3_true, d3_geo_clipAntimeridianLine, d3_geo_clipAntimeridianInterpolate, [ -π, -π / 2 ]);
3334 function d3_geo_clipAntimeridianLine(listener) {
3335 var λ0 = NaN, φ0 = NaN, sλ0 = NaN, clean;
3336 return {
3337 lineStart: function() {
3338 listener.lineStart();
3339 clean = 1;
3340 },
3341 point: function(λ1, φ1) {
3342 var sλ1 = λ1 > 0 ? π : -π, dλ = abs(λ1 - λ0);
3343 if (abs(dλ - π) < ε) {
3344 listener.point(λ0, φ0 = (φ0 + φ1) / 2 > 0 ? halfπ : -halfπ);
3345 listener.point(sλ0, φ0);
3346 listener.lineEnd();
3347 listener.lineStart();
3348 listener.point(sλ1, φ0);
3349 listener.point(λ1, φ0);
3350 clean = 0;
3351 } else if (sλ0 !== sλ1 && dλ >= π) {
3352 if (abs(λ0 - sλ0) < ε) λ0 -= sλ0 * ε;
3353 if (abs(λ1 - sλ1) < ε) λ1 -= sλ1 * ε;
3354 φ0 = d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1);
3355 listener.point(sλ0, φ0);
3356 listener.lineEnd();
3357 listener.lineStart();
3358 listener.point(sλ1, φ0);
3359 clean = 0;
3360 }
3361 listener.point(λ0 = λ1, φ0 = φ1);
3362 sλ0 = sλ1;
3363 },
3364 lineEnd: function() {
3365 listener.lineEnd();
3366 λ0 = φ0 = NaN;
3367 },
3368 clean: function() {
3369 return 2 - clean;
3370 }
3371 };
3372 }
3373 function d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1) {
3374 var cosφ0, cosφ1, sinλ0_λ1 = Math.sin(λ0 - λ1);
3375 return abs(sinλ0_λ1) > ε ? Math.atan((Math.sin(φ0) * (cosφ1 = Math.cos(φ1)) * Math.sin(λ1) - Math.sin(φ1) * (cosφ0 = Math.cos(φ0)) * Math.sin(λ0)) / (cosφ0 * cosφ1 * sinλ0_λ1)) : (φ0 + φ1) / 2;
3376 }
3377 function d3_geo_clipAntimeridianInterpolate(from, to, direction, listener) {
3378 var φ;
3379 if (from == null) {
3380 φ = direction * halfπ;
3381 listener.point(-π, φ);
3382 listener.point(0, φ);
3383 listener.point(π, φ);
3384 listener.point(π, 0);
3385 listener.point(π, -φ);
3386 listener.point(0, -φ);
3387 listener.point(-π, -φ);
3388 listener.point(-π, 0);
3389 listener.point(-π, φ);
3390 } else if (abs(from[0] - to[0]) > ε) {
3391 var s = from[0] < to[0] ? π : -π;
3392 φ = direction * s / 2;
3393 listener.point(-s, φ);
3394 listener.point(0, φ);
3395 listener.point(s, φ);
3396 } else {
3397 listener.point(to[0], to[1]);
3398 }
3399 }
3400 function d3_geo_pointInPolygon(point, polygon) {
3401 var meridian = point[0], parallel = point[1], meridianNormal = [ Math.sin(meridian), -Math.cos(meridian), 0 ], polarAngle = 0, winding = 0;
3402 d3_geo_areaRingSum.reset();
3403 for (var i = 0, n = polygon.length; i < n; ++i) {
3404 var ring = polygon[i], m = ring.length;
3405 if (!m) continue;
3406 var point0 = ring[0], λ0 = point0[0], φ0 = point0[1] / 2 + π / 4, sinφ0 = Math.sin(φ0), cosφ0 = Math.cos(φ0), j = 1;
3407 while (true) {
3408 if (j === m) j = 0;
3409 point = ring[j];
3410 var λ = point[0], φ = point[1] / 2 + π / 4, sinφ = Math.sin(φ), cosφ = Math.cos(φ), dλ = λ - λ0, sdλ = dλ >= 0 ? 1 : -1, adλ = sdλ * dλ, antimeridian = adλ > π, k = sinφ0 * sinφ;
3411 d3_geo_areaRingSum.add(Math.atan2(k * sdλ * Math.sin(adλ), cosφ0 * cosφ + k * Math.cos(adλ)));
3412 polarAngle += antimeridian ? dλ + sdλ * τ : dλ;
3413 if (antimeridian ^ λ0 >= meridian ^ λ >= meridian) {
3414 var arc = d3_geo_cartesianCross(d3_geo_cartesian(point0), d3_geo_cartesian(point));
3415 d3_geo_cartesianNormalize(arc);
3416 var intersection = d3_geo_cartesianCross(meridianNormal, arc);
3417 d3_geo_cartesianNormalize(intersection);
3418 var φarc = (antimeridian ^ dλ >= 0 ? -1 : 1) * d3_asin(intersection[2]);
3419 if (parallel > φarc || parallel === φarc && (arc[0] || arc[1])) {
3420 winding += antimeridian ^ dλ >= 0 ? 1 : -1;
3421 }
3422 }
3423 if (!j++) break;
3424 λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ, point0 = point;
3425 }
3426 }
3427 return (polarAngle < -ε || polarAngle < ε && d3_geo_areaRingSum < 0) ^ winding & 1;
3428 }
3429 function d3_geo_clipCircle(radius) {
3430 var cr = Math.cos(radius), smallRadius = cr > 0, notHemisphere = abs(cr) > ε, interpolate = d3_geo_circleInterpolate(radius, 6 * d3_radians);
3431 return d3_geo_clip(visible, clipLine, interpolate, smallRadius ? [ 0, -radius ] : [ -π, radius - π ]);
3432 function visible(λ, φ) {
3433 return Math.cos(λ) * Math.cos(φ) > cr;
3434 }
3435 function clipLine(listener) {
3436 var point0, c0, v0, v00, clean;
3437 return {
3438 lineStart: function() {
3439 v00 = v0 = false;
3440 clean = 1;
3441 },
3442 point: function(λ, φ) {
3443 var point1 = [ λ, φ ], point2, v = visible(λ, φ), c = smallRadius ? v ? 0 : code(λ, φ) : v ? code(λ + (λ < 0 ? π : -π), φ) : 0;
3444 if (!point0 && (v00 = v0 = v)) listener.lineStart();
3445 if (v !== v0) {
3446 point2 = intersect(point0, point1);
3447 if (d3_geo_sphericalEqual(point0, point2) || d3_geo_sphericalEqual(point1, point2)) {
3448 point1[0] += ε;
3449 point1[1] += ε;
3450 v = visible(point1[0], point1[1]);
3451 }
3452 }
3453 if (v !== v0) {
3454 clean = 0;
3455 if (v) {
3456 listener.lineStart();
3457 point2 = intersect(point1, point0);
3458 listener.point(point2[0], point2[1]);
3459 } else {
3460 point2 = intersect(point0, point1);
3461 listener.point(point2[0], point2[1]);
3462 listener.lineEnd();
3463 }
3464 point0 = point2;
3465 } else if (notHemisphere && point0 && smallRadius ^ v) {
3466 var t;
3467 if (!(c & c0) && (t = intersect(point1, point0, true))) {
3468 clean = 0;
3469 if (smallRadius) {
3470 listener.lineStart();
3471 listener.point(t[0][0], t[0][1]);
3472 listener.point(t[1][0], t[1][1]);
3473 listener.lineEnd();
3474 } else {
3475 listener.point(t[1][0], t[1][1]);
3476 listener.lineEnd();
3477 listener.lineStart();
3478 listener.point(t[0][0], t[0][1]);
3479 }
3480 }
3481 }
3482 if (v && (!point0 || !d3_geo_sphericalEqual(point0, point1))) {
3483 listener.point(point1[0], point1[1]);
3484 }
3485 point0 = point1, v0 = v, c0 = c;
3486 },
3487 lineEnd: function() {
3488 if (v0) listener.lineEnd();
3489 point0 = null;
3490 },
3491 clean: function() {
3492 return clean | (v00 && v0) << 1;
3493 }
3494 };
3495 }
3496 function intersect(a, b, two) {
3497 var pa = d3_geo_cartesian(a), pb = d3_geo_cartesian(b);
3498 var n1 = [ 1, 0, 0 ], n2 = d3_geo_cartesianCross(pa, pb), n2n2 = d3_geo_cartesianDot(n2, n2), n1n2 = n2[0], determinant = n2n2 - n1n2 * n1n2;
3499 if (!determinant) return !two && a;
3500 var c1 = cr * n2n2 / determinant, c2 = -cr * n1n2 / determinant, n1xn2 = d3_geo_cartesianCross(n1, n2), A = d3_geo_cartesianScale(n1, c1), B = d3_geo_cartesianScale(n2, c2);
3501 d3_geo_cartesianAdd(A, B);
3502 var u = n1xn2, w = d3_geo_cartesianDot(A, u), uu = d3_geo_cartesianDot(u, u), t2 = w * w - uu * (d3_geo_cartesianDot(A, A) - 1);
3503 if (t2 < 0) return;
3504 var t = Math.sqrt(t2), q = d3_geo_cartesianScale(u, (-w - t) / uu);
3505 d3_geo_cartesianAdd(q, A);
3506 q = d3_geo_spherical(q);
3507 if (!two) return q;
3508 var λ0 = a[0], λ1 = b[0], φ0 = a[1], φ1 = b[1], z;
3509 if (λ1 < λ0) z = λ0, λ0 = λ1, λ1 = z;
3510 var δλ = λ1 - λ0, polar = abs(δλ - π) < ε, meridian = polar || δλ < ε;
3511 if (!polar && φ1 < φ0) z = φ0, φ0 = φ1, φ1 = z;
3512 if (meridian ? polar ? φ0 + φ1 > 0 ^ q[1] < (abs(q[0] - λ0) < ε ? φ0 : φ1) : φ0 <= q[1] && q[1] <= φ1 : δλ > π ^ (λ0 <= q[0] && q[0] <= λ1)) {
3513 var q1 = d3_geo_cartesianScale(u, (-w + t) / uu);
3514 d3_geo_cartesianAdd(q1, A);
3515 return [ q, d3_geo_spherical(q1) ];
3516 }
3517 }
3518 function code(λ, φ) {
3519 var r = smallRadius ? radius : π - radius, code = 0;
3520 if (λ < -r) code |= 1; else if (λ > r) code |= 2;
3521 if (φ < -r) code |= 4; else if (φ > r) code |= 8;
3522 return code;
3523 }
3524 }
3525 function d3_geom_clipLine(x0, y0, x1, y1) {
3526 return function(line) {
3527 var a = line.a, b = line.b, ax = a.x, ay = a.y, bx = b.x, by = b.y, t0 = 0, t1 = 1, dx = bx - ax, dy = by - ay, r;
3528 r = x0 - ax;
3529 if (!dx && r > 0) return;
3530 r /= dx;
3531 if (dx < 0) {
3532 if (r < t0) return;
3533 if (r < t1) t1 = r;
3534 } else if (dx > 0) {
3535 if (r > t1) return;
3536 if (r > t0) t0 = r;
3537 }
3538 r = x1 - ax;
3539 if (!dx && r < 0) return;
3540 r /= dx;
3541 if (dx < 0) {
3542 if (r > t1) return;
3543 if (r > t0) t0 = r;
3544 } else if (dx > 0) {
3545 if (r < t0) return;
3546 if (r < t1) t1 = r;
3547 }
3548 r = y0 - ay;
3549 if (!dy && r > 0) return;
3550 r /= dy;
3551 if (dy < 0) {
3552 if (r < t0) return;
3553 if (r < t1) t1 = r;
3554 } else if (dy > 0) {
3555 if (r > t1) return;
3556 if (r > t0) t0 = r;
3557 }
3558 r = y1 - ay;
3559 if (!dy && r < 0) return;
3560 r /= dy;
3561 if (dy < 0) {
3562 if (r > t1) return;
3563 if (r > t0) t0 = r;
3564 } else if (dy > 0) {
3565 if (r < t0) return;
3566 if (r < t1) t1 = r;
3567 }
3568 if (t0 > 0) line.a = {
3569 x: ax + t0 * dx,
3570 y: ay + t0 * dy
3571 };
3572 if (t1 < 1) line.b = {
3573 x: ax + t1 * dx,
3574 y: ay + t1 * dy
3575 };
3576 return line;
3577 };
3578 }
3579 var d3_geo_clipExtentMAX = 1e9;
3580 d3.geo.clipExtent = function() {
3581 var x0, y0, x1, y1, stream, clip, clipExtent = {
3582 stream: function(output) {
3583 if (stream) stream.valid = false;
3584 stream = clip(output);
3585 stream.valid = true;
3586 return stream;
3587 },
3588 extent: function(_) {
3589 if (!arguments.length) return [ [ x0, y0 ], [ x1, y1 ] ];
3590 clip = d3_geo_clipExtent(x0 = +_[0][0], y0 = +_[0][1], x1 = +_[1][0], y1 = +_[1][1]);
3591 if (stream) stream.valid = false, stream = null;
3592 return clipExtent;
3593 }
3594 };
3595 return clipExtent.extent([ [ 0, 0 ], [ 960, 500 ] ]);
3596 };
3597 function d3_geo_clipExtent(x0, y0, x1, y1) {
3598 return function(listener) {
3599 var listener_ = listener, bufferListener = d3_geo_clipBufferListener(), clipLine = d3_geom_clipLine(x0, y0, x1, y1), segments, polygon, ring;
3600 var clip = {
3601 point: point,
3602 lineStart: lineStart,
3603 lineEnd: lineEnd,
3604 polygonStart: function() {
3605 listener = bufferListener;
3606 segments = [];
3607 polygon = [];
3608 clean = true;
3609 },
3610 polygonEnd: function() {
3611 listener = listener_;
3612 segments = d3.merge(segments);
3613 var clipStartInside = insidePolygon([ x0, y1 ]), inside = clean && clipStartInside, visible = segments.length;
3614 if (inside || visible) {
3615 listener.polygonStart();
3616 if (inside) {
3617 listener.lineStart();
3618 interpolate(null, null, 1, listener);
3619 listener.lineEnd();
3620 }
3621 if (visible) {
3622 d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener);
3623 }
3624 listener.polygonEnd();
3625 }
3626 segments = polygon = ring = null;
3627 }
3628 };
3629 function insidePolygon(p) {
3630 var wn = 0, n = polygon.length, y = p[1];
3631 for (var i = 0; i < n; ++i) {
3632 for (var j = 1, v = polygon[i], m = v.length, a = v[0], b; j < m; ++j) {
3633 b = v[j];
3634 if (a[1] <= y) {
3635 if (b[1] > y && d3_cross2d(a, b, p) > 0) ++wn;
3636 } else {
3637 if (b[1] <= y && d3_cross2d(a, b, p) < 0) --wn;
3638 }
3639 a = b;
3640 }
3641 }
3642 return wn !== 0;
3643 }
3644 function interpolate(from, to, direction, listener) {
3645 var a = 0, a1 = 0;
3646 if (from == null || (a = corner(from, direction)) !== (a1 = corner(to, direction)) || comparePoints(from, to) < 0 ^ direction > 0) {
3647 do {
3648 listener.point(a === 0 || a === 3 ? x0 : x1, a > 1 ? y1 : y0);
3649 } while ((a = (a + direction + 4) % 4) !== a1);
3650 } else {
3651 listener.point(to[0], to[1]);
3652 }
3653 }
3654 function pointVisible(x, y) {
3655 return x0 <= x && x <= x1 && y0 <= y && y <= y1;
3656 }
3657 function point(x, y) {
3658 if (pointVisible(x, y)) listener.point(x, y);
3659 }
3660 var x__, y__, v__, x_, y_, v_, first, clean;
3661 function lineStart() {
3662 clip.point = linePoint;
3663 if (polygon) polygon.push(ring = []);
3664 first = true;
3665 v_ = false;
3666 x_ = y_ = NaN;
3667 }
3668 function lineEnd() {
3669 if (segments) {
3670 linePoint(x__, y__);
3671 if (v__ && v_) bufferListener.rejoin();
3672 segments.push(bufferListener.buffer());
3673 }
3674 clip.point = point;
3675 if (v_) listener.lineEnd();
3676 }
3677 function linePoint(x, y) {
3678 x = Math.max(-d3_geo_clipExtentMAX, Math.min(d3_geo_clipExtentMAX, x));
3679 y = Math.max(-d3_geo_clipExtentMAX, Math.min(d3_geo_clipExtentMAX, y));
3680 var v = pointVisible(x, y);
3681 if (polygon) ring.push([ x, y ]);
3682 if (first) {
3683 x__ = x, y__ = y, v__ = v;
3684 first = false;
3685 if (v) {
3686 listener.lineStart();
3687 listener.point(x, y);
3688 }
3689 } else {
3690 if (v && v_) listener.point(x, y); else {
3691 var l = {
3692 a: {
3693 x: x_,
3694 y: y_
3695 },
3696 b: {
3697 x: x,
3698 y: y
3699 }
3700 };
3701 if (clipLine(l)) {
3702 if (!v_) {
3703 listener.lineStart();
3704 listener.point(l.a.x, l.a.y);
3705 }
3706 listener.point(l.b.x, l.b.y);
3707 if (!v) listener.lineEnd();
3708 clean = false;
3709 } else if (v) {
3710 listener.lineStart();
3711 listener.point(x, y);
3712 clean = false;
3713 }
3714 }
3715 }
3716 x_ = x, y_ = y, v_ = v;
3717 }
3718 return clip;
3719 };
3720 function corner(p, direction) {
3721 return abs(p[0] - x0) < ε ? direction > 0 ? 0 : 3 : abs(p[0] - x1) < ε ? direction > 0 ? 2 : 1 : abs(p[1] - y0) < ε ? direction > 0 ? 1 : 0 : direction > 0 ? 3 : 2;
3722 }
3723 function compare(a, b) {
3724 return comparePoints(a.x, b.x);
3725 }
3726 function comparePoints(a, b) {
3727 var ca = corner(a, 1), cb = corner(b, 1);
3728 return ca !== cb ? ca - cb : ca === 0 ? b[1] - a[1] : ca === 1 ? a[0] - b[0] : ca === 2 ? a[1] - b[1] : b[0] - a[0];
3729 }
3730 }
3731 function d3_geo_compose(a, b) {
3732 function compose(x, y) {
3733 return x = a(x, y), b(x[0], x[1]);
3734 }
3735 if (a.invert && b.invert) compose.invert = function(x, y) {
3736 return x = b.invert(x, y), x && a.invert(x[0], x[1]);
3737 };
3738 return compose;
3739 }
3740 function d3_geo_conic(projectAt) {
3741 var φ0 = 0, φ1 = π / 3, m = d3_geo_projectionMutator(projectAt), p = m(φ0, φ1);
3742 p.parallels = function(_) {
3743 if (!arguments.length) return [ φ0 / π * 180, φ1 / π * 180 ];
3744 return m(φ0 = _[0] * π / 180, φ1 = _[1] * π / 180);
3745 };
3746 return p;
3747 }
3748 function d3_geo_conicEqualArea(φ0, φ1) {
3749 var sinφ0 = Math.sin(φ0), n = (sinφ0 + Math.sin(φ1)) / 2, C = 1 + sinφ0 * (2 * n - sinφ0), ρ0 = Math.sqrt(C) / n;
3750 function forward(λ, φ) {
3751 var ρ = Math.sqrt(C - 2 * n * Math.sin(φ)) / n;
3752 return [ ρ * Math.sin(λ *= n), ρ0 - ρ * Math.cos(λ) ];
3753 }
3754 forward.invert = function(x, y) {
3755 var ρ0_y = ρ0 - y;
3756 return [ Math.atan2(x, ρ0_y) / n, d3_asin((C - (x * x + ρ0_y * ρ0_y) * n * n) / (2 * n)) ];
3757 };
3758 return forward;
3759 }
3760 (d3.geo.conicEqualArea = function() {
3761 return d3_geo_conic(d3_geo_conicEqualArea);
3762 }).raw = d3_geo_conicEqualArea;
3763 d3.geo.albers = function() {
3764 return d3.geo.conicEqualArea().rotate([ 96, 0 ]).center([ -.6, 38.7 ]).parallels([ 29.5, 45.5 ]).scale(1070);
3765 };
3766 d3.geo.albersUsa = function() {
3767 var lower48 = d3.geo.albers();
3768 var alaska = d3.geo.conicEqualArea().rotate([ 154, 0 ]).center([ -2, 58.5 ]).parallels([ 55, 65 ]);
3769 var hawaii = d3.geo.conicEqualArea().rotate([ 157, 0 ]).center([ -3, 19.9 ]).parallels([ 8, 18 ]);
3770 var point, pointStream = {
3771 point: function(x, y) {
3772 point = [ x, y ];
3773 }
3774 }, lower48Point, alaskaPoint, hawaiiPoint;
3775 function albersUsa(coordinates) {
3776 var x = coordinates[0], y = coordinates[1];
3777 point = null;
3778 (lower48Point(x, y), point) || (alaskaPoint(x, y), point) || hawaiiPoint(x, y);
3779 return point;
3780 }
3781 albersUsa.invert = function(coordinates) {
3782 var k = lower48.scale(), t = lower48.translate(), x = (coordinates[0] - t[0]) / k, y = (coordinates[1] - t[1]) / k;
3783 return (y >= .12 && y < .234 && x >= -.425 && x < -.214 ? alaska : y >= .166 && y < .234 && x >= -.214 && x < -.115 ? hawaii : lower48).invert(coordinates);
3784 };
3785 albersUsa.stream = function(stream) {
3786 var lower48Stream = lower48.stream(stream), alaskaStream = alaska.stream(stream), hawaiiStream = hawaii.stream(stream);
3787 return {
3788 point: function(x, y) {
3789 lower48Stream.point(x, y);
3790 alaskaStream.point(x, y);
3791 hawaiiStream.point(x, y);
3792 },
3793 sphere: function() {
3794 lower48Stream.sphere();
3795 alaskaStream.sphere();
3796 hawaiiStream.sphere();
3797 },
3798 lineStart: function() {
3799 lower48Stream.lineStart();
3800 alaskaStream.lineStart();
3801 hawaiiStream.lineStart();
3802 },
3803 lineEnd: function() {
3804 lower48Stream.lineEnd();
3805 alaskaStream.lineEnd();
3806 hawaiiStream.lineEnd();
3807 },
3808 polygonStart: function() {
3809 lower48Stream.polygonStart();
3810 alaskaStream.polygonStart();
3811 hawaiiStream.polygonStart();
3812 },
3813 polygonEnd: function() {
3814 lower48Stream.polygonEnd();
3815 alaskaStream.polygonEnd();
3816 hawaiiStream.polygonEnd();
3817 }
3818 };
3819 };
3820 albersUsa.precision = function(_) {
3821 if (!arguments.length) return lower48.precision();
3822 lower48.precision(_);
3823 alaska.precision(_);
3824 hawaii.precision(_);
3825 return albersUsa;
3826 };
3827 albersUsa.scale = function(_) {
3828 if (!arguments.length) return lower48.scale();
3829 lower48.scale(_);
3830 alaska.scale(_ * .35);
3831 hawaii.scale(_);
3832 return albersUsa.translate(lower48.translate());
3833 };
3834 albersUsa.translate = function(_) {
3835 if (!arguments.length) return lower48.translate();
3836 var k = lower48.scale(), x = +_[0], y = +_[1];
3837 lower48Point = lower48.translate(_).clipExtent([ [ x - .455 * k, y - .238 * k ], [ x + .455 * k, y + .238 * k ] ]).stream(pointStream).point;
3838 alaskaPoint = alaska.translate([ x - .307 * k, y + .201 * k ]).clipExtent([ [ x - .425 * k + ε, y + .12 * k + ε ], [ x - .214 * k - ε, y + .234 * k - ε ] ]).stream(pointStream).point;
3839 hawaiiPoint = hawaii.translate([ x - .205 * k, y + .212 * k ]).clipExtent([ [ x - .214 * k + ε, y + .166 * k + ε ], [ x - .115 * k - ε, y + .234 * k - ε ] ]).stream(pointStream).point;
3840 return albersUsa;
3841 };
3842 return albersUsa.scale(1070);
3843 };
3844 var d3_geo_pathAreaSum, d3_geo_pathAreaPolygon, d3_geo_pathArea = {
3845 point: d3_noop,
3846 lineStart: d3_noop,
3847 lineEnd: d3_noop,
3848 polygonStart: function() {
3849 d3_geo_pathAreaPolygon = 0;
3850 d3_geo_pathArea.lineStart = d3_geo_pathAreaRingStart;
3851 },
3852 polygonEnd: function() {
3853 d3_geo_pathArea.lineStart = d3_geo_pathArea.lineEnd = d3_geo_pathArea.point = d3_noop;
3854 d3_geo_pathAreaSum += abs(d3_geo_pathAreaPolygon / 2);
3855 }
3856 };
3857 function d3_geo_pathAreaRingStart() {
3858 var x00, y00, x0, y0;
3859 d3_geo_pathArea.point = function(x, y) {
3860 d3_geo_pathArea.point = nextPoint;
3861 x00 = x0 = x, y00 = y0 = y;
3862 };
3863 function nextPoint(x, y) {
3864 d3_geo_pathAreaPolygon += y0 * x - x0 * y;
3865 x0 = x, y0 = y;
3866 }
3867 d3_geo_pathArea.lineEnd = function() {
3868 nextPoint(x00, y00);
3869 };
3870 }
3871 var d3_geo_pathBoundsX0, d3_geo_pathBoundsY0, d3_geo_pathBoundsX1, d3_geo_pathBoundsY1;
3872 var d3_geo_pathBounds = {
3873 point: d3_geo_pathBoundsPoint,
3874 lineStart: d3_noop,
3875 lineEnd: d3_noop,
3876 polygonStart: d3_noop,
3877 polygonEnd: d3_noop
3878 };
3879 function d3_geo_pathBoundsPoint(x, y) {
3880 if (x < d3_geo_pathBoundsX0) d3_geo_pathBoundsX0 = x;
3881 if (x > d3_geo_pathBoundsX1) d3_geo_pathBoundsX1 = x;
3882 if (y < d3_geo_pathBoundsY0) d3_geo_pathBoundsY0 = y;
3883 if (y > d3_geo_pathBoundsY1) d3_geo_pathBoundsY1 = y;
3884 }
3885 function d3_geo_pathBuffer() {
3886 var pointCircle = d3_geo_pathBufferCircle(4.5), buffer = [];
3887 var stream = {
3888 point: point,
3889 lineStart: function() {
3890 stream.point = pointLineStart;
3891 },
3892 lineEnd: lineEnd,
3893 polygonStart: function() {
3894 stream.lineEnd = lineEndPolygon;
3895 },
3896 polygonEnd: function() {
3897 stream.lineEnd = lineEnd;
3898 stream.point = point;
3899 },
3900 pointRadius: function(_) {
3901 pointCircle = d3_geo_pathBufferCircle(_);
3902 return stream;
3903 },
3904 result: function() {
3905 if (buffer.length) {
3906 var result = buffer.join("");
3907 buffer = [];
3908 return result;
3909 }
3910 }
3911 };
3912 function point(x, y) {
3913 buffer.push("M", x, ",", y, pointCircle);
3914 }
3915 function pointLineStart(x, y) {
3916 buffer.push("M", x, ",", y);
3917 stream.point = pointLine;
3918 }
3919 function pointLine(x, y) {
3920 buffer.push("L", x, ",", y);
3921 }
3922 function lineEnd() {
3923 stream.point = point;
3924 }
3925 function lineEndPolygon() {
3926 buffer.push("Z");
3927 }
3928 return stream;
3929 }
3930 function d3_geo_pathBufferCircle(radius) {
3931 return "m0," + radius + "a" + radius + "," + radius + " 0 1,1 0," + -2 * radius + "a" + radius + "," + radius + " 0 1,1 0," + 2 * radius + "z";
3932 }
3933 var d3_geo_pathCentroid = {
3934 point: d3_geo_pathCentroidPoint,
3935 lineStart: d3_geo_pathCentroidLineStart,
3936 lineEnd: d3_geo_pathCentroidLineEnd,
3937 polygonStart: function() {
3938 d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidRingStart;
3939 },
3940 polygonEnd: function() {
3941 d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint;
3942 d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidLineStart;
3943 d3_geo_pathCentroid.lineEnd = d3_geo_pathCentroidLineEnd;
3944 }
3945 };
3946 function d3_geo_pathCentroidPoint(x, y) {
3947 d3_geo_centroidX0 += x;
3948 d3_geo_centroidY0 += y;
3949 ++d3_geo_centroidZ0;
3950 }
3951 function d3_geo_pathCentroidLineStart() {
3952 var x0, y0;
3953 d3_geo_pathCentroid.point = function(x, y) {
3954 d3_geo_pathCentroid.point = nextPoint;
3955 d3_geo_pathCentroidPoint(x0 = x, y0 = y);
3956 };
3957 function nextPoint(x, y) {
3958 var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy);
3959 d3_geo_centroidX1 += z * (x0 + x) / 2;
3960 d3_geo_centroidY1 += z * (y0 + y) / 2;
3961 d3_geo_centroidZ1 += z;
3962 d3_geo_pathCentroidPoint(x0 = x, y0 = y);
3963 }
3964 }
3965 function d3_geo_pathCentroidLineEnd() {
3966 d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint;
3967 }
3968 function d3_geo_pathCentroidRingStart() {
3969 var x00, y00, x0, y0;
3970 d3_geo_pathCentroid.point = function(x, y) {
3971 d3_geo_pathCentroid.point = nextPoint;
3972 d3_geo_pathCentroidPoint(x00 = x0 = x, y00 = y0 = y);
3973 };
3974 function nextPoint(x, y) {
3975 var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy);
3976 d3_geo_centroidX1 += z * (x0 + x) / 2;
3977 d3_geo_centroidY1 += z * (y0 + y) / 2;
3978 d3_geo_centroidZ1 += z;
3979 z = y0 * x - x0 * y;
3980 d3_geo_centroidX2 += z * (x0 + x);
3981 d3_geo_centroidY2 += z * (y0 + y);
3982 d3_geo_centroidZ2 += z * 3;
3983 d3_geo_pathCentroidPoint(x0 = x, y0 = y);
3984 }
3985 d3_geo_pathCentroid.lineEnd = function() {
3986 nextPoint(x00, y00);
3987 };
3988 }
3989 function d3_geo_pathContext(context) {
3990 var pointRadius = 4.5;
3991 var stream = {
3992 point: point,
3993 lineStart: function() {
3994 stream.point = pointLineStart;
3995 },
3996 lineEnd: lineEnd,
3997 polygonStart: function() {
3998 stream.lineEnd = lineEndPolygon;
3999 },
4000 polygonEnd: function() {
4001 stream.lineEnd = lineEnd;
4002 stream.point = point;
4003 },
4004 pointRadius: function(_) {
4005 pointRadius = _;
4006 return stream;
4007 },
4008 result: d3_noop
4009 };
4010 function point(x, y) {
4011 context.moveTo(x, y);
4012 context.arc(x, y, pointRadius, 0, τ);
4013 }
4014 function pointLineStart(x, y) {
4015 context.moveTo(x, y);
4016 stream.point = pointLine;
4017 }
4018 function pointLine(x, y) {
4019 context.lineTo(x, y);
4020 }
4021 function lineEnd() {
4022 stream.point = point;
4023 }
4024 function lineEndPolygon() {
4025 context.closePath();
4026 }
4027 return stream;
4028 }
4029 function d3_geo_resample(project) {
4030 var δ2 = .5, cosMinDistance = Math.cos(30 * d3_radians), maxDepth = 16;
4031 function resample(stream) {
4032 return (maxDepth ? resampleRecursive : resampleNone)(stream);
4033 }
4034 function resampleNone(stream) {
4035 return d3_geo_transformPoint(stream, function(x, y) {
4036 x = project(x, y);
4037 stream.point(x[0], x[1]);
4038 });
4039 }
4040 function resampleRecursive(stream) {
4041 var λ00, φ00, x00, y00, a00, b00, c00, λ0, x0, y0, a0, b0, c0;
4042 var resample = {
4043 point: point,
4044 lineStart: lineStart,
4045 lineEnd: lineEnd,
4046 polygonStart: function() {
4047 stream.polygonStart();
4048 resample.lineStart = ringStart;
4049 },
4050 polygonEnd: function() {
4051 stream.polygonEnd();
4052 resample.lineStart = lineStart;
4053 }
4054 };
4055 function point(x, y) {
4056 x = project(x, y);
4057 stream.point(x[0], x[1]);
4058 }
4059 function lineStart() {
4060 x0 = NaN;
4061 resample.point = linePoint;
4062 stream.lineStart();
4063 }
4064 function linePoint(λ, φ) {
4065 var c = d3_geo_cartesian([ λ, φ ]), p = project(λ, φ);
4066 resampleLineTo(x0, y0, λ0, a0, b0, c0, x0 = p[0], y0 = p[1], λ0 = λ, a0 = c[0], b0 = c[1], c0 = c[2], maxDepth, stream);
4067 stream.point(x0, y0);
4068 }
4069 function lineEnd() {
4070 resample.point = point;
4071 stream.lineEnd();
4072 }
4073 function ringStart() {
4074 lineStart();
4075 resample.point = ringPoint;
4076 resample.lineEnd = ringEnd;
4077 }
4078 function ringPoint(λ, φ) {
4079 linePoint(λ00 = λ, φ00 = φ), x00 = x0, y00 = y0, a00 = a0, b00 = b0, c00 = c0;
4080 resample.point = linePoint;
4081 }
4082 function ringEnd() {
4083 resampleLineTo(x0, y0, λ0, a0, b0, c0, x00, y00, λ00, a00, b00, c00, maxDepth, stream);
4084 resample.lineEnd = lineEnd;
4085 lineEnd();
4086 }
4087 return resample;
4088 }
4089 function resampleLineTo(x0, y0, λ0, a0, b0, c0, x1, y1, λ1, a1, b1, c1, depth, stream) {
4090 var dx = x1 - x0, dy = y1 - y0, d2 = dx * dx + dy * dy;
4091 if (d2 > 4 * δ2 && depth--) {
4092 var a = a0 + a1, b = b0 + b1, c = c0 + c1, m = Math.sqrt(a * a + b * b + c * c), φ2 = Math.asin(c /= m), λ2 = abs(abs(c) - 1) < ε || abs(λ0 - λ1) < ε ? (λ0 + λ1) / 2 : Math.atan2(b, a), p = project(λ2, φ2), x2 = p[0], y2 = p[1], dx2 = x2 - x0, dy2 = y2 - y0, dz = dy * dx2 - dx * dy2;
4093 if (dz * dz / d2 > δ2 || abs((dx * dx2 + dy * dy2) / d2 - .5) > .3 || a0 * a1 + b0 * b1 + c0 * c1 < cosMinDistance) {
4094 resampleLineTo(x0, y0, λ0, a0, b0, c0, x2, y2, λ2, a /= m, b /= m, c, depth, stream);
4095 stream.point(x2, y2);
4096 resampleLineTo(x2, y2, λ2, a, b, c, x1, y1, λ1, a1, b1, c1, depth, stream);
4097 }
4098 }
4099 }
4100 resample.precision = function(_) {
4101 if (!arguments.length) return Math.sqrt(δ2);
4102 maxDepth = (δ2 = _ * _) > 0 && 16;
4103 return resample;
4104 };
4105 return resample;
4106 }
4107 d3.geo.path = function() {
4108 var pointRadius = 4.5, projection, context, projectStream, contextStream, cacheStream;
4109 function path(object) {
4110 if (object) {
4111 if (typeof pointRadius === "function") contextStream.pointRadius(+pointRadius.apply(this, arguments));
4112 if (!cacheStream || !cacheStream.valid) cacheStream = projectStream(contextStream);
4113 d3.geo.stream(object, cacheStream);
4114 }
4115 return contextStream.result();
4116 }
4117 path.area = function(object) {
4118 d3_geo_pathAreaSum = 0;
4119 d3.geo.stream(object, projectStream(d3_geo_pathArea));
4120 return d3_geo_pathAreaSum;
4121 };
4122 path.centroid = function(object) {
4123 d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 = d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 = d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0;
4124 d3.geo.stream(object, projectStream(d3_geo_pathCentroid));
4125 return d3_geo_centroidZ2 ? [ d3_geo_centroidX2 / d3_geo_centroidZ2, d3_geo_centroidY2 / d3_geo_centroidZ2 ] : d3_geo_centroidZ1 ? [ d3_geo_centroidX1 / d3_geo_centroidZ1, d3_geo_centroidY1 / d3_geo_centroidZ1 ] : d3_geo_centroidZ0 ? [ d3_geo_centroidX0 / d3_geo_centroidZ0, d3_geo_centroidY0 / d3_geo_centroidZ0 ] : [ NaN, NaN ];
4126 };
4127 path.bounds = function(object) {
4128 d3_geo_pathBoundsX1 = d3_geo_pathBoundsY1 = -(d3_geo_pathBoundsX0 = d3_geo_pathBoundsY0 = Infinity);
4129 d3.geo.stream(object, projectStream(d3_geo_pathBounds));
4130 return [ [ d3_geo_pathBoundsX0, d3_geo_pathBoundsY0 ], [ d3_geo_pathBoundsX1, d3_geo_pathBoundsY1 ] ];
4131 };
4132 path.projection = function(_) {
4133 if (!arguments.length) return projection;
4134 projectStream = (projection = _) ? _.stream || d3_geo_pathProjectStream(_) : d3_identity;
4135 return reset();
4136 };
4137 path.context = function(_) {
4138 if (!arguments.length) return context;
4139 contextStream = (context = _) == null ? new d3_geo_pathBuffer() : new d3_geo_pathContext(_);
4140 if (typeof pointRadius !== "function") contextStream.pointRadius(pointRadius);
4141 return reset();
4142 };
4143 path.pointRadius = function(_) {
4144 if (!arguments.length) return pointRadius;
4145 pointRadius = typeof _ === "function" ? _ : (contextStream.pointRadius(+_), +_);
4146 return path;
4147 };
4148 function reset() {
4149 cacheStream = null;
4150 return path;
4151 }
4152 return path.projection(d3.geo.albersUsa()).context(null);
4153 };
4154 function d3_geo_pathProjectStream(project) {
4155 var resample = d3_geo_resample(function(x, y) {
4156 return project([ x * d3_degrees, y * d3_degrees ]);
4157 });
4158 return function(stream) {
4159 return d3_geo_projectionRadians(resample(stream));
4160 };
4161 }
4162 d3.geo.transform = function(methods) {
4163 return {
4164 stream: function(stream) {
4165 var transform = new d3_geo_transform(stream);
4166 for (var k in methods) transform[k] = methods[k];
4167 return transform;
4168 }
4169 };
4170 };
4171 function d3_geo_transform(stream) {
4172 this.stream = stream;
4173 }
4174 d3_geo_transform.prototype = {
4175 point: function(x, y) {
4176 this.stream.point(x, y);
4177 },
4178 sphere: function() {
4179 this.stream.sphere();
4180 },
4181 lineStart: function() {
4182 this.stream.lineStart();
4183 },
4184 lineEnd: function() {
4185 this.stream.lineEnd();
4186 },
4187 polygonStart: function() {
4188 this.stream.polygonStart();
4189 },
4190 polygonEnd: function() {
4191 this.stream.polygonEnd();
4192 }
4193 };
4194 function d3_geo_transformPoint(stream, point) {
4195 return {
4196 point: point,
4197 sphere: function() {
4198 stream.sphere();
4199 },
4200 lineStart: function() {
4201 stream.lineStart();
4202 },
4203 lineEnd: function() {
4204 stream.lineEnd();
4205 },
4206 polygonStart: function() {
4207 stream.polygonStart();
4208 },
4209 polygonEnd: function() {
4210 stream.polygonEnd();
4211 }
4212 };
4213 }
4214 d3.geo.projection = d3_geo_projection;
4215 d3.geo.projectionMutator = d3_geo_projectionMutator;
4216 function d3_geo_projection(project) {
4217 return d3_geo_projectionMutator(function() {
4218 return project;
4219 })();
4220 }
4221 function d3_geo_projectionMutator(projectAt) {
4222 var project, rotate, projectRotate, projectResample = d3_geo_resample(function(x, y) {
4223 x = project(x, y);
4224 return [ x[0] * k + δx, δy - x[1] * k ];
4225 }), k = 150, x = 480, y = 250, λ = 0, φ = 0, δλ = 0, δφ = 0, δγ = 0, δx, δy, preclip = d3_geo_clipAntimeridian, postclip = d3_identity, clipAngle = null, clipExtent = null, stream;
4226 function projection(point) {
4227 point = projectRotate(point[0] * d3_radians, point[1] * d3_radians);
4228 return [ point[0] * k + δx, δy - point[1] * k ];
4229 }
4230 function invert(point) {
4231 point = projectRotate.invert((point[0] - δx) / k, (δy - point[1]) / k);
4232 return point && [ point[0] * d3_degrees, point[1] * d3_degrees ];
4233 }
4234 projection.stream = function(output) {
4235 if (stream) stream.valid = false;
4236 stream = d3_geo_projectionRadians(preclip(rotate, projectResample(postclip(output))));
4237 stream.valid = true;
4238 return stream;
4239 };
4240 projection.clipAngle = function(_) {
4241 if (!arguments.length) return clipAngle;
4242 preclip = _ == null ? (clipAngle = _, d3_geo_clipAntimeridian) : d3_geo_clipCircle((clipAngle = +_) * d3_radians);
4243 return invalidate();
4244 };
4245 projection.clipExtent = function(_) {
4246 if (!arguments.length) return clipExtent;
4247 clipExtent = _;
4248 postclip = _ ? d3_geo_clipExtent(_[0][0], _[0][1], _[1][0], _[1][1]) : d3_identity;
4249 return invalidate();
4250 };
4251 projection.scale = function(_) {
4252 if (!arguments.length) return k;
4253 k = +_;
4254 return reset();
4255 };
4256 projection.translate = function(_) {
4257 if (!arguments.length) return [ x, y ];
4258 x = +_[0];
4259 y = +_[1];
4260 return reset();
4261 };
4262 projection.center = function(_) {
4263 if (!arguments.length) return [ λ * d3_degrees, φ * d3_degrees ];
4264 λ = _[0] % 360 * d3_radians;
4265 φ = _[1] % 360 * d3_radians;
4266 return reset();
4267 };
4268 projection.rotate = function(_) {
4269 if (!arguments.length) return [ δλ * d3_degrees, δφ * d3_degrees, δγ * d3_degrees ];
4270 δλ = _[0] % 360 * d3_radians;
4271 δφ = _[1] % 360 * d3_radians;
4272 δγ = _.length > 2 ? _[2] % 360 * d3_radians : 0;
4273 return reset();
4274 };
4275 d3.rebind(projection, projectResample, "precision");
4276 function reset() {
4277 projectRotate = d3_geo_compose(rotate = d3_geo_rotation(δλ, δφ, δγ), project);
4278 var center = project(λ, φ);
4279 δx = x - center[0] * k;
4280 δy = y + center[1] * k;
4281 return invalidate();
4282 }
4283 function invalidate() {
4284 if (stream) stream.valid = false, stream = null;
4285 return projection;
4286 }
4287 return function() {
4288 project = projectAt.apply(this, arguments);
4289 projection.invert = project.invert && invert;
4290 return reset();
4291 };
4292 }
4293 function d3_geo_projectionRadians(stream) {
4294 return d3_geo_transformPoint(stream, function(x, y) {
4295 stream.point(x * d3_radians, y * d3_radians);
4296 });
4297 }
4298 function d3_geo_equirectangular(λ, φ) {
4299 return [ λ, φ ];
4300 }
4301 (d3.geo.equirectangular = function() {
4302 return d3_geo_projection(d3_geo_equirectangular);
4303 }).raw = d3_geo_equirectangular.invert = d3_geo_equirectangular;
4304 d3.geo.rotation = function(rotate) {
4305 rotate = d3_geo_rotation(rotate[0] % 360 * d3_radians, rotate[1] * d3_radians, rotate.length > 2 ? rotate[2] * d3_radians : 0);
4306 function forward(coordinates) {
4307 coordinates = rotate(coordinates[0] * d3_radians, coordinates[1] * d3_radians);
4308 return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates;
4309 }
4310 forward.invert = function(coordinates) {
4311 coordinates = rotate.invert(coordinates[0] * d3_radians, coordinates[1] * d3_radians);
4312 return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates;
4313 };
4314 return forward;
4315 };
4316 function d3_geo_identityRotation(λ, φ) {
4317 return [ λ > π ? λ - τ : λ < -π ? λ + τ : λ, φ ];
4318 }
4319 d3_geo_identityRotation.invert = d3_geo_equirectangular;
4320 function d3_geo_rotation(δλ, δφ, δγ) {
4321 return δλ ? δφ || δγ ? d3_geo_compose(d3_geo_rotationλ(δλ), d3_geo_rotationφγ(δφ, δγ)) : d3_geo_rotationλ(δλ) : δφ || δγ ? d3_geo_rotationφγ(δφ, δγ) : d3_geo_identityRotation;
4322 }
4323 function d3_geo_forwardRotationλ(δλ) {
4324 return function(λ, φ) {
4325 return λ += δλ, [ λ > π ? λ - τ : λ < -π ? λ + τ : λ, φ ];
4326 };
4327 }
4328 function d3_geo_rotationλ(δλ) {
4329 var rotation = d3_geo_forwardRotationλ(δλ);
4330 rotation.invert = d3_geo_forwardRotationλ(-δλ);
4331 return rotation;
4332 }
4333 function d3_geo_rotationφγ(δφ, δγ) {
4334 var cosδφ = Math.cos(δφ), sinδφ = Math.sin(δφ), cosδγ = Math.cos(δγ), sinδγ = Math.sin(δγ);
4335 function rotation(λ, φ) {
4336 var cosφ = Math.cos(φ), x = Math.cos(λ) * cosφ, y = Math.sin(λ) * cosφ, z = Math.sin(φ), k = z * cosδφ + x * sinδφ;
4337 return [ Math.atan2(y * cosδγ - k * sinδγ, x * cosδφ - z * sinδφ), d3_asin(k * cosδγ + y * sinδγ) ];
4338 }
4339 rotation.invert = function(λ, φ) {
4340 var cosφ = Math.cos(φ), x = Math.cos(λ) * cosφ, y = Math.sin(λ) * cosφ, z = Math.sin(φ), k = z * cosδγ - y * sinδγ;
4341 return [ Math.atan2(y * cosδγ + z * sinδγ, x * cosδφ + k * sinδφ), d3_asin(k * cosδφ - x * sinδφ) ];
4342 };
4343 return rotation;
4344 }
4345 d3.geo.circle = function() {
4346 var origin = [ 0, 0 ], angle, precision = 6, interpolate;
4347 function circle() {
4348 var center = typeof origin === "function" ? origin.apply(this, arguments) : origin, rotate = d3_geo_rotation(-center[0] * d3_radians, -center[1] * d3_radians, 0).invert, ring = [];
4349 interpolate(null, null, 1, {
4350 point: function(x, y) {
4351 ring.push(x = rotate(x, y));
4352 x[0] *= d3_degrees, x[1] *= d3_degrees;
4353 }
4354 });
4355 return {
4356 type: "Polygon",
4357 coordinates: [ ring ]
4358 };
4359 }
4360 circle.origin = function(x) {
4361 if (!arguments.length) return origin;
4362 origin = x;
4363 return circle;
4364 };
4365 circle.angle = function(x) {
4366 if (!arguments.length) return angle;
4367 interpolate = d3_geo_circleInterpolate((angle = +x) * d3_radians, precision * d3_radians);
4368 return circle;
4369 };
4370 circle.precision = function(_) {
4371 if (!arguments.length) return precision;
4372 interpolate = d3_geo_circleInterpolate(angle * d3_radians, (precision = +_) * d3_radians);
4373 return circle;
4374 };
4375 return circle.angle(90);
4376 };
4377 function d3_geo_circleInterpolate(radius, precision) {
4378 var cr = Math.cos(radius), sr = Math.sin(radius);
4379 return function(from, to, direction, listener) {
4380 var step = direction * precision;
4381 if (from != null) {
4382 from = d3_geo_circleAngle(cr, from);
4383 to = d3_geo_circleAngle(cr, to);
4384 if (direction > 0 ? from < to : from > to) from += direction * τ;
4385 } else {
4386 from = radius + direction * τ;
4387 to = radius - .5 * step;
4388 }
4389 for (var point, t = from; direction > 0 ? t > to : t < to; t -= step) {
4390 listener.point((point = d3_geo_spherical([ cr, -sr * Math.cos(t), -sr * Math.sin(t) ]))[0], point[1]);
4391 }
4392 };
4393 }
4394 function d3_geo_circleAngle(cr, point) {
4395 var a = d3_geo_cartesian(point);
4396 a[0] -= cr;
4397 d3_geo_cartesianNormalize(a);
4398 var angle = d3_acos(-a[1]);
4399 return ((-a[2] < 0 ? -angle : angle) + 2 * Math.PI - ε) % (2 * Math.PI);
4400 }
4401 d3.geo.distance = function(a, b) {
4402 var Δλ = (b[0] - a[0]) * d3_radians, φ0 = a[1] * d3_radians, φ1 = b[1] * d3_radians, sinΔλ = Math.sin(Δλ), cosΔλ = Math.cos(Δλ), sinφ0 = Math.sin(φ0), cosφ0 = Math.cos(φ0), sinφ1 = Math.sin(φ1), cosφ1 = Math.cos(φ1), t;
4403 return Math.atan2(Math.sqrt((t = cosφ1 * sinΔλ) * t + (t = cosφ0 * sinφ1 - sinφ0 * cosφ1 * cosΔλ) * t), sinφ0 * sinφ1 + cosφ0 * cosφ1 * cosΔλ);
4404 };
4405 d3.geo.graticule = function() {
4406 var x1, x0, X1, X0, y1, y0, Y1, Y0, dx = 10, dy = dx, DX = 90, DY = 360, x, y, X, Y, precision = 2.5;
4407 function graticule() {
4408 return {
4409 type: "MultiLineString",
4410 coordinates: lines()
4411 };
4412 }
4413 function lines() {
4414 return d3.range(Math.ceil(X0 / DX) * DX, X1, DX).map(X).concat(d3.range(Math.ceil(Y0 / DY) * DY, Y1, DY).map(Y)).concat(d3.range(Math.ceil(x0 / dx) * dx, x1, dx).filter(function(x) {
4415 return abs(x % DX) > ε;
4416 }).map(x)).concat(d3.range(Math.ceil(y0 / dy) * dy, y1, dy).filter(function(y) {
4417 return abs(y % DY) > ε;
4418 }).map(y));
4419 }
4420 graticule.lines = function() {
4421 return lines().map(function(coordinates) {
4422 return {
4423 type: "LineString",
4424 coordinates: coordinates
4425 };
4426 });
4427 };
4428 graticule.outline = function() {
4429 return {
4430 type: "Polygon",
4431 coordinates: [ X(X0).concat(Y(Y1).slice(1), X(X1).reverse().slice(1), Y(Y0).reverse().slice(1)) ]
4432 };
4433 };
4434 graticule.extent = function(_) {
4435 if (!arguments.length) return graticule.minorExtent();
4436 return graticule.majorExtent(_).minorExtent(_);
4437 };
4438 graticule.majorExtent = function(_) {
4439 if (!arguments.length) return [ [ X0, Y0 ], [ X1, Y1 ] ];
4440 X0 = +_[0][0], X1 = +_[1][0];
4441 Y0 = +_[0][1], Y1 = +_[1][1];
4442 if (X0 > X1) _ = X0, X0 = X1, X1 = _;
4443 if (Y0 > Y1) _ = Y0, Y0 = Y1, Y1 = _;
4444 return graticule.precision(precision);
4445 };
4446 graticule.minorExtent = function(_) {
4447 if (!arguments.length) return [ [ x0, y0 ], [ x1, y1 ] ];
4448 x0 = +_[0][0], x1 = +_[1][0];
4449 y0 = +_[0][1], y1 = +_[1][1];
4450 if (x0 > x1) _ = x0, x0 = x1, x1 = _;
4451 if (y0 > y1) _ = y0, y0 = y1, y1 = _;
4452 return graticule.precision(precision);
4453 };
4454 graticule.step = function(_) {
4455 if (!arguments.length) return graticule.minorStep();
4456 return graticule.majorStep(_).minorStep(_);
4457 };
4458 graticule.majorStep = function(_) {
4459 if (!arguments.length) return [ DX, DY ];
4460 DX = +_[0], DY = +_[1];
4461 return graticule;
4462 };
4463 graticule.minorStep = function(_) {
4464 if (!arguments.length) return [ dx, dy ];
4465 dx = +_[0], dy = +_[1];
4466 return graticule;
4467 };
4468 graticule.precision = function(_) {
4469 if (!arguments.length) return precision;
4470 precision = +_;
4471 x = d3_geo_graticuleX(y0, y1, 90);
4472 y = d3_geo_graticuleY(x0, x1, precision);
4473 X = d3_geo_graticuleX(Y0, Y1, 90);
4474 Y = d3_geo_graticuleY(X0, X1, precision);
4475 return graticule;
4476 };
4477 return graticule.majorExtent([ [ -180, -90 + ε ], [ 180, 90 - ε ] ]).minorExtent([ [ -180, -80 - ε ], [ 180, 80 + ε ] ]);
4478 };
4479 function d3_geo_graticuleX(y0, y1, dy) {
4480 var y = d3.range(y0, y1 - ε, dy).concat(y1);
4481 return function(x) {
4482 return y.map(function(y) {
4483 return [ x, y ];
4484 });
4485 };
4486 }
4487 function d3_geo_graticuleY(x0, x1, dx) {
4488 var x = d3.range(x0, x1 - ε, dx).concat(x1);
4489 return function(y) {
4490 return x.map(function(x) {
4491 return [ x, y ];
4492 });
4493 };
4494 }
4495 function d3_source(d) {
4496 return d.source;
4497 }
4498 function d3_target(d) {
4499 return d.target;
4500 }
4501 d3.geo.greatArc = function() {
4502 var source = d3_source, source_, target = d3_target, target_;
4503 function greatArc() {
4504 return {
4505 type: "LineString",
4506 coordinates: [ source_ || source.apply(this, arguments), target_ || target.apply(this, arguments) ]
4507 };
4508 }
4509 greatArc.distance = function() {
4510 return d3.geo.distance(source_ || source.apply(this, arguments), target_ || target.apply(this, arguments));
4511 };
4512 greatArc.source = function(_) {
4513 if (!arguments.length) return source;
4514 source = _, source_ = typeof _ === "function" ? null : _;
4515 return greatArc;
4516 };
4517 greatArc.target = function(_) {
4518 if (!arguments.length) return target;
4519 target = _, target_ = typeof _ === "function" ? null : _;
4520 return greatArc;
4521 };
4522 greatArc.precision = function() {
4523 return arguments.length ? greatArc : 0;
4524 };
4525 return greatArc;
4526 };
4527 d3.geo.interpolate = function(source, target) {
4528 return d3_geo_interpolate(source[0] * d3_radians, source[1] * d3_radians, target[0] * d3_radians, target[1] * d3_radians);
4529 };
4530 function d3_geo_interpolate(x0, y0, x1, y1) {
4531 var cy0 = Math.cos(y0), sy0 = Math.sin(y0), cy1 = Math.cos(y1), sy1 = Math.sin(y1), kx0 = cy0 * Math.cos(x0), ky0 = cy0 * Math.sin(x0), kx1 = cy1 * Math.cos(x1), ky1 = cy1 * Math.sin(x1), d = 2 * Math.asin(Math.sqrt(d3_haversin(y1 - y0) + cy0 * cy1 * d3_haversin(x1 - x0))), k = 1 / Math.sin(d);
4532 var interpolate = d ? function(t) {
4533 var B = Math.sin(t *= d) * k, A = Math.sin(d - t) * k, x = A * kx0 + B * kx1, y = A * ky0 + B * ky1, z = A * sy0 + B * sy1;
4534 return [ Math.atan2(y, x) * d3_degrees, Math.atan2(z, Math.sqrt(x * x + y * y)) * d3_degrees ];
4535 } : function() {
4536 return [ x0 * d3_degrees, y0 * d3_degrees ];
4537 };
4538 interpolate.distance = d;
4539 return interpolate;
4540 }
4541 d3.geo.length = function(object) {
4542 d3_geo_lengthSum = 0;
4543 d3.geo.stream(object, d3_geo_length);
4544 return d3_geo_lengthSum;
4545 };
4546 var d3_geo_lengthSum;
4547 var d3_geo_length = {
4548 sphere: d3_noop,
4549 point: d3_noop,
4550 lineStart: d3_geo_lengthLineStart,
4551 lineEnd: d3_noop,
4552 polygonStart: d3_noop,
4553 polygonEnd: d3_noop
4554 };
4555 function d3_geo_lengthLineStart() {
4556 var λ0, sinφ0, cosφ0;
4557 d3_geo_length.point = function(λ, φ) {
4558 λ0 = λ * d3_radians, sinφ0 = Math.sin(φ *= d3_radians), cosφ0 = Math.cos(φ);
4559 d3_geo_length.point = nextPoint;
4560 };
4561 d3_geo_length.lineEnd = function() {
4562 d3_geo_length.point = d3_geo_length.lineEnd = d3_noop;
4563 };
4564 function nextPoint(λ, φ) {
4565 var sinφ = Math.sin(φ *= d3_radians), cosφ = Math.cos(φ), t = abs((λ *= d3_radians) - λ0), cosΔλ = Math.cos(t);
4566 d3_geo_lengthSum += Math.atan2(Math.sqrt((t = cosφ * Math.sin(t)) * t + (t = cosφ0 * sinφ - sinφ0 * cosφ * cosΔλ) * t), sinφ0 * sinφ + cosφ0 * cosφ * cosΔλ);
4567 λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ;
4568 }
4569 }
4570 function d3_geo_azimuthal(scale, angle) {
4571 function azimuthal(λ, φ) {
4572 var cosλ = Math.cos(λ), cosφ = Math.cos(φ), k = scale(cosλ * cosφ);
4573 return [ k * cosφ * Math.sin(λ), k * Math.sin(φ) ];
4574 }
4575 azimuthal.invert = function(x, y) {
4576 var ρ = Math.sqrt(x * x + y * y), c = angle(ρ), sinc = Math.sin(c), cosc = Math.cos(c);
4577 return [ Math.atan2(x * sinc, ρ * cosc), Math.asin(ρ && y * sinc / ρ) ];
4578 };
4579 return azimuthal;
4580 }
4581 var d3_geo_azimuthalEqualArea = d3_geo_azimuthal(function(cosλcosφ) {
4582 return Math.sqrt(2 / (1 + cosλcosφ));
4583 }, function(ρ) {
4584 return 2 * Math.asin(ρ / 2);
4585 });
4586 (d3.geo.azimuthalEqualArea = function() {
4587 return d3_geo_projection(d3_geo_azimuthalEqualArea);
4588 }).raw = d3_geo_azimuthalEqualArea;
4589 var d3_geo_azimuthalEquidistant = d3_geo_azimuthal(function(cosλcosφ) {
4590 var c = Math.acos(cosλcosφ);
4591 return c && c / Math.sin(c);
4592 }, d3_identity);
4593 (d3.geo.azimuthalEquidistant = function() {
4594 return d3_geo_projection(d3_geo_azimuthalEquidistant);
4595 }).raw = d3_geo_azimuthalEquidistant;
4596 function d3_geo_conicConformal(φ0, φ1) {
4597 var cosφ0 = Math.cos(φ0), t = function(φ) {
4598 return Math.tan(π / 4 + φ / 2);
4599 }, n = φ0 === φ1 ? Math.sin(φ0) : Math.log(cosφ0 / Math.cos(φ1)) / Math.log(t(φ1) / t(φ0)), F = cosφ0 * Math.pow(t(φ0), n) / n;
4600 if (!n) return d3_geo_mercator;
4601 function forward(λ, φ) {
4602 if (F > 0) {
4603 if (φ < -halfπ + ε) φ = -halfπ + ε;
4604 } else {
4605 if (φ > halfπ - ε) φ = halfπ - ε;
4606 }
4607 var ρ = F / Math.pow(t(φ), n);
4608 return [ ρ * Math.sin(n * λ), F - ρ * Math.cos(n * λ) ];
4609 }
4610 forward.invert = function(x, y) {
4611 var ρ0_y = F - y, ρ = d3_sgn(n) * Math.sqrt(x * x + ρ0_y * ρ0_y);
4612 return [ Math.atan2(x, ρ0_y) / n, 2 * Math.atan(Math.pow(F / ρ, 1 / n)) - halfπ ];
4613 };
4614 return forward;
4615 }
4616 (d3.geo.conicConformal = function() {
4617 return d3_geo_conic(d3_geo_conicConformal);
4618 }).raw = d3_geo_conicConformal;
4619 function d3_geo_conicEquidistant(φ0, φ1) {
4620 var cosφ0 = Math.cos(φ0), n = φ0 === φ1 ? Math.sin(φ0) : (cosφ0 - Math.cos(φ1)) / (φ1 - φ0), G = cosφ0 / n + φ0;
4621 if (abs(n) < ε) return d3_geo_equirectangular;
4622 function forward(λ, φ) {
4623 var ρ = G - φ;
4624 return [ ρ * Math.sin(n * λ), G - ρ * Math.cos(n * λ) ];
4625 }
4626 forward.invert = function(x, y) {
4627 var ρ0_y = G - y;
4628 return [ Math.atan2(x, ρ0_y) / n, G - d3_sgn(n) * Math.sqrt(x * x + ρ0_y * ρ0_y) ];
4629 };
4630 return forward;
4631 }
4632 (d3.geo.conicEquidistant = function() {
4633 return d3_geo_conic(d3_geo_conicEquidistant);
4634 }).raw = d3_geo_conicEquidistant;
4635 var d3_geo_gnomonic = d3_geo_azimuthal(function(cosλcosφ) {
4636 return 1 / cosλcosφ;
4637 }, Math.atan);
4638 (d3.geo.gnomonic = function() {
4639 return d3_geo_projection(d3_geo_gnomonic);
4640 }).raw = d3_geo_gnomonic;
4641 function d3_geo_mercator(λ, φ) {
4642 return [ λ, Math.log(Math.tan(π / 4 + φ / 2)) ];
4643 }
4644 d3_geo_mercator.invert = function(x, y) {
4645 return [ x, 2 * Math.atan(Math.exp(y)) - halfπ ];
4646 };
4647 function d3_geo_mercatorProjection(project) {
4648 var m = d3_geo_projection(project), scale = m.scale, translate = m.translate, clipExtent = m.clipExtent, clipAuto;
4649 m.scale = function() {
4650 var v = scale.apply(m, arguments);
4651 return v === m ? clipAuto ? m.clipExtent(null) : m : v;
4652 };
4653 m.translate = function() {
4654 var v = translate.apply(m, arguments);
4655 return v === m ? clipAuto ? m.clipExtent(null) : m : v;
4656 };
4657 m.clipExtent = function(_) {
4658 var v = clipExtent.apply(m, arguments);
4659 if (v === m) {
4660 if (clipAuto = _ == null) {
4661 var k = π * scale(), t = translate();
4662 clipExtent([ [ t[0] - k, t[1] - k ], [ t[0] + k, t[1] + k ] ]);
4663 }
4664 } else if (clipAuto) {
4665 v = null;
4666 }
4667 return v;
4668 };
4669 return m.clipExtent(null);
4670 }
4671 (d3.geo.mercator = function() {
4672 return d3_geo_mercatorProjection(d3_geo_mercator);
4673 }).raw = d3_geo_mercator;
4674 var d3_geo_orthographic = d3_geo_azimuthal(function() {
4675 return 1;
4676 }, Math.asin);
4677 (d3.geo.orthographic = function() {
4678 return d3_geo_projection(d3_geo_orthographic);
4679 }).raw = d3_geo_orthographic;
4680 var d3_geo_stereographic = d3_geo_azimuthal(function(cosλcosφ) {
4681 return 1 / (1 + cosλcosφ);
4682 }, function(ρ) {
4683 return 2 * Math.atan(ρ);
4684 });
4685 (d3.geo.stereographic = function() {
4686 return d3_geo_projection(d3_geo_stereographic);
4687 }).raw = d3_geo_stereographic;
4688 function d3_geo_transverseMercator(λ, φ) {
4689 return [ Math.log(Math.tan(π / 4 + φ / 2)), -λ ];
4690 }
4691 d3_geo_transverseMercator.invert = function(x, y) {
4692 return [ -y, 2 * Math.atan(Math.exp(x)) - halfπ ];
4693 };
4694 (d3.geo.transverseMercator = function() {
4695 var projection = d3_geo_mercatorProjection(d3_geo_transverseMercator), center = projection.center, rotate = projection.rotate;
4696 projection.center = function(_) {
4697 return _ ? center([ -_[1], _[0] ]) : (_ = center(), [ _[1], -_[0] ]);
4698 };
4699 projection.rotate = function(_) {
4700 return _ ? rotate([ _[0], _[1], _.length > 2 ? _[2] + 90 : 90 ]) : (_ = rotate(),
4701 [ _[0], _[1], _[2] - 90 ]);
4702 };
4703 return rotate([ 0, 0, 90 ]);
4704 }).raw = d3_geo_transverseMercator;
4705 d3.geom = {};
4706 function d3_geom_pointX(d) {
4707 return d[0];
4708 }
4709 function d3_geom_pointY(d) {
4710 return d[1];
4711 }
4712 d3.geom.hull = function(vertices) {
4713 var x = d3_geom_pointX, y = d3_geom_pointY;
4714 if (arguments.length) return hull(vertices);
4715 function hull(data) {
4716 if (data.length < 3) return [];
4717 var fx = d3_functor(x), fy = d3_functor(y), i, n = data.length, points = [], flippedPoints = [];
4718 for (i = 0; i < n; i++) {
4719 points.push([ +fx.call(this, data[i], i), +fy.call(this, data[i], i), i ]);
4720 }
4721 points.sort(d3_geom_hullOrder);
4722 for (i = 0; i < n; i++) flippedPoints.push([ points[i][0], -points[i][1] ]);
4723 var upper = d3_geom_hullUpper(points), lower = d3_geom_hullUpper(flippedPoints);
4724 var skipLeft = lower[0] === upper[0], skipRight = lower[lower.length - 1] === upper[upper.length - 1], polygon = [];
4725 for (i = upper.length - 1; i >= 0; --i) polygon.push(data[points[upper[i]][2]]);
4726 for (i = +skipLeft; i < lower.length - skipRight; ++i) polygon.push(data[points[lower[i]][2]]);
4727 return polygon;
4728 }
4729 hull.x = function(_) {
4730 return arguments.length ? (x = _, hull) : x;
4731 };
4732 hull.y = function(_) {
4733 return arguments.length ? (y = _, hull) : y;
4734 };
4735 return hull;
4736 };
4737 function d3_geom_hullUpper(points) {
4738 var n = points.length, hull = [ 0, 1 ], hs = 2;
4739 for (var i = 2; i < n; i++) {
4740 while (hs > 1 && d3_cross2d(points[hull[hs - 2]], points[hull[hs - 1]], points[i]) <= 0) --hs;
4741 hull[hs++] = i;
4742 }
4743 return hull.slice(0, hs);
4744 }
4745 function d3_geom_hullOrder(a, b) {
4746 return a[0] - b[0] || a[1] - b[1];
4747 }
4748 d3.geom.polygon = function(coordinates) {
4749 d3_subclass(coordinates, d3_geom_polygonPrototype);
4750 return coordinates;
4751 };
4752 var d3_geom_polygonPrototype = d3.geom.polygon.prototype = [];
4753 d3_geom_polygonPrototype.area = function() {
4754 var i = -1, n = this.length, a, b = this[n - 1], area = 0;
4755 while (++i < n) {
4756 a = b;
4757 b = this[i];
4758 area += a[1] * b[0] - a[0] * b[1];
4759 }
4760 return area * .5;
4761 };
4762 d3_geom_polygonPrototype.centroid = function(k) {
4763 var i = -1, n = this.length, x = 0, y = 0, a, b = this[n - 1], c;
4764 if (!arguments.length) k = -1 / (6 * this.area());
4765 while (++i < n) {
4766 a = b;
4767 b = this[i];
4768 c = a[0] * b[1] - b[0] * a[1];
4769 x += (a[0] + b[0]) * c;
4770 y += (a[1] + b[1]) * c;
4771 }
4772 return [ x * k, y * k ];
4773 };
4774 d3_geom_polygonPrototype.clip = function(subject) {
4775 var input, closed = d3_geom_polygonClosed(subject), i = -1, n = this.length - d3_geom_polygonClosed(this), j, m, a = this[n - 1], b, c, d;
4776 while (++i < n) {
4777 input = subject.slice();
4778 subject.length = 0;
4779 b = this[i];
4780 c = input[(m = input.length - closed) - 1];
4781 j = -1;
4782 while (++j < m) {
4783 d = input[j];
4784 if (d3_geom_polygonInside(d, a, b)) {
4785 if (!d3_geom_polygonInside(c, a, b)) {
4786 subject.push(d3_geom_polygonIntersect(c, d, a, b));
4787 }
4788 subject.push(d);
4789 } else if (d3_geom_polygonInside(c, a, b)) {
4790 subject.push(d3_geom_polygonIntersect(c, d, a, b));
4791 }
4792 c = d;
4793 }
4794 if (closed) subject.push(subject[0]);
4795 a = b;
4796 }
4797 return subject;
4798 };
4799 function d3_geom_polygonInside(p, a, b) {
4800 return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]);
4801 }
4802 function d3_geom_polygonIntersect(c, d, a, b) {
4803 var x1 = c[0], x3 = a[0], x21 = d[0] - x1, x43 = b[0] - x3, y1 = c[1], y3 = a[1], y21 = d[1] - y1, y43 = b[1] - y3, ua = (x43 * (y1 - y3) - y43 * (x1 - x3)) / (y43 * x21 - x43 * y21);
4804 return [ x1 + ua * x21, y1 + ua * y21 ];
4805 }
4806 function d3_geom_polygonClosed(coordinates) {
4807 var a = coordinates[0], b = coordinates[coordinates.length - 1];
4808 return !(a[0] - b[0] || a[1] - b[1]);
4809 }
4810 var d3_geom_voronoiEdges, d3_geom_voronoiCells, d3_geom_voronoiBeaches, d3_geom_voronoiBeachPool = [], d3_geom_voronoiFirstCircle, d3_geom_voronoiCircles, d3_geom_voronoiCirclePool = [];
4811 function d3_geom_voronoiBeach() {
4812 d3_geom_voronoiRedBlackNode(this);
4813 this.edge = this.site = this.circle = null;
4814 }
4815 function d3_geom_voronoiCreateBeach(site) {
4816 var beach = d3_geom_voronoiBeachPool.pop() || new d3_geom_voronoiBeach();
4817 beach.site = site;
4818 return beach;
4819 }
4820 function d3_geom_voronoiDetachBeach(beach) {
4821 d3_geom_voronoiDetachCircle(beach);
4822 d3_geom_voronoiBeaches.remove(beach);
4823 d3_geom_voronoiBeachPool.push(beach);
4824 d3_geom_voronoiRedBlackNode(beach);
4825 }
4826 function d3_geom_voronoiRemoveBeach(beach) {
4827 var circle = beach.circle, x = circle.x, y = circle.cy, vertex = {
4828 x: x,
4829 y: y
4830 }, previous = beach.P, next = beach.N, disappearing = [ beach ];
4831 d3_geom_voronoiDetachBeach(beach);
4832 var lArc = previous;
4833 while (lArc.circle && abs(x - lArc.circle.x) < ε && abs(y - lArc.circle.cy) < ε) {
4834 previous = lArc.P;
4835 disappearing.unshift(lArc);
4836 d3_geom_voronoiDetachBeach(lArc);
4837 lArc = previous;
4838 }
4839 disappearing.unshift(lArc);
4840 d3_geom_voronoiDetachCircle(lArc);
4841 var rArc = next;
4842 while (rArc.circle && abs(x - rArc.circle.x) < ε && abs(y - rArc.circle.cy) < ε) {
4843 next = rArc.N;
4844 disappearing.push(rArc);
4845 d3_geom_voronoiDetachBeach(rArc);
4846 rArc = next;
4847 }
4848 disappearing.push(rArc);
4849 d3_geom_voronoiDetachCircle(rArc);
4850 var nArcs = disappearing.length, iArc;
4851 for (iArc = 1; iArc < nArcs; ++iArc) {
4852 rArc = disappearing[iArc];
4853 lArc = disappearing[iArc - 1];
4854 d3_geom_voronoiSetEdgeEnd(rArc.edge, lArc.site, rArc.site, vertex);
4855 }
4856 lArc = disappearing[0];
4857 rArc = disappearing[nArcs - 1];
4858 rArc.edge = d3_geom_voronoiCreateEdge(lArc.site, rArc.site, null, vertex);
4859 d3_geom_voronoiAttachCircle(lArc);
4860 d3_geom_voronoiAttachCircle(rArc);
4861 }
4862 function d3_geom_voronoiAddBeach(site) {
4863 var x = site.x, directrix = site.y, lArc, rArc, dxl, dxr, node = d3_geom_voronoiBeaches._;
4864 while (node) {
4865 dxl = d3_geom_voronoiLeftBreakPoint(node, directrix) - x;
4866 if (dxl > ε) node = node.L; else {
4867 dxr = x - d3_geom_voronoiRightBreakPoint(node, directrix);
4868 if (dxr > ε) {
4869 if (!node.R) {
4870 lArc = node;
4871 break;
4872 }
4873 node = node.R;
4874 } else {
4875 if (dxl > -ε) {
4876 lArc = node.P;
4877 rArc = node;
4878 } else if (dxr > -ε) {
4879 lArc = node;
4880 rArc = node.N;
4881 } else {
4882 lArc = rArc = node;
4883 }
4884 break;
4885 }
4886 }
4887 }
4888 var newArc = d3_geom_voronoiCreateBeach(site);
4889 d3_geom_voronoiBeaches.insert(lArc, newArc);
4890 if (!lArc && !rArc) return;
4891 if (lArc === rArc) {
4892 d3_geom_voronoiDetachCircle(lArc);
4893 rArc = d3_geom_voronoiCreateBeach(lArc.site);
4894 d3_geom_voronoiBeaches.insert(newArc, rArc);
4895 newArc.edge = rArc.edge = d3_geom_voronoiCreateEdge(lArc.site, newArc.site);
4896 d3_geom_voronoiAttachCircle(lArc);
4897 d3_geom_voronoiAttachCircle(rArc);
4898 return;
4899 }
4900 if (!rArc) {
4901 newArc.edge = d3_geom_voronoiCreateEdge(lArc.site, newArc.site);
4902 return;
4903 }
4904 d3_geom_voronoiDetachCircle(lArc);
4905 d3_geom_voronoiDetachCircle(rArc);
4906 var lSite = lArc.site, ax = lSite.x, ay = lSite.y, bx = site.x - ax, by = site.y - ay, rSite = rArc.site, cx = rSite.x - ax, cy = rSite.y - ay, d = 2 * (bx * cy - by * cx), hb = bx * bx + by * by, hc = cx * cx + cy * cy, vertex = {
4907 x: (cy * hb - by * hc) / d + ax,
4908 y: (bx * hc - cx * hb) / d + ay
4909 };
4910 d3_geom_voronoiSetEdgeEnd(rArc.edge, lSite, rSite, vertex);
4911 newArc.edge = d3_geom_voronoiCreateEdge(lSite, site, null, vertex);
4912 rArc.edge = d3_geom_voronoiCreateEdge(site, rSite, null, vertex);
4913 d3_geom_voronoiAttachCircle(lArc);
4914 d3_geom_voronoiAttachCircle(rArc);
4915 }
4916 function d3_geom_voronoiLeftBreakPoint(arc, directrix) {
4917 var site = arc.site, rfocx = site.x, rfocy = site.y, pby2 = rfocy - directrix;
4918 if (!pby2) return rfocx;
4919 var lArc = arc.P;
4920 if (!lArc) return -Infinity;
4921 site = lArc.site;
4922 var lfocx = site.x, lfocy = site.y, plby2 = lfocy - directrix;
4923 if (!plby2) return lfocx;
4924 var hl = lfocx - rfocx, aby2 = 1 / pby2 - 1 / plby2, b = hl / plby2;
4925 if (aby2) return (-b + Math.sqrt(b * b - 2 * aby2 * (hl * hl / (-2 * plby2) - lfocy + plby2 / 2 + rfocy - pby2 / 2))) / aby2 + rfocx;
4926 return (rfocx + lfocx) / 2;
4927 }
4928 function d3_geom_voronoiRightBreakPoint(arc, directrix) {
4929 var rArc = arc.N;
4930 if (rArc) return d3_geom_voronoiLeftBreakPoint(rArc, directrix);
4931 var site = arc.site;
4932 return site.y === directrix ? site.x : Infinity;
4933 }
4934 function d3_geom_voronoiCell(site) {
4935 this.site = site;
4936 this.edges = [];
4937 }
4938 d3_geom_voronoiCell.prototype.prepare = function() {
4939 var halfEdges = this.edges, iHalfEdge = halfEdges.length, edge;
4940 while (iHalfEdge--) {
4941 edge = halfEdges[iHalfEdge].edge;
4942 if (!edge.b || !edge.a) halfEdges.splice(iHalfEdge, 1);
4943 }
4944 halfEdges.sort(d3_geom_voronoiHalfEdgeOrder);
4945 return halfEdges.length;
4946 };
4947 function d3_geom_voronoiCloseCells(extent) {
4948 var x0 = extent[0][0], x1 = extent[1][0], y0 = extent[0][1], y1 = extent[1][1], x2, y2, x3, y3, cells = d3_geom_voronoiCells, iCell = cells.length, cell, iHalfEdge, halfEdges, nHalfEdges, start, end;
4949 while (iCell--) {
4950 cell = cells[iCell];
4951 if (!cell || !cell.prepare()) continue;
4952 halfEdges = cell.edges;
4953 nHalfEdges = halfEdges.length;
4954 iHalfEdge = 0;
4955 while (iHalfEdge < nHalfEdges) {
4956 end = halfEdges[iHalfEdge].end(), x3 = end.x, y3 = end.y;
4957 start = halfEdges[++iHalfEdge % nHalfEdges].start(), x2 = start.x, y2 = start.y;
4958 if (abs(x3 - x2) > ε || abs(y3 - y2) > ε) {
4959 halfEdges.splice(iHalfEdge, 0, new d3_geom_voronoiHalfEdge(d3_geom_voronoiCreateBorderEdge(cell.site, end, abs(x3 - x0) < ε && y1 - y3 > ε ? {
4960 x: x0,
4961 y: abs(x2 - x0) < ε ? y2 : y1
4962 } : abs(y3 - y1) < ε && x1 - x3 > ε ? {
4963 x: abs(y2 - y1) < ε ? x2 : x1,
4964 y: y1
4965 } : abs(x3 - x1) < ε && y3 - y0 > ε ? {
4966 x: x1,
4967 y: abs(x2 - x1) < ε ? y2 : y0
4968 } : abs(y3 - y0) < ε && x3 - x0 > ε ? {
4969 x: abs(y2 - y0) < ε ? x2 : x0,
4970 y: y0
4971 } : null), cell.site, null));
4972 ++nHalfEdges;
4973 }
4974 }
4975 }
4976 }
4977 function d3_geom_voronoiHalfEdgeOrder(a, b) {
4978 return b.angle - a.angle;
4979 }
4980 function d3_geom_voronoiCircle() {
4981 d3_geom_voronoiRedBlackNode(this);
4982 this.x = this.y = this.arc = this.site = this.cy = null;
4983 }
4984 function d3_geom_voronoiAttachCircle(arc) {
4985 var lArc = arc.P, rArc = arc.N;
4986 if (!lArc || !rArc) return;
4987 var lSite = lArc.site, cSite = arc.site, rSite = rArc.site;
4988 if (lSite === rSite) return;
4989 var bx = cSite.x, by = cSite.y, ax = lSite.x - bx, ay = lSite.y - by, cx = rSite.x - bx, cy = rSite.y - by;
4990 var d = 2 * (ax * cy - ay * cx);
4991 if (d >= -ε2) return;
4992 var ha = ax * ax + ay * ay, hc = cx * cx + cy * cy, x = (cy * ha - ay * hc) / d, y = (ax * hc - cx * ha) / d, cy = y + by;
4993 var circle = d3_geom_voronoiCirclePool.pop() || new d3_geom_voronoiCircle();
4994 circle.arc = arc;
4995 circle.site = cSite;
4996 circle.x = x + bx;
4997 circle.y = cy + Math.sqrt(x * x + y * y);
4998 circle.cy = cy;
4999 arc.circle = circle;
5000 var before = null, node = d3_geom_voronoiCircles._;
5001 while (node) {
5002 if (circle.y < node.y || circle.y === node.y && circle.x <= node.x) {
5003 if (node.L) node = node.L; else {
5004 before = node.P;
5005 break;
5006 }
5007 } else {
5008 if (node.R) node = node.R; else {
5009 before = node;
5010 break;
5011 }
5012 }
5013 }
5014 d3_geom_voronoiCircles.insert(before, circle);
5015 if (!before) d3_geom_voronoiFirstCircle = circle;
5016 }
5017 function d3_geom_voronoiDetachCircle(arc) {
5018 var circle = arc.circle;
5019 if (circle) {
5020 if (!circle.P) d3_geom_voronoiFirstCircle = circle.N;
5021 d3_geom_voronoiCircles.remove(circle);
5022 d3_geom_voronoiCirclePool.push(circle);
5023 d3_geom_voronoiRedBlackNode(circle);
5024 arc.circle = null;
5025 }
5026 }
5027 function d3_geom_voronoiClipEdges(extent) {
5028 var edges = d3_geom_voronoiEdges, clip = d3_geom_clipLine(extent[0][0], extent[0][1], extent[1][0], extent[1][1]), i = edges.length, e;
5029 while (i--) {
5030 e = edges[i];
5031 if (!d3_geom_voronoiConnectEdge(e, extent) || !clip(e) || abs(e.a.x - e.b.x) < ε && abs(e.a.y - e.b.y) < ε) {
5032 e.a = e.b = null;
5033 edges.splice(i, 1);
5034 }
5035 }
5036 }
5037 function d3_geom_voronoiConnectEdge(edge, extent) {
5038 var vb = edge.b;
5039 if (vb) return true;
5040 var va = edge.a, x0 = extent[0][0], x1 = extent[1][0], y0 = extent[0][1], y1 = extent[1][1], lSite = edge.l, rSite = edge.r, lx = lSite.x, ly = lSite.y, rx = rSite.x, ry = rSite.y, fx = (lx + rx) / 2, fy = (ly + ry) / 2, fm, fb;
5041 if (ry === ly) {
5042 if (fx < x0 || fx >= x1) return;
5043 if (lx > rx) {
5044 if (!va) va = {
5045 x: fx,
5046 y: y0
5047 }; else if (va.y >= y1) return;
5048 vb = {
5049 x: fx,
5050 y: y1
5051 };
5052 } else {
5053 if (!va) va = {
5054 x: fx,
5055 y: y1
5056 }; else if (va.y < y0) return;
5057 vb = {
5058 x: fx,
5059 y: y0
5060 };
5061 }
5062 } else {
5063 fm = (lx - rx) / (ry - ly);
5064 fb = fy - fm * fx;
5065 if (fm < -1 || fm > 1) {
5066 if (lx > rx) {
5067 if (!va) va = {
5068 x: (y0 - fb) / fm,
5069 y: y0
5070 }; else if (va.y >= y1) return;
5071 vb = {
5072 x: (y1 - fb) / fm,
5073 y: y1
5074 };
5075 } else {
5076 if (!va) va = {
5077 x: (y1 - fb) / fm,
5078 y: y1
5079 }; else if (va.y < y0) return;
5080 vb = {
5081 x: (y0 - fb) / fm,
5082 y: y0
5083 };
5084 }
5085 } else {
5086 if (ly < ry) {
5087 if (!va) va = {
5088 x: x0,
5089 y: fm * x0 + fb
5090 }; else if (va.x >= x1) return;
5091 vb = {
5092 x: x1,
5093 y: fm * x1 + fb
5094 };
5095 } else {
5096 if (!va) va = {
5097 x: x1,
5098 y: fm * x1 + fb
5099 }; else if (va.x < x0) return;
5100 vb = {
5101 x: x0,
5102 y: fm * x0 + fb
5103 };
5104 }
5105 }
5106 }
5107 edge.a = va;
5108 edge.b = vb;
5109 return true;
5110 }
5111 function d3_geom_voronoiEdge(lSite, rSite) {
5112 this.l = lSite;
5113 this.r = rSite;
5114 this.a = this.b = null;
5115 }
5116 function d3_geom_voronoiCreateEdge(lSite, rSite, va, vb) {
5117 var edge = new d3_geom_voronoiEdge(lSite, rSite);
5118 d3_geom_voronoiEdges.push(edge);
5119 if (va) d3_geom_voronoiSetEdgeEnd(edge, lSite, rSite, va);
5120 if (vb) d3_geom_voronoiSetEdgeEnd(edge, rSite, lSite, vb);
5121 d3_geom_voronoiCells[lSite.i].edges.push(new d3_geom_voronoiHalfEdge(edge, lSite, rSite));
5122 d3_geom_voronoiCells[rSite.i].edges.push(new d3_geom_voronoiHalfEdge(edge, rSite, lSite));
5123 return edge;
5124 }
5125 function d3_geom_voronoiCreateBorderEdge(lSite, va, vb) {
5126 var edge = new d3_geom_voronoiEdge(lSite, null);
5127 edge.a = va;
5128 edge.b = vb;
5129 d3_geom_voronoiEdges.push(edge);
5130 return edge;
5131 }
5132 function d3_geom_voronoiSetEdgeEnd(edge, lSite, rSite, vertex) {
5133 if (!edge.a && !edge.b) {
5134 edge.a = vertex;
5135 edge.l = lSite;
5136 edge.r = rSite;
5137 } else if (edge.l === rSite) {
5138 edge.b = vertex;
5139 } else {
5140 edge.a = vertex;
5141 }
5142 }
5143 function d3_geom_voronoiHalfEdge(edge, lSite, rSite) {
5144 var va = edge.a, vb = edge.b;
5145 this.edge = edge;
5146 this.site = lSite;
5147 this.angle = rSite ? Math.atan2(rSite.y - lSite.y, rSite.x - lSite.x) : edge.l === lSite ? Math.atan2(vb.x - va.x, va.y - vb.y) : Math.atan2(va.x - vb.x, vb.y - va.y);
5148 }
5149 d3_geom_voronoiHalfEdge.prototype = {
5150 start: function() {
5151 return this.edge.l === this.site ? this.edge.a : this.edge.b;
5152 },
5153 end: function() {
5154 return this.edge.l === this.site ? this.edge.b : this.edge.a;
5155 }
5156 };
5157 function d3_geom_voronoiRedBlackTree() {
5158 this._ = null;
5159 }
5160 function d3_geom_voronoiRedBlackNode(node) {
5161 node.U = node.C = node.L = node.R = node.P = node.N = null;
5162 }
5163 d3_geom_voronoiRedBlackTree.prototype = {
5164 insert: function(after, node) {
5165 var parent, grandpa, uncle;
5166 if (after) {
5167 node.P = after;
5168 node.N = after.N;
5169 if (after.N) after.N.P = node;
5170 after.N = node;
5171 if (after.R) {
5172 after = after.R;
5173 while (after.L) after = after.L;
5174 after.L = node;
5175 } else {
5176 after.R = node;
5177 }
5178 parent = after;
5179 } else if (this._) {
5180 after = d3_geom_voronoiRedBlackFirst(this._);
5181 node.P = null;
5182 node.N = after;
5183 after.P = after.L = node;
5184 parent = after;
5185 } else {
5186 node.P = node.N = null;
5187 this._ = node;
5188 parent = null;
5189 }
5190 node.L = node.R = null;
5191 node.U = parent;
5192 node.C = true;
5193 after = node;
5194 while (parent && parent.C) {
5195 grandpa = parent.U;
5196 if (parent === grandpa.L) {
5197 uncle = grandpa.R;
5198 if (uncle && uncle.C) {
5199 parent.C = uncle.C = false;
5200 grandpa.C = true;
5201 after = grandpa;
5202 } else {
5203 if (after === parent.R) {
5204 d3_geom_voronoiRedBlackRotateLeft(this, parent);
5205 after = parent;
5206 parent = after.U;
5207 }
5208 parent.C = false;
5209 grandpa.C = true;
5210 d3_geom_voronoiRedBlackRotateRight(this, grandpa);
5211 }
5212 } else {
5213 uncle = grandpa.L;
5214 if (uncle && uncle.C) {
5215 parent.C = uncle.C = false;
5216 grandpa.C = true;
5217 after = grandpa;
5218 } else {
5219 if (after === parent.L) {
5220 d3_geom_voronoiRedBlackRotateRight(this, parent);
5221 after = parent;
5222 parent = after.U;
5223 }
5224 parent.C = false;
5225 grandpa.C = true;
5226 d3_geom_voronoiRedBlackRotateLeft(this, grandpa);
5227 }
5228 }
5229 parent = after.U;
5230 }
5231 this._.C = false;
5232 },
5233 remove: function(node) {
5234 if (node.N) node.N.P = node.P;
5235 if (node.P) node.P.N = node.N;
5236 node.N = node.P = null;
5237 var parent = node.U, sibling, left = node.L, right = node.R, next, red;
5238 if (!left) next = right; else if (!right) next = left; else next = d3_geom_voronoiRedBlackFirst(right);
5239 if (parent) {
5240 if (parent.L === node) parent.L = next; else parent.R = next;
5241 } else {
5242 this._ = next;
5243 }
5244 if (left && right) {
5245 red = next.C;
5246 next.C = node.C;
5247 next.L = left;
5248 left.U = next;
5249 if (next !== right) {
5250 parent = next.U;
5251 next.U = node.U;
5252 node = next.R;
5253 parent.L = node;
5254 next.R = right;
5255 right.U = next;
5256 } else {
5257 next.U = parent;
5258 parent = next;
5259 node = next.R;
5260 }
5261 } else {
5262 red = node.C;
5263 node = next;
5264 }
5265 if (node) node.U = parent;
5266 if (red) return;
5267 if (node && node.C) {
5268 node.C = false;
5269 return;
5270 }
5271 do {
5272 if (node === this._) break;
5273 if (node === parent.L) {
5274 sibling = parent.R;
5275 if (sibling.C) {
5276 sibling.C = false;
5277 parent.C = true;
5278 d3_geom_voronoiRedBlackRotateLeft(this, parent);
5279 sibling = parent.R;
5280 }
5281 if (sibling.L && sibling.L.C || sibling.R && sibling.R.C) {
5282 if (!sibling.R || !sibling.R.C) {
5283 sibling.L.C = false;
5284 sibling.C = true;
5285 d3_geom_voronoiRedBlackRotateRight(this, sibling);
5286 sibling = parent.R;
5287 }
5288 sibling.C = parent.C;
5289 parent.C = sibling.R.C = false;
5290 d3_geom_voronoiRedBlackRotateLeft(this, parent);
5291 node = this._;
5292 break;
5293 }
5294 } else {
5295 sibling = parent.L;
5296 if (sibling.C) {
5297 sibling.C = false;
5298 parent.C = true;
5299 d3_geom_voronoiRedBlackRotateRight(this, parent);
5300 sibling = parent.L;
5301 }
5302 if (sibling.L && sibling.L.C || sibling.R && sibling.R.C) {
5303 if (!sibling.L || !sibling.L.C) {
5304 sibling.R.C = false;
5305 sibling.C = true;
5306 d3_geom_voronoiRedBlackRotateLeft(this, sibling);
5307 sibling = parent.L;
5308 }
5309 sibling.C = parent.C;
5310 parent.C = sibling.L.C = false;
5311 d3_geom_voronoiRedBlackRotateRight(this, parent);
5312 node = this._;
5313 break;
5314 }
5315 }
5316 sibling.C = true;
5317 node = parent;
5318 parent = parent.U;
5319 } while (!node.C);
5320 if (node) node.C = false;
5321 }
5322 };
5323 function d3_geom_voronoiRedBlackRotateLeft(tree, node) {
5324 var p = node, q = node.R, parent = p.U;
5325 if (parent) {
5326 if (parent.L === p) parent.L = q; else parent.R = q;
5327 } else {
5328 tree._ = q;
5329 }
5330 q.U = parent;
5331 p.U = q;
5332 p.R = q.L;
5333 if (p.R) p.R.U = p;
5334 q.L = p;
5335 }
5336 function d3_geom_voronoiRedBlackRotateRight(tree, node) {
5337 var p = node, q = node.L, parent = p.U;
5338 if (parent) {
5339 if (parent.L === p) parent.L = q; else parent.R = q;
5340 } else {
5341 tree._ = q;
5342 }
5343 q.U = parent;
5344 p.U = q;
5345 p.L = q.R;
5346 if (p.L) p.L.U = p;
5347 q.R = p;
5348 }
5349 function d3_geom_voronoiRedBlackFirst(node) {
5350 while (node.L) node = node.L;
5351 return node;
5352 }
5353 function d3_geom_voronoi(sites, bbox) {
5354 var site = sites.sort(d3_geom_voronoiVertexOrder).pop(), x0, y0, circle;
5355 d3_geom_voronoiEdges = [];
5356 d3_geom_voronoiCells = new Array(sites.length);
5357 d3_geom_voronoiBeaches = new d3_geom_voronoiRedBlackTree();
5358 d3_geom_voronoiCircles = new d3_geom_voronoiRedBlackTree();
5359 while (true) {
5360 circle = d3_geom_voronoiFirstCircle;
5361 if (site && (!circle || site.y < circle.y || site.y === circle.y && site.x < circle.x)) {
5362 if (site.x !== x0 || site.y !== y0) {
5363 d3_geom_voronoiCells[site.i] = new d3_geom_voronoiCell(site);
5364 d3_geom_voronoiAddBeach(site);
5365 x0 = site.x, y0 = site.y;
5366 }
5367 site = sites.pop();
5368 } else if (circle) {
5369 d3_geom_voronoiRemoveBeach(circle.arc);
5370 } else {
5371 break;
5372 }
5373 }
5374 if (bbox) d3_geom_voronoiClipEdges(bbox), d3_geom_voronoiCloseCells(bbox);
5375 var diagram = {
5376 cells: d3_geom_voronoiCells,
5377 edges: d3_geom_voronoiEdges
5378 };
5379 d3_geom_voronoiBeaches = d3_geom_voronoiCircles = d3_geom_voronoiEdges = d3_geom_voronoiCells = null;
5380 return diagram;
5381 }
5382 function d3_geom_voronoiVertexOrder(a, b) {
5383 return b.y - a.y || b.x - a.x;
5384 }
5385 d3.geom.voronoi = function(points) {
5386 var x = d3_geom_pointX, y = d3_geom_pointY, fx = x, fy = y, clipExtent = d3_geom_voronoiClipExtent;
5387 if (points) return voronoi(points);
5388 function voronoi(data) {
5389 var polygons = new Array(data.length), x0 = clipExtent[0][0], y0 = clipExtent[0][1], x1 = clipExtent[1][0], y1 = clipExtent[1][1];
5390 d3_geom_voronoi(sites(data), clipExtent).cells.forEach(function(cell, i) {
5391 var edges = cell.edges, site = cell.site, polygon = polygons[i] = edges.length ? edges.map(function(e) {
5392 var s = e.start();
5393 return [ s.x, s.y ];
5394 }) : site.x >= x0 && site.x <= x1 && site.y >= y0 && site.y <= y1 ? [ [ x0, y1 ], [ x1, y1 ], [ x1, y0 ], [ x0, y0 ] ] : [];
5395 polygon.point = data[i];
5396 });
5397 return polygons;
5398 }
5399 function sites(data) {
5400 return data.map(function(d, i) {
5401 return {
5402 x: Math.round(fx(d, i) / ε) * ε,
5403 y: Math.round(fy(d, i) / ε) * ε,
5404 i: i
5405 };
5406 });
5407 }
5408 voronoi.links = function(data) {
5409 return d3_geom_voronoi(sites(data)).edges.filter(function(edge) {
5410 return edge.l && edge.r;
5411 }).map(function(edge) {
5412 return {
5413 source: data[edge.l.i],
5414 target: data[edge.r.i]
5415 };
5416 });
5417 };
5418 voronoi.triangles = function(data) {
5419 var triangles = [];
5420 d3_geom_voronoi(sites(data)).cells.forEach(function(cell, i) {
5421 var site = cell.site, edges = cell.edges.sort(d3_geom_voronoiHalfEdgeOrder), j = -1, m = edges.length, e0, s0, e1 = edges[m - 1].edge, s1 = e1.l === site ? e1.r : e1.l;
5422 while (++j < m) {
5423 e0 = e1;
5424 s0 = s1;
5425 e1 = edges[j].edge;
5426 s1 = e1.l === site ? e1.r : e1.l;
5427 if (i < s0.i && i < s1.i && d3_geom_voronoiTriangleArea(site, s0, s1) < 0) {
5428 triangles.push([ data[i], data[s0.i], data[s1.i] ]);
5429 }
5430 }
5431 });
5432 return triangles;
5433 };
5434 voronoi.x = function(_) {
5435 return arguments.length ? (fx = d3_functor(x = _), voronoi) : x;
5436 };
5437 voronoi.y = function(_) {
5438 return arguments.length ? (fy = d3_functor(y = _), voronoi) : y;
5439 };
5440 voronoi.clipExtent = function(_) {
5441 if (!arguments.length) return clipExtent === d3_geom_voronoiClipExtent ? null : clipExtent;
5442 clipExtent = _ == null ? d3_geom_voronoiClipExtent : _;
5443 return voronoi;
5444 };
5445 voronoi.size = function(_) {
5446 if (!arguments.length) return clipExtent === d3_geom_voronoiClipExtent ? null : clipExtent && clipExtent[1];
5447 return voronoi.clipExtent(_ && [ [ 0, 0 ], _ ]);
5448 };
5449 return voronoi;
5450 };
5451 var d3_geom_voronoiClipExtent = [ [ -1e6, -1e6 ], [ 1e6, 1e6 ] ];
5452 function d3_geom_voronoiTriangleArea(a, b, c) {
5453 return (a.x - c.x) * (b.y - a.y) - (a.x - b.x) * (c.y - a.y);
5454 }
5455 d3.geom.delaunay = function(vertices) {
5456 return d3.geom.voronoi().triangles(vertices);
5457 };
5458 d3.geom.quadtree = function(points, x1, y1, x2, y2) {
5459 var x = d3_geom_pointX, y = d3_geom_pointY, compat;
5460 if (compat = arguments.length) {
5461 x = d3_geom_quadtreeCompatX;
5462 y = d3_geom_quadtreeCompatY;
5463 if (compat === 3) {
5464 y2 = y1;
5465 x2 = x1;
5466 y1 = x1 = 0;
5467 }
5468 return quadtree(points);
5469 }
5470 function quadtree(data) {
5471 var d, fx = d3_functor(x), fy = d3_functor(y), xs, ys, i, n, x1_, y1_, x2_, y2_;
5472 if (x1 != null) {
5473 x1_ = x1, y1_ = y1, x2_ = x2, y2_ = y2;
5474 } else {
5475 x2_ = y2_ = -(x1_ = y1_ = Infinity);
5476 xs = [], ys = [];
5477 n = data.length;
5478 if (compat) for (i = 0; i < n; ++i) {
5479 d = data[i];
5480 if (d.x < x1_) x1_ = d.x;
5481 if (d.y < y1_) y1_ = d.y;
5482 if (d.x > x2_) x2_ = d.x;
5483 if (d.y > y2_) y2_ = d.y;
5484 xs.push(d.x);
5485 ys.push(d.y);
5486 } else for (i = 0; i < n; ++i) {
5487 var x_ = +fx(d = data[i], i), y_ = +fy(d, i);
5488 if (x_ < x1_) x1_ = x_;
5489 if (y_ < y1_) y1_ = y_;
5490 if (x_ > x2_) x2_ = x_;
5491 if (y_ > y2_) y2_ = y_;
5492 xs.push(x_);
5493 ys.push(y_);
5494 }
5495 }
5496 var dx = x2_ - x1_, dy = y2_ - y1_;
5497 if (dx > dy) y2_ = y1_ + dx; else x2_ = x1_ + dy;
5498 function insert(n, d, x, y, x1, y1, x2, y2) {
5499 if (isNaN(x) || isNaN(y)) return;
5500 if (n.leaf) {
5501 var nx = n.x, ny = n.y;
5502 if (nx != null) {
5503 if (abs(nx - x) + abs(ny - y) < .01) {
5504 insertChild(n, d, x, y, x1, y1, x2, y2);
5505 } else {
5506 var nPoint = n.point;
5507 n.x = n.y = n.point = null;
5508 insertChild(n, nPoint, nx, ny, x1, y1, x2, y2);
5509 insertChild(n, d, x, y, x1, y1, x2, y2);
5510 }
5511 } else {
5512 n.x = x, n.y = y, n.point = d;
5513 }
5514 } else {
5515 insertChild(n, d, x, y, x1, y1, x2, y2);
5516 }
5517 }
5518 function insertChild(n, d, x, y, x1, y1, x2, y2) {
5519 var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, right = x >= sx, bottom = y >= sy, i = (bottom << 1) + right;
5520 n.leaf = false;
5521 n = n.nodes[i] || (n.nodes[i] = d3_geom_quadtreeNode());
5522 if (right) x1 = sx; else x2 = sx;
5523 if (bottom) y1 = sy; else y2 = sy;
5524 insert(n, d, x, y, x1, y1, x2, y2);
5525 }
5526 var root = d3_geom_quadtreeNode();
5527 root.add = function(d) {
5528 insert(root, d, +fx(d, ++i), +fy(d, i), x1_, y1_, x2_, y2_);
5529 };
5530 root.visit = function(f) {
5531 d3_geom_quadtreeVisit(f, root, x1_, y1_, x2_, y2_);
5532 };
5533 i = -1;
5534 if (x1 == null) {
5535 while (++i < n) {
5536 insert(root, data[i], xs[i], ys[i], x1_, y1_, x2_, y2_);
5537 }
5538 --i;
5539 } else data.forEach(root.add);
5540 xs = ys = data = d = null;
5541 return root;
5542 }
5543 quadtree.x = function(_) {
5544 return arguments.length ? (x = _, quadtree) : x;
5545 };
5546 quadtree.y = function(_) {
5547 return arguments.length ? (y = _, quadtree) : y;
5548 };
5549 quadtree.extent = function(_) {
5550 if (!arguments.length) return x1 == null ? null : [ [ x1, y1 ], [ x2, y2 ] ];
5551 if (_ == null) x1 = y1 = x2 = y2 = null; else x1 = +_[0][0], y1 = +_[0][1], x2 = +_[1][0],
5552 y2 = +_[1][1];
5553 return quadtree;
5554 };
5555 quadtree.size = function(_) {
5556 if (!arguments.length) return x1 == null ? null : [ x2 - x1, y2 - y1 ];
5557 if (_ == null) x1 = y1 = x2 = y2 = null; else x1 = y1 = 0, x2 = +_[0], y2 = +_[1];
5558 return quadtree;
5559 };
5560 return quadtree;
5561 };
5562 function d3_geom_quadtreeCompatX(d) {
5563 return d.x;
5564 }
5565 function d3_geom_quadtreeCompatY(d) {
5566 return d.y;
5567 }
5568 function d3_geom_quadtreeNode() {
5569 return {
5570 leaf: true,
5571 nodes: [],
5572 point: null,
5573 x: null,
5574 y: null
5575 };
5576 }
5577 function d3_geom_quadtreeVisit(f, node, x1, y1, x2, y2) {
5578 if (!f(node, x1, y1, x2, y2)) {
5579 var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, children = node.nodes;
5580 if (children[0]) d3_geom_quadtreeVisit(f, children[0], x1, y1, sx, sy);
5581 if (children[1]) d3_geom_quadtreeVisit(f, children[1], sx, y1, x2, sy);
5582 if (children[2]) d3_geom_quadtreeVisit(f, children[2], x1, sy, sx, y2);
5583 if (children[3]) d3_geom_quadtreeVisit(f, children[3], sx, sy, x2, y2);
5584 }
5585 }
5586 d3.interpolateRgb = d3_interpolateRgb;
5587 function d3_interpolateRgb(a, b) {
5588 a = d3.rgb(a);
5589 b = d3.rgb(b);
5590 var ar = a.r, ag = a.g, ab = a.b, br = b.r - ar, bg = b.g - ag, bb = b.b - ab;
5591 return function(t) {
5592 return "#" + d3_rgb_hex(Math.round(ar + br * t)) + d3_rgb_hex(Math.round(ag + bg * t)) + d3_rgb_hex(Math.round(ab + bb * t));
5593 };
5594 }
5595 d3.interpolateObject = d3_interpolateObject;
5596 function d3_interpolateObject(a, b) {
5597 var i = {}, c = {}, k;
5598 for (k in a) {
5599 if (k in b) {
5600 i[k] = d3_interpolate(a[k], b[k]);
5601 } else {
5602 c[k] = a[k];
5603 }
5604 }
5605 for (k in b) {
5606 if (!(k in a)) {
5607 c[k] = b[k];
5608 }
5609 }
5610 return function(t) {
5611 for (k in i) c[k] = i[k](t);
5612 return c;
5613 };
5614 }
5615 d3.interpolateNumber = d3_interpolateNumber;
5616 function d3_interpolateNumber(a, b) {
5617 a = +a, b = +b;
5618 return function(t) {
5619 return a * (1 - t) + b * t;
5620 };
5621 }
5622 d3.interpolateString = d3_interpolateString;
5623 function d3_interpolateString(a, b) {
5624 var bi = d3_interpolate_numberA.lastIndex = d3_interpolate_numberB.lastIndex = 0, am, bm, bs, i = -1, s = [], q = [];
5625 a = a + "", b = b + "";
5626 while ((am = d3_interpolate_numberA.exec(a)) && (bm = d3_interpolate_numberB.exec(b))) {
5627 if ((bs = bm.index) > bi) {
5628 bs = b.slice(bi, bs);
5629 if (s[i]) s[i] += bs; else s[++i] = bs;
5630 }
5631 if ((am = am[0]) === (bm = bm[0])) {
5632 if (s[i]) s[i] += bm; else s[++i] = bm;
5633 } else {
5634 s[++i] = null;
5635 q.push({
5636 i: i,
5637 x: d3_interpolateNumber(am, bm)
5638 });
5639 }
5640 bi = d3_interpolate_numberB.lastIndex;
5641 }
5642 if (bi < b.length) {
5643 bs = b.slice(bi);
5644 if (s[i]) s[i] += bs; else s[++i] = bs;
5645 }
5646 return s.length < 2 ? q[0] ? (b = q[0].x, function(t) {
5647 return b(t) + "";
5648 }) : function() {
5649 return b;
5650 } : (b = q.length, function(t) {
5651 for (var i = 0, o; i < b; ++i) s[(o = q[i]).i] = o.x(t);
5652 return s.join("");
5653 });
5654 }
5655 var d3_interpolate_numberA = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g, d3_interpolate_numberB = new RegExp(d3_interpolate_numberA.source, "g");
5656 d3.interpolate = d3_interpolate;
5657 function d3_interpolate(a, b) {
5658 var i = d3.interpolators.length, f;
5659 while (--i >= 0 && !(f = d3.interpolators[i](a, b))) ;
5660 return f;
5661 }
5662 d3.interpolators = [ function(a, b) {
5663 var t = typeof b;
5664 return (t === "string" ? d3_rgb_names.has(b) || /^(#|rgb\(|hsl\()/.test(b) ? d3_interpolateRgb : d3_interpolateString : b instanceof d3_color ? d3_interpolateRgb : Array.isArray(b) ? d3_interpolateArray : t === "object" && isNaN(b) ? d3_interpolateObject : d3_interpolateNumber)(a, b);
5665 } ];
5666 d3.interpolateArray = d3_interpolateArray;
5667 function d3_interpolateArray(a, b) {
5668 var x = [], c = [], na = a.length, nb = b.length, n0 = Math.min(a.length, b.length), i;
5669 for (i = 0; i < n0; ++i) x.push(d3_interpolate(a[i], b[i]));
5670 for (;i < na; ++i) c[i] = a[i];
5671 for (;i < nb; ++i) c[i] = b[i];
5672 return function(t) {
5673 for (i = 0; i < n0; ++i) c[i] = x[i](t);
5674 return c;
5675 };
5676 }
5677 var d3_ease_default = function() {
5678 return d3_identity;
5679 };
5680 var d3_ease = d3.map({
5681 linear: d3_ease_default,
5682 poly: d3_ease_poly,
5683 quad: function() {
5684 return d3_ease_quad;
5685 },
5686 cubic: function() {
5687 return d3_ease_cubic;
5688 },
5689 sin: function() {
5690 return d3_ease_sin;
5691 },
5692 exp: function() {
5693 return d3_ease_exp;
5694 },
5695 circle: function() {
5696 return d3_ease_circle;
5697 },
5698 elastic: d3_ease_elastic,
5699 back: d3_ease_back,
5700 bounce: function() {
5701 return d3_ease_bounce;
5702 }
5703 });
5704 var d3_ease_mode = d3.map({
5705 "in": d3_identity,
5706 out: d3_ease_reverse,
5707 "in-out": d3_ease_reflect,
5708 "out-in": function(f) {
5709 return d3_ease_reflect(d3_ease_reverse(f));
5710 }
5711 });
5712 d3.ease = function(name) {
5713 var i = name.indexOf("-"), t = i >= 0 ? name.slice(0, i) : name, m = i >= 0 ? name.slice(i + 1) : "in";
5714 t = d3_ease.get(t) || d3_ease_default;
5715 m = d3_ease_mode.get(m) || d3_identity;
5716 return d3_ease_clamp(m(t.apply(null, d3_arraySlice.call(arguments, 1))));
5717 };
5718 function d3_ease_clamp(f) {
5719 return function(t) {
5720 return t <= 0 ? 0 : t >= 1 ? 1 : f(t);
5721 };
5722 }
5723 function d3_ease_reverse(f) {
5724 return function(t) {
5725 return 1 - f(1 - t);
5726 };
5727 }
5728 function d3_ease_reflect(f) {
5729 return function(t) {
5730 return .5 * (t < .5 ? f(2 * t) : 2 - f(2 - 2 * t));
5731 };
5732 }
5733 function d3_ease_quad(t) {
5734 return t * t;
5735 }
5736 function d3_ease_cubic(t) {
5737 return t * t * t;
5738 }
5739 function d3_ease_cubicInOut(t) {
5740 if (t <= 0) return 0;
5741 if (t >= 1) return 1;
5742 var t2 = t * t, t3 = t2 * t;
5743 return 4 * (t < .5 ? t3 : 3 * (t - t2) + t3 - .75);
5744 }
5745 function d3_ease_poly(e) {
5746 return function(t) {
5747 return Math.pow(t, e);
5748 };
5749 }
5750 function d3_ease_sin(t) {
5751 return 1 - Math.cos(t * halfπ);
5752 }
5753 function d3_ease_exp(t) {
5754 return Math.pow(2, 10 * (t - 1));
5755 }
5756 function d3_ease_circle(t) {
5757 return 1 - Math.sqrt(1 - t * t);
5758 }
5759 function d3_ease_elastic(a, p) {
5760 var s;
5761 if (arguments.length < 2) p = .45;
5762 if (arguments.length) s = p / τ * Math.asin(1 / a); else a = 1, s = p / 4;
5763 return function(t) {
5764 return 1 + a * Math.pow(2, -10 * t) * Math.sin((t - s) * τ / p);
5765 };
5766 }
5767 function d3_ease_back(s) {
5768 if (!s) s = 1.70158;
5769 return function(t) {
5770 return t * t * ((s + 1) * t - s);
5771 };
5772 }
5773 function d3_ease_bounce(t) {
5774 return t < 1 / 2.75 ? 7.5625 * t * t : t < 2 / 2.75 ? 7.5625 * (t -= 1.5 / 2.75) * t + .75 : t < 2.5 / 2.75 ? 7.5625 * (t -= 2.25 / 2.75) * t + .9375 : 7.5625 * (t -= 2.625 / 2.75) * t + .984375;
5775 }
5776 d3.interpolateHcl = d3_interpolateHcl;
5777 function d3_interpolateHcl(a, b) {
5778 a = d3.hcl(a);
5779 b = d3.hcl(b);
5780 var ah = a.h, ac = a.c, al = a.l, bh = b.h - ah, bc = b.c - ac, bl = b.l - al;
5781 if (isNaN(bc)) bc = 0, ac = isNaN(ac) ? b.c : ac;
5782 if (isNaN(bh)) bh = 0, ah = isNaN(ah) ? b.h : ah; else if (bh > 180) bh -= 360; else if (bh < -180) bh += 360;
5783 return function(t) {
5784 return d3_hcl_lab(ah + bh * t, ac + bc * t, al + bl * t) + "";
5785 };
5786 }
5787 d3.interpolateHsl = d3_interpolateHsl;
5788 function d3_interpolateHsl(a, b) {
5789 a = d3.hsl(a);
5790 b = d3.hsl(b);
5791 var ah = a.h, as = a.s, al = a.l, bh = b.h - ah, bs = b.s - as, bl = b.l - al;
5792 if (isNaN(bs)) bs = 0, as = isNaN(as) ? b.s : as;
5793 if (isNaN(bh)) bh = 0, ah = isNaN(ah) ? b.h : ah; else if (bh > 180) bh -= 360; else if (bh < -180) bh += 360;
5794 return function(t) {
5795 return d3_hsl_rgb(ah + bh * t, as + bs * t, al + bl * t) + "";
5796 };
5797 }
5798 d3.interpolateLab = d3_interpolateLab;
5799 function d3_interpolateLab(a, b) {
5800 a = d3.lab(a);
5801 b = d3.lab(b);
5802 var al = a.l, aa = a.a, ab = a.b, bl = b.l - al, ba = b.a - aa, bb = b.b - ab;
5803 return function(t) {
5804 return d3_lab_rgb(al + bl * t, aa + ba * t, ab + bb * t) + "";
5805 };
5806 }
5807 d3.interpolateRound = d3_interpolateRound;
5808 function d3_interpolateRound(a, b) {
5809 b -= a;
5810 return function(t) {
5811 return Math.round(a + b * t);
5812 };
5813 }
5814 d3.transform = function(string) {
5815 var g = d3_document.createElementNS(d3.ns.prefix.svg, "g");
5816 return (d3.transform = function(string) {
5817 if (string != null) {
5818 g.setAttribute("transform", string);
5819 var t = g.transform.baseVal.consolidate();
5820 }
5821 return new d3_transform(t ? t.matrix : d3_transformIdentity);
5822 })(string);
5823 };
5824 function d3_transform(m) {
5825 var r0 = [ m.a, m.b ], r1 = [ m.c, m.d ], kx = d3_transformNormalize(r0), kz = d3_transformDot(r0, r1), ky = d3_transformNormalize(d3_transformCombine(r1, r0, -kz)) || 0;
5826 if (r0[0] * r1[1] < r1[0] * r0[1]) {
5827 r0[0] *= -1;
5828 r0[1] *= -1;
5829 kx *= -1;
5830 kz *= -1;
5831 }
5832 this.rotate = (kx ? Math.atan2(r0[1], r0[0]) : Math.atan2(-r1[0], r1[1])) * d3_degrees;
5833 this.translate = [ m.e, m.f ];
5834 this.scale = [ kx, ky ];
5835 this.skew = ky ? Math.atan2(kz, ky) * d3_degrees : 0;
5836 }
5837 d3_transform.prototype.toString = function() {
5838 return "translate(" + this.translate + ")rotate(" + this.rotate + ")skewX(" + this.skew + ")scale(" + this.scale + ")";
5839 };
5840 function d3_transformDot(a, b) {
5841 return a[0] * b[0] + a[1] * b[1];
5842 }
5843 function d3_transformNormalize(a) {
5844 var k = Math.sqrt(d3_transformDot(a, a));
5845 if (k) {
5846 a[0] /= k;
5847 a[1] /= k;
5848 }
5849 return k;
5850 }
5851 function d3_transformCombine(a, b, k) {
5852 a[0] += k * b[0];
5853 a[1] += k * b[1];
5854 return a;
5855 }
5856 var d3_transformIdentity = {
5857 a: 1,
5858 b: 0,
5859 c: 0,
5860 d: 1,
5861 e: 0,
5862 f: 0
5863 };
5864 d3.interpolateTransform = d3_interpolateTransform;
5865 function d3_interpolateTransform(a, b) {
5866 var s = [], q = [], n, A = d3.transform(a), B = d3.transform(b), ta = A.translate, tb = B.translate, ra = A.rotate, rb = B.rotate, wa = A.skew, wb = B.skew, ka = A.scale, kb = B.scale;
5867 if (ta[0] != tb[0] || ta[1] != tb[1]) {
5868 s.push("translate(", null, ",", null, ")");
5869 q.push({
5870 i: 1,
5871 x: d3_interpolateNumber(ta[0], tb[0])
5872 }, {
5873 i: 3,
5874 x: d3_interpolateNumber(ta[1], tb[1])
5875 });
5876 } else if (tb[0] || tb[1]) {
5877 s.push("translate(" + tb + ")");
5878 } else {
5879 s.push("");
5880 }
5881 if (ra != rb) {
5882 if (ra - rb > 180) rb += 360; else if (rb - ra > 180) ra += 360;
5883 q.push({
5884 i: s.push(s.pop() + "rotate(", null, ")") - 2,
5885 x: d3_interpolateNumber(ra, rb)
5886 });
5887 } else if (rb) {
5888 s.push(s.pop() + "rotate(" + rb + ")");
5889 }
5890 if (wa != wb) {
5891 q.push({
5892 i: s.push(s.pop() + "skewX(", null, ")") - 2,
5893 x: d3_interpolateNumber(wa, wb)
5894 });
5895 } else if (wb) {
5896 s.push(s.pop() + "skewX(" + wb + ")");
5897 }
5898 if (ka[0] != kb[0] || ka[1] != kb[1]) {
5899 n = s.push(s.pop() + "scale(", null, ",", null, ")");
5900 q.push({
5901 i: n - 4,
5902 x: d3_interpolateNumber(ka[0], kb[0])
5903 }, {
5904 i: n - 2,
5905 x: d3_interpolateNumber(ka[1], kb[1])
5906 });
5907 } else if (kb[0] != 1 || kb[1] != 1) {
5908 s.push(s.pop() + "scale(" + kb + ")");
5909 }
5910 n = q.length;
5911 return function(t) {
5912 var i = -1, o;
5913 while (++i < n) s[(o = q[i]).i] = o.x(t);
5914 return s.join("");
5915 };
5916 }
5917 function d3_uninterpolateNumber(a, b) {
5918 b = (b -= a = +a) || 1 / b;
5919 return function(x) {
5920 return (x - a) / b;
5921 };
5922 }
5923 function d3_uninterpolateClamp(a, b) {
5924 b = (b -= a = +a) || 1 / b;
5925 return function(x) {
5926 return Math.max(0, Math.min(1, (x - a) / b));
5927 };
5928 }
5929 d3.layout = {};
5930 d3.layout.bundle = function() {
5931 return function(links) {
5932 var paths = [], i = -1, n = links.length;
5933 while (++i < n) paths.push(d3_layout_bundlePath(links[i]));
5934 return paths;
5935 };
5936 };
5937 function d3_layout_bundlePath(link) {
5938 var start = link.source, end = link.target, lca = d3_layout_bundleLeastCommonAncestor(start, end), points = [ start ];
5939 while (start !== lca) {
5940 start = start.parent;
5941 points.push(start);
5942 }
5943 var k = points.length;
5944 while (end !== lca) {
5945 points.splice(k, 0, end);
5946 end = end.parent;
5947 }
5948 return points;
5949 }
5950 function d3_layout_bundleAncestors(node) {
5951 var ancestors = [], parent = node.parent;
5952 while (parent != null) {
5953 ancestors.push(node);
5954 node = parent;
5955 parent = parent.parent;
5956 }
5957 ancestors.push(node);
5958 return ancestors;
5959 }
5960 function d3_layout_bundleLeastCommonAncestor(a, b) {
5961 if (a === b) return a;
5962 var aNodes = d3_layout_bundleAncestors(a), bNodes = d3_layout_bundleAncestors(b), aNode = aNodes.pop(), bNode = bNodes.pop(), sharedNode = null;
5963 while (aNode === bNode) {
5964 sharedNode = aNode;
5965 aNode = aNodes.pop();
5966 bNode = bNodes.pop();
5967 }
5968 return sharedNode;
5969 }
5970 d3.layout.chord = function() {
5971 var chord = {}, chords, groups, matrix, n, padding = 0, sortGroups, sortSubgroups, sortChords;
5972 function relayout() {
5973 var subgroups = {}, groupSums = [], groupIndex = d3.range(n), subgroupIndex = [], k, x, x0, i, j;
5974 chords = [];
5975 groups = [];
5976 k = 0, i = -1;
5977 while (++i < n) {
5978 x = 0, j = -1;
5979 while (++j < n) {
5980 x += matrix[i][j];
5981 }
5982 groupSums.push(x);
5983 subgroupIndex.push(d3.range(n));
5984 k += x;
5985 }
5986 if (sortGroups) {
5987 groupIndex.sort(function(a, b) {
5988 return sortGroups(groupSums[a], groupSums[b]);
5989 });
5990 }
5991 if (sortSubgroups) {
5992 subgroupIndex.forEach(function(d, i) {
5993 d.sort(function(a, b) {
5994 return sortSubgroups(matrix[i][a], matrix[i][b]);
5995 });
5996 });
5997 }
5998 k = (τ - padding * n) / k;
5999 x = 0, i = -1;
6000 while (++i < n) {
6001 x0 = x, j = -1;
6002 while (++j < n) {
6003 var di = groupIndex[i], dj = subgroupIndex[di][j], v = matrix[di][dj], a0 = x, a1 = x += v * k;
6004 subgroups[di + "-" + dj] = {
6005 index: di,
6006 subindex: dj,
6007 startAngle: a0,
6008 endAngle: a1,
6009 value: v
6010 };
6011 }
6012 groups[di] = {
6013 index: di,
6014 startAngle: x0,
6015 endAngle: x,
6016 value: (x - x0) / k
6017 };
6018 x += padding;
6019 }
6020 i = -1;
6021 while (++i < n) {
6022 j = i - 1;
6023 while (++j < n) {
6024 var source = subgroups[i + "-" + j], target = subgroups[j + "-" + i];
6025 if (source.value || target.value) {
6026 chords.push(source.value < target.value ? {
6027 source: target,
6028 target: source
6029 } : {
6030 source: source,
6031 target: target
6032 });
6033 }
6034 }
6035 }
6036 if (sortChords) resort();
6037 }
6038 function resort() {
6039 chords.sort(function(a, b) {
6040 return sortChords((a.source.value + a.target.value) / 2, (b.source.value + b.target.value) / 2);
6041 });
6042 }
6043 chord.matrix = function(x) {
6044 if (!arguments.length) return matrix;
6045 n = (matrix = x) && matrix.length;
6046 chords = groups = null;
6047 return chord;
6048 };
6049 chord.padding = function(x) {
6050 if (!arguments.length) return padding;
6051 padding = x;
6052 chords = groups = null;
6053 return chord;
6054 };
6055 chord.sortGroups = function(x) {
6056 if (!arguments.length) return sortGroups;
6057 sortGroups = x;
6058 chords = groups = null;
6059 return chord;
6060 };
6061 chord.sortSubgroups = function(x) {
6062 if (!arguments.length) return sortSubgroups;
6063 sortSubgroups = x;
6064 chords = null;
6065 return chord;
6066 };
6067 chord.sortChords = function(x) {
6068 if (!arguments.length) return sortChords;
6069 sortChords = x;
6070 if (chords) resort();
6071 return chord;
6072 };
6073 chord.chords = function() {
6074 if (!chords) relayout();
6075 return chords;
6076 };
6077 chord.groups = function() {
6078 if (!groups) relayout();
6079 return groups;
6080 };
6081 return chord;
6082 };
6083 d3.layout.force = function() {
6084 var force = {}, event = d3.dispatch("start", "tick", "end"), size = [ 1, 1 ], drag, alpha, friction = .9, linkDistance = d3_layout_forceLinkDistance, linkStrength = d3_layout_forceLinkStrength, charge = -30, chargeDistance2 = d3_layout_forceChargeDistance2, gravity = .1, theta2 = .64, nodes = [], links = [], distances, strengths, charges;
6085 function repulse(node) {
6086 return function(quad, x1, _, x2) {
6087 if (quad.point !== node) {
6088 var dx = quad.cx - node.x, dy = quad.cy - node.y, dw = x2 - x1, dn = dx * dx + dy * dy;
6089 if (dw * dw / theta2 < dn) {
6090 if (dn < chargeDistance2) {
6091 var k = quad.charge / dn;
6092 node.px -= dx * k;
6093 node.py -= dy * k;
6094 }
6095 return true;
6096 }
6097 if (quad.point && dn && dn < chargeDistance2) {
6098 var k = quad.pointCharge / dn;
6099 node.px -= dx * k;
6100 node.py -= dy * k;
6101 }
6102 }
6103 return !quad.charge;
6104 };
6105 }
6106 force.tick = function() {
6107 if ((alpha *= .99) < .005) {
6108 event.end({
6109 type: "end",
6110 alpha: alpha = 0
6111 });
6112 return true;
6113 }
6114 var n = nodes.length, m = links.length, q, i, o, s, t, l, k, x, y;
6115 for (i = 0; i < m; ++i) {
6116 o = links[i];
6117 s = o.source;
6118 t = o.target;
6119 x = t.x - s.x;
6120 y = t.y - s.y;
6121 if (l = x * x + y * y) {
6122 l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l;
6123 x *= l;
6124 y *= l;
6125 t.x -= x * (k = s.weight / (t.weight + s.weight));
6126 t.y -= y * k;
6127 s.x += x * (k = 1 - k);
6128 s.y += y * k;
6129 }
6130 }
6131 if (k = alpha * gravity) {
6132 x = size[0] / 2;
6133 y = size[1] / 2;
6134 i = -1;
6135 if (k) while (++i < n) {
6136 o = nodes[i];
6137 o.x += (x - o.x) * k;
6138 o.y += (y - o.y) * k;
6139 }
6140 }
6141 if (charge) {
6142 d3_layout_forceAccumulate(q = d3.geom.quadtree(nodes), alpha, charges);
6143 i = -1;
6144 while (++i < n) {
6145 if (!(o = nodes[i]).fixed) {
6146 q.visit(repulse(o));
6147 }
6148 }
6149 }
6150 i = -1;
6151 while (++i < n) {
6152 o = nodes[i];
6153 if (o.fixed) {
6154 o.x = o.px;
6155 o.y = o.py;
6156 } else {
6157 o.x -= (o.px - (o.px = o.x)) * friction;
6158 o.y -= (o.py - (o.py = o.y)) * friction;
6159 }
6160 }
6161 event.tick({
6162 type: "tick",
6163 alpha: alpha
6164 });
6165 };
6166 force.nodes = function(x) {
6167 if (!arguments.length) return nodes;
6168 nodes = x;
6169 return force;
6170 };
6171 force.links = function(x) {
6172 if (!arguments.length) return links;
6173 links = x;
6174 return force;
6175 };
6176 force.size = function(x) {
6177 if (!arguments.length) return size;
6178 size = x;
6179 return force;
6180 };
6181 force.linkDistance = function(x) {
6182 if (!arguments.length) return linkDistance;
6183 linkDistance = typeof x === "function" ? x : +x;
6184 return force;
6185 };
6186 force.distance = force.linkDistance;
6187 force.linkStrength = function(x) {
6188 if (!arguments.length) return linkStrength;
6189 linkStrength = typeof x === "function" ? x : +x;
6190 return force;
6191 };
6192 force.friction = function(x) {
6193 if (!arguments.length) return friction;
6194 friction = +x;
6195 return force;
6196 };
6197 force.charge = function(x) {
6198 if (!arguments.length) return charge;
6199 charge = typeof x === "function" ? x : +x;
6200 return force;
6201 };
6202 force.chargeDistance = function(x) {
6203 if (!arguments.length) return Math.sqrt(chargeDistance2);
6204 chargeDistance2 = x * x;
6205 return force;
6206 };
6207 force.gravity = function(x) {
6208 if (!arguments.length) return gravity;
6209 gravity = +x;
6210 return force;
6211 };
6212 force.theta = function(x) {
6213 if (!arguments.length) return Math.sqrt(theta2);
6214 theta2 = x * x;
6215 return force;
6216 };
6217 force.alpha = function(x) {
6218 if (!arguments.length) return alpha;
6219 x = +x;
6220 if (alpha) {
6221 if (x > 0) alpha = x; else alpha = 0;
6222 } else if (x > 0) {
6223 event.start({
6224 type: "start",
6225 alpha: alpha = x
6226 });
6227 d3.timer(force.tick);
6228 }
6229 return force;
6230 };
6231 force.start = function() {
6232 var i, n = nodes.length, m = links.length, w = size[0], h = size[1], neighbors, o;
6233 for (i = 0; i < n; ++i) {
6234 (o = nodes[i]).index = i;
6235 o.weight = 0;
6236 }
6237 for (i = 0; i < m; ++i) {
6238 o = links[i];
6239 if (typeof o.source == "number") o.source = nodes[o.source];
6240 if (typeof o.target == "number") o.target = nodes[o.target];
6241 ++o.source.weight;
6242 ++o.target.weight;
6243 }
6244 for (i = 0; i < n; ++i) {
6245 o = nodes[i];
6246 if (isNaN(o.x)) o.x = position("x", w);
6247 if (isNaN(o.y)) o.y = position("y", h);
6248 if (isNaN(o.px)) o.px = o.x;
6249 if (isNaN(o.py)) o.py = o.y;
6250 }
6251 distances = [];
6252 if (typeof linkDistance === "function") for (i = 0; i < m; ++i) distances[i] = +linkDistance.call(this, links[i], i); else for (i = 0; i < m; ++i) distances[i] = linkDistance;
6253 strengths = [];
6254 if (typeof linkStrength === "function") for (i = 0; i < m; ++i) strengths[i] = +linkStrength.call(this, links[i], i); else for (i = 0; i < m; ++i) strengths[i] = linkStrength;
6255 charges = [];
6256 if (typeof charge === "function") for (i = 0; i < n; ++i) charges[i] = +charge.call(this, nodes[i], i); else for (i = 0; i < n; ++i) charges[i] = charge;
6257 function position(dimension, size) {
6258 if (!neighbors) {
6259 neighbors = new Array(n);
6260 for (j = 0; j < n; ++j) {
6261 neighbors[j] = [];
6262 }
6263 for (j = 0; j < m; ++j) {
6264 var o = links[j];
6265 neighbors[o.source.index].push(o.target);
6266 neighbors[o.target.index].push(o.source);
6267 }
6268 }
6269 var candidates = neighbors[i], j = -1, m = candidates.length, x;
6270 while (++j < m) if (!isNaN(x = candidates[j][dimension])) return x;
6271 return Math.random() * size;
6272 }
6273 return force.resume();
6274 };
6275 force.resume = function() {
6276 return force.alpha(.1);
6277 };
6278 force.stop = function() {
6279 return force.alpha(0);
6280 };
6281 force.drag = function() {
6282 if (!drag) drag = d3.behavior.drag().origin(d3_identity).on("dragstart.force", d3_layout_forceDragstart).on("drag.force", dragmove).on("dragend.force", d3_layout_forceDragend);
6283 if (!arguments.length) return drag;
6284 this.on("mouseover.force", d3_layout_forceMouseover).on("mouseout.force", d3_layout_forceMouseout).call(drag);
6285 };
6286 function dragmove(d) {
6287 d.px = d3.event.x, d.py = d3.event.y;
6288 force.resume();
6289 }
6290 return d3.rebind(force, event, "on");
6291 };
6292 function d3_layout_forceDragstart(d) {
6293 d.fixed |= 2;
6294 }
6295 function d3_layout_forceDragend(d) {
6296 d.fixed &= ~6;
6297 }
6298 function d3_layout_forceMouseover(d) {
6299 d.fixed |= 4;
6300 d.px = d.x, d.py = d.y;
6301 }
6302 function d3_layout_forceMouseout(d) {
6303 d.fixed &= ~4;
6304 }
6305 function d3_layout_forceAccumulate(quad, alpha, charges) {
6306 var cx = 0, cy = 0;
6307 quad.charge = 0;
6308 if (!quad.leaf) {
6309 var nodes = quad.nodes, n = nodes.length, i = -1, c;
6310 while (++i < n) {
6311 c = nodes[i];
6312 if (c == null) continue;
6313 d3_layout_forceAccumulate(c, alpha, charges);
6314 quad.charge += c.charge;
6315 cx += c.charge * c.cx;
6316 cy += c.charge * c.cy;
6317 }
6318 }
6319 if (quad.point) {
6320 if (!quad.leaf) {
6321 quad.point.x += Math.random() - .5;
6322 quad.point.y += Math.random() - .5;
6323 }
6324 var k = alpha * charges[quad.point.index];
6325 quad.charge += quad.pointCharge = k;
6326 cx += k * quad.point.x;
6327 cy += k * quad.point.y;
6328 }
6329 quad.cx = cx / quad.charge;
6330 quad.cy = cy / quad.charge;
6331 }
6332 var d3_layout_forceLinkDistance = 20, d3_layout_forceLinkStrength = 1, d3_layout_forceChargeDistance2 = Infinity;
6333 d3.layout.hierarchy = function() {
6334 var sort = d3_layout_hierarchySort, children = d3_layout_hierarchyChildren, value = d3_layout_hierarchyValue;
6335 function hierarchy(root) {
6336 var stack = [ root ], nodes = [], node;
6337 root.depth = 0;
6338 while ((node = stack.pop()) != null) {
6339 nodes.push(node);
6340 if ((childs = children.call(hierarchy, node, node.depth)) && (n = childs.length)) {
6341 var n, childs, child;
6342 while (--n >= 0) {
6343 stack.push(child = childs[n]);
6344 child.parent = node;
6345 child.depth = node.depth + 1;
6346 }
6347 if (value) node.value = 0;
6348 node.children = childs;
6349 } else {
6350 if (value) node.value = +value.call(hierarchy, node, node.depth) || 0;
6351 delete node.children;
6352 }
6353 }
6354 d3_layout_hierarchyVisitAfter(root, function(node) {
6355 var childs, parent;
6356 if (sort && (childs = node.children)) childs.sort(sort);
6357 if (value && (parent = node.parent)) parent.value += node.value;
6358 });
6359 return nodes;
6360 }
6361 hierarchy.sort = function(x) {
6362 if (!arguments.length) return sort;
6363 sort = x;
6364 return hierarchy;
6365 };
6366 hierarchy.children = function(x) {
6367 if (!arguments.length) return children;
6368 children = x;
6369 return hierarchy;
6370 };
6371 hierarchy.value = function(x) {
6372 if (!arguments.length) return value;
6373 value = x;
6374 return hierarchy;
6375 };
6376 hierarchy.revalue = function(root) {
6377 if (value) {
6378 d3_layout_hierarchyVisitBefore(root, function(node) {
6379 if (node.children) node.value = 0;
6380 });
6381 d3_layout_hierarchyVisitAfter(root, function(node) {
6382 var parent;
6383 if (!node.children) node.value = +value.call(hierarchy, node, node.depth) || 0;
6384 if (parent = node.parent) parent.value += node.value;
6385 });
6386 }
6387 return root;
6388 };
6389 return hierarchy;
6390 };
6391 function d3_layout_hierarchyRebind(object, hierarchy) {
6392 d3.rebind(object, hierarchy, "sort", "children", "value");
6393 object.nodes = object;
6394 object.links = d3_layout_hierarchyLinks;
6395 return object;
6396 }
6397 function d3_layout_hierarchyVisitBefore(node, callback) {
6398 var nodes = [ node ];
6399 while ((node = nodes.pop()) != null) {
6400 callback(node);
6401 if ((children = node.children) && (n = children.length)) {
6402 var n, children;
6403 while (--n >= 0) nodes.push(children[n]);
6404 }
6405 }
6406 }
6407 function d3_layout_hierarchyVisitAfter(node, callback) {
6408 var nodes = [ node ], nodes2 = [];
6409 while ((node = nodes.pop()) != null) {
6410 nodes2.push(node);
6411 if ((children = node.children) && (n = children.length)) {
6412 var i = -1, n, children;
6413 while (++i < n) nodes.push(children[i]);
6414 }
6415 }
6416 while ((node = nodes2.pop()) != null) {
6417 callback(node);
6418 }
6419 }
6420 function d3_layout_hierarchyChildren(d) {
6421 return d.children;
6422 }
6423 function d3_layout_hierarchyValue(d) {
6424 return d.value;
6425 }
6426 function d3_layout_hierarchySort(a, b) {
6427 return b.value - a.value;
6428 }
6429 function d3_layout_hierarchyLinks(nodes) {
6430 return d3.merge(nodes.map(function(parent) {
6431 return (parent.children || []).map(function(child) {
6432 return {
6433 source: parent,
6434 target: child
6435 };
6436 });
6437 }));
6438 }
6439 d3.layout.partition = function() {
6440 var hierarchy = d3.layout.hierarchy(), size = [ 1, 1 ];
6441 function position(node, x, dx, dy) {
6442 var children = node.children;
6443 node.x = x;
6444 node.y = node.depth * dy;
6445 node.dx = dx;
6446 node.dy = dy;
6447 if (children && (n = children.length)) {
6448 var i = -1, n, c, d;
6449 dx = node.value ? dx / node.value : 0;
6450 while (++i < n) {
6451 position(c = children[i], x, d = c.value * dx, dy);
6452 x += d;
6453 }
6454 }
6455 }
6456 function depth(node) {
6457 var children = node.children, d = 0;
6458 if (children && (n = children.length)) {
6459 var i = -1, n;
6460 while (++i < n) d = Math.max(d, depth(children[i]));
6461 }
6462 return 1 + d;
6463 }
6464 function partition(d, i) {
6465 var nodes = hierarchy.call(this, d, i);
6466 position(nodes[0], 0, size[0], size[1] / depth(nodes[0]));
6467 return nodes;
6468 }
6469 partition.size = function(x) {
6470 if (!arguments.length) return size;
6471 size = x;
6472 return partition;
6473 };
6474 return d3_layout_hierarchyRebind(partition, hierarchy);
6475 };
6476 d3.layout.pie = function() {
6477 var value = Number, sort = d3_layout_pieSortByValue, startAngle = 0, endAngle = τ;
6478 function pie(data) {
6479 var values = data.map(function(d, i) {
6480 return +value.call(pie, d, i);
6481 });
6482 var a = +(typeof startAngle === "function" ? startAngle.apply(this, arguments) : startAngle);
6483 var k = ((typeof endAngle === "function" ? endAngle.apply(this, arguments) : endAngle) - a) / d3.sum(values);
6484 var index = d3.range(data.length);
6485 if (sort != null) index.sort(sort === d3_layout_pieSortByValue ? function(i, j) {
6486 return values[j] - values[i];
6487 } : function(i, j) {
6488 return sort(data[i], data[j]);
6489 });
6490 var arcs = [];
6491 index.forEach(function(i) {
6492 var d;
6493 arcs[i] = {
6494 data: data[i],
6495 value: d = values[i],
6496 startAngle: a,
6497 endAngle: a += d * k
6498 };
6499 });
6500 return arcs;
6501 }
6502 pie.value = function(x) {
6503 if (!arguments.length) return value;
6504 value = x;
6505 return pie;
6506 };
6507 pie.sort = function(x) {
6508 if (!arguments.length) return sort;
6509 sort = x;
6510 return pie;
6511 };
6512 pie.startAngle = function(x) {
6513 if (!arguments.length) return startAngle;
6514 startAngle = x;
6515 return pie;
6516 };
6517 pie.endAngle = function(x) {
6518 if (!arguments.length) return endAngle;
6519 endAngle = x;
6520 return pie;
6521 };
6522 return pie;
6523 };
6524 var d3_layout_pieSortByValue = {};
6525 d3.layout.stack = function() {
6526 var values = d3_identity, order = d3_layout_stackOrderDefault, offset = d3_layout_stackOffsetZero, out = d3_layout_stackOut, x = d3_layout_stackX, y = d3_layout_stackY;
6527 function stack(data, index) {
6528 if (!(n = data.length)) return data;
6529 var series = data.map(function(d, i) {
6530 return values.call(stack, d, i);
6531 });
6532 var points = series.map(function(d) {
6533 return d.map(function(v, i) {
6534 return [ x.call(stack, v, i), y.call(stack, v, i) ];
6535 });
6536 });
6537 var orders = order.call(stack, points, index);
6538 series = d3.permute(series, orders);
6539 points = d3.permute(points, orders);
6540 var offsets = offset.call(stack, points, index);
6541 var m = series[0].length, n, i, j, o;
6542 for (j = 0; j < m; ++j) {
6543 out.call(stack, series[0][j], o = offsets[j], points[0][j][1]);
6544 for (i = 1; i < n; ++i) {
6545 out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]);
6546 }
6547 }
6548 return data;
6549 }
6550 stack.values = function(x) {
6551 if (!arguments.length) return values;
6552 values = x;
6553 return stack;
6554 };
6555 stack.order = function(x) {
6556 if (!arguments.length) return order;
6557 order = typeof x === "function" ? x : d3_layout_stackOrders.get(x) || d3_layout_stackOrderDefault;
6558 return stack;
6559 };
6560 stack.offset = function(x) {
6561 if (!arguments.length) return offset;
6562 offset = typeof x === "function" ? x : d3_layout_stackOffsets.get(x) || d3_layout_stackOffsetZero;
6563 return stack;
6564 };
6565 stack.x = function(z) {
6566 if (!arguments.length) return x;
6567 x = z;
6568 return stack;
6569 };
6570 stack.y = function(z) {
6571 if (!arguments.length) return y;
6572 y = z;
6573 return stack;
6574 };
6575 stack.out = function(z) {
6576 if (!arguments.length) return out;
6577 out = z;
6578 return stack;
6579 };
6580 return stack;
6581 };
6582 function d3_layout_stackX(d) {
6583 return d.x;
6584 }
6585 function d3_layout_stackY(d) {
6586 return d.y;
6587 }
6588 function d3_layout_stackOut(d, y0, y) {
6589 d.y0 = y0;
6590 d.y = y;
6591 }
6592 var d3_layout_stackOrders = d3.map({
6593 "inside-out": function(data) {
6594 var n = data.length, i, j, max = data.map(d3_layout_stackMaxIndex), sums = data.map(d3_layout_stackReduceSum), index = d3.range(n).sort(function(a, b) {
6595 return max[a] - max[b];
6596 }), top = 0, bottom = 0, tops = [], bottoms = [];
6597 for (i = 0; i < n; ++i) {
6598 j = index[i];
6599 if (top < bottom) {
6600 top += sums[j];
6601 tops.push(j);
6602 } else {
6603 bottom += sums[j];
6604 bottoms.push(j);
6605 }
6606 }
6607 return bottoms.reverse().concat(tops);
6608 },
6609 reverse: function(data) {
6610 return d3.range(data.length).reverse();
6611 },
6612 "default": d3_layout_stackOrderDefault
6613 });
6614 var d3_layout_stackOffsets = d3.map({
6615 silhouette: function(data) {
6616 var n = data.length, m = data[0].length, sums = [], max = 0, i, j, o, y0 = [];
6617 for (j = 0; j < m; ++j) {
6618 for (i = 0, o = 0; i < n; i++) o += data[i][j][1];
6619 if (o > max) max = o;
6620 sums.push(o);
6621 }
6622 for (j = 0; j < m; ++j) {
6623 y0[j] = (max - sums[j]) / 2;
6624 }
6625 return y0;
6626 },
6627 wiggle: function(data) {
6628 var n = data.length, x = data[0], m = x.length, i, j, k, s1, s2, s3, dx, o, o0, y0 = [];
6629 y0[0] = o = o0 = 0;
6630 for (j = 1; j < m; ++j) {
6631 for (i = 0, s1 = 0; i < n; ++i) s1 += data[i][j][1];
6632 for (i = 0, s2 = 0, dx = x[j][0] - x[j - 1][0]; i < n; ++i) {
6633 for (k = 0, s3 = (data[i][j][1] - data[i][j - 1][1]) / (2 * dx); k < i; ++k) {
6634 s3 += (data[k][j][1] - data[k][j - 1][1]) / dx;
6635 }
6636 s2 += s3 * data[i][j][1];
6637 }
6638 y0[j] = o -= s1 ? s2 / s1 * dx : 0;
6639 if (o < o0) o0 = o;
6640 }
6641 for (j = 0; j < m; ++j) y0[j] -= o0;
6642 return y0;
6643 },
6644 expand: function(data) {
6645 var n = data.length, m = data[0].length, k = 1 / n, i, j, o, y0 = [];
6646 for (j = 0; j < m; ++j) {
6647 for (i = 0, o = 0; i < n; i++) o += data[i][j][1];
6648 if (o) for (i = 0; i < n; i++) data[i][j][1] /= o; else for (i = 0; i < n; i++) data[i][j][1] = k;
6649 }
6650 for (j = 0; j < m; ++j) y0[j] = 0;
6651 return y0;
6652 },
6653 zero: d3_layout_stackOffsetZero
6654 });
6655 function d3_layout_stackOrderDefault(data) {
6656 return d3.range(data.length);
6657 }
6658 function d3_layout_stackOffsetZero(data) {
6659 var j = -1, m = data[0].length, y0 = [];
6660 while (++j < m) y0[j] = 0;
6661 return y0;
6662 }
6663 function d3_layout_stackMaxIndex(array) {
6664 var i = 1, j = 0, v = array[0][1], k, n = array.length;
6665 for (;i < n; ++i) {
6666 if ((k = array[i][1]) > v) {
6667 j = i;
6668 v = k;
6669 }
6670 }
6671 return j;
6672 }
6673 function d3_layout_stackReduceSum(d) {
6674 return d.reduce(d3_layout_stackSum, 0);
6675 }
6676 function d3_layout_stackSum(p, d) {
6677 return p + d[1];
6678 }
6679 d3.layout.histogram = function() {
6680 var frequency = true, valuer = Number, ranger = d3_layout_histogramRange, binner = d3_layout_histogramBinSturges;
6681 function histogram(data, i) {
6682 var bins = [], values = data.map(valuer, this), range = ranger.call(this, values, i), thresholds = binner.call(this, range, values, i), bin, i = -1, n = values.length, m = thresholds.length - 1, k = frequency ? 1 : 1 / n, x;
6683 while (++i < m) {
6684 bin = bins[i] = [];
6685 bin.dx = thresholds[i + 1] - (bin.x = thresholds[i]);
6686 bin.y = 0;
6687 }
6688 if (m > 0) {
6689 i = -1;
6690 while (++i < n) {
6691 x = values[i];
6692 if (x >= range[0] && x <= range[1]) {
6693 bin = bins[d3.bisect(thresholds, x, 1, m) - 1];
6694 bin.y += k;
6695 bin.push(data[i]);
6696 }
6697 }
6698 }
6699 return bins;
6700 }
6701 histogram.value = function(x) {
6702 if (!arguments.length) return valuer;
6703 valuer = x;
6704 return histogram;
6705 };
6706 histogram.range = function(x) {
6707 if (!arguments.length) return ranger;
6708 ranger = d3_functor(x);
6709 return histogram;
6710 };
6711 histogram.bins = function(x) {
6712 if (!arguments.length) return binner;
6713 binner = typeof x === "number" ? function(range) {
6714 return d3_layout_histogramBinFixed(range, x);
6715 } : d3_functor(x);
6716 return histogram;
6717 };
6718 histogram.frequency = function(x) {
6719 if (!arguments.length) return frequency;
6720 frequency = !!x;
6721 return histogram;
6722 };
6723 return histogram;
6724 };
6725 function d3_layout_histogramBinSturges(range, values) {
6726 return d3_layout_histogramBinFixed(range, Math.ceil(Math.log(values.length) / Math.LN2 + 1));
6727 }
6728 function d3_layout_histogramBinFixed(range, n) {
6729 var x = -1, b = +range[0], m = (range[1] - b) / n, f = [];
6730 while (++x <= n) f[x] = m * x + b;
6731 return f;
6732 }
6733 function d3_layout_histogramRange(values) {
6734 return [ d3.min(values), d3.max(values) ];
6735 }
6736 d3.layout.pack = function() {
6737 var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort), padding = 0, size = [ 1, 1 ], radius;
6738 function pack(d, i) {
6739 var nodes = hierarchy.call(this, d, i), root = nodes[0], w = size[0], h = size[1], r = radius == null ? Math.sqrt : typeof radius === "function" ? radius : function() {
6740 return radius;
6741 };
6742 root.x = root.y = 0;
6743 d3_layout_hierarchyVisitAfter(root, function(d) {
6744 d.r = +r(d.value);
6745 });
6746 d3_layout_hierarchyVisitAfter(root, d3_layout_packSiblings);
6747 if (padding) {
6748 var dr = padding * (radius ? 1 : Math.max(2 * root.r / w, 2 * root.r / h)) / 2;
6749 d3_layout_hierarchyVisitAfter(root, function(d) {
6750 d.r += dr;
6751 });
6752 d3_layout_hierarchyVisitAfter(root, d3_layout_packSiblings);
6753 d3_layout_hierarchyVisitAfter(root, function(d) {
6754 d.r -= dr;
6755 });
6756 }
6757 d3_layout_packTransform(root, w / 2, h / 2, radius ? 1 : 1 / Math.max(2 * root.r / w, 2 * root.r / h));
6758 return nodes;
6759 }
6760 pack.size = function(_) {
6761 if (!arguments.length) return size;
6762 size = _;
6763 return pack;
6764 };
6765 pack.radius = function(_) {
6766 if (!arguments.length) return radius;
6767 radius = _ == null || typeof _ === "function" ? _ : +_;
6768 return pack;
6769 };
6770 pack.padding = function(_) {
6771 if (!arguments.length) return padding;
6772 padding = +_;
6773 return pack;
6774 };
6775 return d3_layout_hierarchyRebind(pack, hierarchy);
6776 };
6777 function d3_layout_packSort(a, b) {
6778 return a.value - b.value;
6779 }
6780 function d3_layout_packInsert(a, b) {
6781 var c = a._pack_next;
6782 a._pack_next = b;
6783 b._pack_prev = a;
6784 b._pack_next = c;
6785 c._pack_prev = b;
6786 }
6787 function d3_layout_packSplice(a, b) {
6788 a._pack_next = b;
6789 b._pack_prev = a;
6790 }
6791 function d3_layout_packIntersects(a, b) {
6792 var dx = b.x - a.x, dy = b.y - a.y, dr = a.r + b.r;
6793 return .999 * dr * dr > dx * dx + dy * dy;
6794 }
6795 function d3_layout_packSiblings(node) {
6796 if (!(nodes = node.children) || !(n = nodes.length)) return;
6797 var nodes, xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity, a, b, c, i, j, k, n;
6798 function bound(node) {
6799 xMin = Math.min(node.x - node.r, xMin);
6800 xMax = Math.max(node.x + node.r, xMax);
6801 yMin = Math.min(node.y - node.r, yMin);
6802 yMax = Math.max(node.y + node.r, yMax);
6803 }
6804 nodes.forEach(d3_layout_packLink);
6805 a = nodes[0];
6806 a.x = -a.r;
6807 a.y = 0;
6808 bound(a);
6809 if (n > 1) {
6810 b = nodes[1];
6811 b.x = b.r;
6812 b.y = 0;
6813 bound(b);
6814 if (n > 2) {
6815 c = nodes[2];
6816 d3_layout_packPlace(a, b, c);
6817 bound(c);
6818 d3_layout_packInsert(a, c);
6819 a._pack_prev = c;
6820 d3_layout_packInsert(c, b);
6821 b = a._pack_next;
6822 for (i = 3; i < n; i++) {
6823 d3_layout_packPlace(a, b, c = nodes[i]);
6824 var isect = 0, s1 = 1, s2 = 1;
6825 for (j = b._pack_next; j !== b; j = j._pack_next, s1++) {
6826 if (d3_layout_packIntersects(j, c)) {
6827 isect = 1;
6828 break;
6829 }
6830 }
6831 if (isect == 1) {
6832 for (k = a._pack_prev; k !== j._pack_prev; k = k._pack_prev, s2++) {
6833 if (d3_layout_packIntersects(k, c)) {
6834 break;
6835 }
6836 }
6837 }
6838 if (isect) {
6839 if (s1 < s2 || s1 == s2 && b.r < a.r) d3_layout_packSplice(a, b = j); else d3_layout_packSplice(a = k, b);
6840 i--;
6841 } else {
6842 d3_layout_packInsert(a, c);
6843 b = c;
6844 bound(c);
6845 }
6846 }
6847 }
6848 }
6849 var cx = (xMin + xMax) / 2, cy = (yMin + yMax) / 2, cr = 0;
6850 for (i = 0; i < n; i++) {
6851 c = nodes[i];
6852 c.x -= cx;
6853 c.y -= cy;
6854 cr = Math.max(cr, c.r + Math.sqrt(c.x * c.x + c.y * c.y));
6855 }
6856 node.r = cr;
6857 nodes.forEach(d3_layout_packUnlink);
6858 }
6859 function d3_layout_packLink(node) {
6860 node._pack_next = node._pack_prev = node;
6861 }
6862 function d3_layout_packUnlink(node) {
6863 delete node._pack_next;
6864 delete node._pack_prev;
6865 }
6866 function d3_layout_packTransform(node, x, y, k) {
6867 var children = node.children;
6868 node.x = x += k * node.x;
6869 node.y = y += k * node.y;
6870 node.r *= k;
6871 if (children) {
6872 var i = -1, n = children.length;
6873 while (++i < n) d3_layout_packTransform(children[i], x, y, k);
6874 }
6875 }
6876 function d3_layout_packPlace(a, b, c) {
6877 var db = a.r + c.r, dx = b.x - a.x, dy = b.y - a.y;
6878 if (db && (dx || dy)) {
6879 var da = b.r + c.r, dc = dx * dx + dy * dy;
6880 da *= da;
6881 db *= db;
6882 var x = .5 + (db - da) / (2 * dc), y = Math.sqrt(Math.max(0, 2 * da * (db + dc) - (db -= dc) * db - da * da)) / (2 * dc);
6883 c.x = a.x + x * dx + y * dy;
6884 c.y = a.y + x * dy - y * dx;
6885 } else {
6886 c.x = a.x + db;
6887 c.y = a.y;
6888 }
6889 }
6890 d3.layout.tree = function() {
6891 var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ], nodeSize = null;
6892 function tree(d, i) {
6893 var nodes = hierarchy.call(this, d, i), root0 = nodes[0], root1 = wrapTree(root0);
6894 d3_layout_hierarchyVisitAfter(root1, firstWalk), root1.parent.m = -root1.z;
6895 d3_layout_hierarchyVisitBefore(root1, secondWalk);
6896 if (nodeSize) d3_layout_hierarchyVisitBefore(root0, sizeNode); else {
6897 var left = root0, right = root0, bottom = root0;
6898 d3_layout_hierarchyVisitBefore(root0, function(node) {
6899 if (node.x < left.x) left = node;
6900 if (node.x > right.x) right = node;
6901 if (node.depth > bottom.depth) bottom = node;
6902 });
6903 var tx = separation(left, right) / 2 - left.x, kx = size[0] / (right.x + separation(right, left) / 2 + tx), ky = size[1] / (bottom.depth || 1);
6904 d3_layout_hierarchyVisitBefore(root0, function(node) {
6905 node.x = (node.x + tx) * kx;
6906 node.y = node.depth * ky;
6907 });
6908 }
6909 return nodes;
6910 }
6911 function wrapTree(root0) {
6912 var root1 = {
6913 A: null,
6914 children: [ root0 ]
6915 }, queue = [ root1 ], node1;
6916 while ((node1 = queue.pop()) != null) {
6917 for (var children = node1.children, child, i = 0, n = children.length; i < n; ++i) {
6918 queue.push((children[i] = child = {
6919 _: children[i],
6920 parent: node1,
6921 children: (child = children[i].children) && child.slice() || [],
6922 A: null,
6923 a: null,
6924 z: 0,
6925 m: 0,
6926 c: 0,
6927 s: 0,
6928 t: null,
6929 i: i
6930 }).a = child);
6931 }
6932 }
6933 return root1.children[0];
6934 }
6935 function firstWalk(v) {
6936 var children = v.children, siblings = v.parent.children, w = v.i ? siblings[v.i - 1] : null;
6937 if (children.length) {
6938 d3_layout_treeShift(v);
6939 var midpoint = (children[0].z + children[children.length - 1].z) / 2;
6940 if (w) {
6941 v.z = w.z + separation(v._, w._);
6942 v.m = v.z - midpoint;
6943 } else {
6944 v.z = midpoint;
6945 }
6946 } else if (w) {
6947 v.z = w.z + separation(v._, w._);
6948 }
6949 v.parent.A = apportion(v, w, v.parent.A || siblings[0]);
6950 }
6951 function secondWalk(v) {
6952 v._.x = v.z + v.parent.m;
6953 v.m += v.parent.m;
6954 }
6955 function apportion(v, w, ancestor) {
6956 if (w) {
6957 var vip = v, vop = v, vim = w, vom = vip.parent.children[0], sip = vip.m, sop = vop.m, sim = vim.m, som = vom.m, shift;
6958 while (vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) {
6959 vom = d3_layout_treeLeft(vom);
6960 vop = d3_layout_treeRight(vop);
6961 vop.a = v;
6962 shift = vim.z + sim - vip.z - sip + separation(vim._, vip._);
6963 if (shift > 0) {
6964 d3_layout_treeMove(d3_layout_treeAncestor(vim, v, ancestor), v, shift);
6965 sip += shift;
6966 sop += shift;
6967 }
6968 sim += vim.m;
6969 sip += vip.m;
6970 som += vom.m;
6971 sop += vop.m;
6972 }
6973 if (vim && !d3_layout_treeRight(vop)) {
6974 vop.t = vim;
6975 vop.m += sim - sop;
6976 }
6977 if (vip && !d3_layout_treeLeft(vom)) {
6978 vom.t = vip;
6979 vom.m += sip - som;
6980 ancestor = v;
6981 }
6982 }
6983 return ancestor;
6984 }
6985 function sizeNode(node) {
6986 node.x *= size[0];
6987 node.y = node.depth * size[1];
6988 }
6989 tree.separation = function(x) {
6990 if (!arguments.length) return separation;
6991 separation = x;
6992 return tree;
6993 };
6994 tree.size = function(x) {
6995 if (!arguments.length) return nodeSize ? null : size;
6996 nodeSize = (size = x) == null ? sizeNode : null;
6997 return tree;
6998 };
6999 tree.nodeSize = function(x) {
7000 if (!arguments.length) return nodeSize ? size : null;
7001 nodeSize = (size = x) == null ? null : sizeNode;
7002 return tree;
7003 };
7004 return d3_layout_hierarchyRebind(tree, hierarchy);
7005 };
7006 function d3_layout_treeSeparation(a, b) {
7007 return a.parent == b.parent ? 1 : 2;
7008 }
7009 function d3_layout_treeLeft(v) {
7010 var children = v.children;
7011 return children.length ? children[0] : v.t;
7012 }
7013 function d3_layout_treeRight(v) {
7014 var children = v.children, n;
7015 return (n = children.length) ? children[n - 1] : v.t;
7016 }
7017 function d3_layout_treeMove(wm, wp, shift) {
7018 var change = shift / (wp.i - wm.i);
7019 wp.c -= change;
7020 wp.s += shift;
7021 wm.c += change;
7022 wp.z += shift;
7023 wp.m += shift;
7024 }
7025 function d3_layout_treeShift(v) {
7026 var shift = 0, change = 0, children = v.children, i = children.length, w;
7027 while (--i >= 0) {
7028 w = children[i];
7029 w.z += shift;
7030 w.m += shift;
7031 shift += w.s + (change += w.c);
7032 }
7033 }
7034 function d3_layout_treeAncestor(vim, v, ancestor) {
7035 return vim.a.parent === v.parent ? vim.a : ancestor;
7036 }
7037 d3.layout.cluster = function() {
7038 var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ], nodeSize = false;
7039 function cluster(d, i) {
7040 var nodes = hierarchy.call(this, d, i), root = nodes[0], previousNode, x = 0;
7041 d3_layout_hierarchyVisitAfter(root, function(node) {
7042 var children = node.children;
7043 if (children && children.length) {
7044 node.x = d3_layout_clusterX(children);
7045 node.y = d3_layout_clusterY(children);
7046 } else {
7047 node.x = previousNode ? x += separation(node, previousNode) : 0;
7048 node.y = 0;
7049 previousNode = node;
7050 }
7051 });
7052 var left = d3_layout_clusterLeft(root), right = d3_layout_clusterRight(root), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2;
7053 d3_layout_hierarchyVisitAfter(root, nodeSize ? function(node) {
7054 node.x = (node.x - root.x) * size[0];
7055 node.y = (root.y - node.y) * size[1];
7056 } : function(node) {
7057 node.x = (node.x - x0) / (x1 - x0) * size[0];
7058 node.y = (1 - (root.y ? node.y / root.y : 1)) * size[1];
7059 });
7060 return nodes;
7061 }
7062 cluster.separation = function(x) {
7063 if (!arguments.length) return separation;
7064 separation = x;
7065 return cluster;
7066 };
7067 cluster.size = function(x) {
7068 if (!arguments.length) return nodeSize ? null : size;
7069 nodeSize = (size = x) == null;
7070 return cluster;
7071 };
7072 cluster.nodeSize = function(x) {
7073 if (!arguments.length) return nodeSize ? size : null;
7074 nodeSize = (size = x) != null;
7075 return cluster;
7076 };
7077 return d3_layout_hierarchyRebind(cluster, hierarchy);
7078 };
7079 function d3_layout_clusterY(children) {
7080 return 1 + d3.max(children, function(child) {
7081 return child.y;
7082 });
7083 }
7084 function d3_layout_clusterX(children) {
7085 return children.reduce(function(x, child) {
7086 return x + child.x;
7087 }, 0) / children.length;
7088 }
7089 function d3_layout_clusterLeft(node) {
7090 var children = node.children;
7091 return children && children.length ? d3_layout_clusterLeft(children[0]) : node;
7092 }
7093 function d3_layout_clusterRight(node) {
7094 var children = node.children, n;
7095 return children && (n = children.length) ? d3_layout_clusterRight(children[n - 1]) : node;
7096 }
7097 d3.layout.treemap = function() {
7098 var hierarchy = d3.layout.hierarchy(), round = Math.round, size = [ 1, 1 ], padding = null, pad = d3_layout_treemapPadNull, sticky = false, stickies, mode = "squarify", ratio = .5 * (1 + Math.sqrt(5));
7099 function scale(children, k) {
7100 var i = -1, n = children.length, child, area;
7101 while (++i < n) {
7102 area = (child = children[i]).value * (k < 0 ? 0 : k);
7103 child.area = isNaN(area) || area <= 0 ? 0 : area;
7104 }
7105 }
7106 function squarify(node) {
7107 var children = node.children;
7108 if (children && children.length) {
7109 var rect = pad(node), row = [], remaining = children.slice(), child, best = Infinity, score, u = mode === "slice" ? rect.dx : mode === "dice" ? rect.dy : mode === "slice-dice" ? node.depth & 1 ? rect.dy : rect.dx : Math.min(rect.dx, rect.dy), n;
7110 scale(remaining, rect.dx * rect.dy / node.value);
7111 row.area = 0;
7112 while ((n = remaining.length) > 0) {
7113 row.push(child = remaining[n - 1]);
7114 row.area += child.area;
7115 if (mode !== "squarify" || (score = worst(row, u)) <= best) {
7116 remaining.pop();
7117 best = score;
7118 } else {
7119 row.area -= row.pop().area;
7120 position(row, u, rect, false);
7121 u = Math.min(rect.dx, rect.dy);
7122 row.length = row.area = 0;
7123 best = Infinity;
7124 }
7125 }
7126 if (row.length) {
7127 position(row, u, rect, true);
7128 row.length = row.area = 0;
7129 }
7130 children.forEach(squarify);
7131 }
7132 }
7133 function stickify(node) {
7134 var children = node.children;
7135 if (children && children.length) {
7136 var rect = pad(node), remaining = children.slice(), child, row = [];
7137 scale(remaining, rect.dx * rect.dy / node.value);
7138 row.area = 0;
7139 while (child = remaining.pop()) {
7140 row.push(child);
7141 row.area += child.area;
7142 if (child.z != null) {
7143 position(row, child.z ? rect.dx : rect.dy, rect, !remaining.length);
7144 row.length = row.area = 0;
7145 }
7146 }
7147 children.forEach(stickify);
7148 }
7149 }
7150 function worst(row, u) {
7151 var s = row.area, r, rmax = 0, rmin = Infinity, i = -1, n = row.length;
7152 while (++i < n) {
7153 if (!(r = row[i].area)) continue;
7154 if (r < rmin) rmin = r;
7155 if (r > rmax) rmax = r;
7156 }
7157 s *= s;
7158 u *= u;
7159 return s ? Math.max(u * rmax * ratio / s, s / (u * rmin * ratio)) : Infinity;
7160 }
7161 function position(row, u, rect, flush) {
7162 var i = -1, n = row.length, x = rect.x, y = rect.y, v = u ? round(row.area / u) : 0, o;
7163 if (u == rect.dx) {
7164 if (flush || v > rect.dy) v = rect.dy;
7165 while (++i < n) {
7166 o = row[i];
7167 o.x = x;
7168 o.y = y;
7169 o.dy = v;
7170 x += o.dx = Math.min(rect.x + rect.dx - x, v ? round(o.area / v) : 0);
7171 }
7172 o.z = true;
7173 o.dx += rect.x + rect.dx - x;
7174 rect.y += v;
7175 rect.dy -= v;
7176 } else {
7177 if (flush || v > rect.dx) v = rect.dx;
7178 while (++i < n) {
7179 o = row[i];
7180 o.x = x;
7181 o.y = y;
7182 o.dx = v;
7183 y += o.dy = Math.min(rect.y + rect.dy - y, v ? round(o.area / v) : 0);
7184 }
7185 o.z = false;
7186 o.dy += rect.y + rect.dy - y;
7187 rect.x += v;
7188 rect.dx -= v;
7189 }
7190 }
7191 function treemap(d) {
7192 var nodes = stickies || hierarchy(d), root = nodes[0];
7193 root.x = 0;
7194 root.y = 0;
7195 root.dx = size[0];
7196 root.dy = size[1];
7197 if (stickies) hierarchy.revalue(root);
7198 scale([ root ], root.dx * root.dy / root.value);
7199 (stickies ? stickify : squarify)(root);
7200 if (sticky) stickies = nodes;
7201 return nodes;
7202 }
7203 treemap.size = function(x) {
7204 if (!arguments.length) return size;
7205 size = x;
7206 return treemap;
7207 };
7208 treemap.padding = function(x) {
7209 if (!arguments.length) return padding;
7210 function padFunction(node) {
7211 var p = x.call(treemap, node, node.depth);
7212 return p == null ? d3_layout_treemapPadNull(node) : d3_layout_treemapPad(node, typeof p === "number" ? [ p, p, p, p ] : p);
7213 }
7214 function padConstant(node) {
7215 return d3_layout_treemapPad(node, x);
7216 }
7217 var type;
7218 pad = (padding = x) == null ? d3_layout_treemapPadNull : (type = typeof x) === "function" ? padFunction : type === "number" ? (x = [ x, x, x, x ],
7219 padConstant) : padConstant;
7220 return treemap;
7221 };
7222 treemap.round = function(x) {
7223 if (!arguments.length) return round != Number;
7224 round = x ? Math.round : Number;
7225 return treemap;
7226 };
7227 treemap.sticky = function(x) {
7228 if (!arguments.length) return sticky;
7229 sticky = x;
7230 stickies = null;
7231 return treemap;
7232 };
7233 treemap.ratio = function(x) {
7234 if (!arguments.length) return ratio;
7235 ratio = x;
7236 return treemap;
7237 };
7238 treemap.mode = function(x) {
7239 if (!arguments.length) return mode;
7240 mode = x + "";
7241 return treemap;
7242 };
7243 return d3_layout_hierarchyRebind(treemap, hierarchy);
7244 };
7245 function d3_layout_treemapPadNull(node) {
7246 return {
7247 x: node.x,
7248 y: node.y,
7249 dx: node.dx,
7250 dy: node.dy
7251 };
7252 }
7253 function d3_layout_treemapPad(node, padding) {
7254 var x = node.x + padding[3], y = node.y + padding[0], dx = node.dx - padding[1] - padding[3], dy = node.dy - padding[0] - padding[2];
7255 if (dx < 0) {
7256 x += dx / 2;
7257 dx = 0;
7258 }
7259 if (dy < 0) {
7260 y += dy / 2;
7261 dy = 0;
7262 }
7263 return {
7264 x: x,
7265 y: y,
7266 dx: dx,
7267 dy: dy
7268 };
7269 }
7270 d3.random = {
7271 normal: function(µ, σ) {
7272 var n = arguments.length;
7273 if (n < 2) σ = 1;
7274 if (n < 1) µ = 0;
7275 return function() {
7276 var x, y, r;
7277 do {
7278 x = Math.random() * 2 - 1;
7279 y = Math.random() * 2 - 1;
7280 r = x * x + y * y;
7281 } while (!r || r > 1);
7282 return µ + σ * x * Math.sqrt(-2 * Math.log(r) / r);
7283 };
7284 },
7285 logNormal: function() {
7286 var random = d3.random.normal.apply(d3, arguments);
7287 return function() {
7288 return Math.exp(random());
7289 };
7290 },
7291 bates: function(m) {
7292 var random = d3.random.irwinHall(m);
7293 return function() {
7294 return random() / m;
7295 };
7296 },
7297 irwinHall: function(m) {
7298 return function() {
7299 for (var s = 0, j = 0; j < m; j++) s += Math.random();
7300 return s;
7301 };
7302 }
7303 };
7304 d3.scale = {};
7305 function d3_scaleExtent(domain) {
7306 var start = domain[0], stop = domain[domain.length - 1];
7307 return start < stop ? [ start, stop ] : [ stop, start ];
7308 }
7309 function d3_scaleRange(scale) {
7310 return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range());
7311 }
7312 function d3_scale_bilinear(domain, range, uninterpolate, interpolate) {
7313 var u = uninterpolate(domain[0], domain[1]), i = interpolate(range[0], range[1]);
7314 return function(x) {
7315 return i(u(x));
7316 };
7317 }
7318 function d3_scale_nice(domain, nice) {
7319 var i0 = 0, i1 = domain.length - 1, x0 = domain[i0], x1 = domain[i1], dx;
7320 if (x1 < x0) {
7321 dx = i0, i0 = i1, i1 = dx;
7322 dx = x0, x0 = x1, x1 = dx;
7323 }
7324 domain[i0] = nice.floor(x0);
7325 domain[i1] = nice.ceil(x1);
7326 return domain;
7327 }
7328 function d3_scale_niceStep(step) {
7329 return step ? {
7330 floor: function(x) {
7331 return Math.floor(x / step) * step;
7332 },
7333 ceil: function(x) {
7334 return Math.ceil(x / step) * step;
7335 }
7336 } : d3_scale_niceIdentity;
7337 }
7338 var d3_scale_niceIdentity = {
7339 floor: d3_identity,
7340 ceil: d3_identity
7341 };
7342 function d3_scale_polylinear(domain, range, uninterpolate, interpolate) {
7343 var u = [], i = [], j = 0, k = Math.min(domain.length, range.length) - 1;
7344 if (domain[k] < domain[0]) {
7345 domain = domain.slice().reverse();
7346 range = range.slice().reverse();
7347 }
7348 while (++j <= k) {
7349 u.push(uninterpolate(domain[j - 1], domain[j]));
7350 i.push(interpolate(range[j - 1], range[j]));
7351 }
7352 return function(x) {
7353 var j = d3.bisect(domain, x, 1, k) - 1;
7354 return i[j](u[j](x));
7355 };
7356 }
7357 d3.scale.linear = function() {
7358 return d3_scale_linear([ 0, 1 ], [ 0, 1 ], d3_interpolate, false);
7359 };
7360 function d3_scale_linear(domain, range, interpolate, clamp) {
7361 var output, input;
7362 function rescale() {
7363 var linear = Math.min(domain.length, range.length) > 2 ? d3_scale_polylinear : d3_scale_bilinear, uninterpolate = clamp ? d3_uninterpolateClamp : d3_uninterpolateNumber;
7364 output = linear(domain, range, uninterpolate, interpolate);
7365 input = linear(range, domain, uninterpolate, d3_interpolate);
7366 return scale;
7367 }
7368 function scale(x) {
7369 return output(x);
7370 }
7371 scale.invert = function(y) {
7372 return input(y);
7373 };
7374 scale.domain = function(x) {
7375 if (!arguments.length) return domain;
7376 domain = x.map(Number);
7377 return rescale();
7378 };
7379 scale.range = function(x) {
7380 if (!arguments.length) return range;
7381 range = x;
7382 return rescale();
7383 };
7384 scale.rangeRound = function(x) {
7385 return scale.range(x).interpolate(d3_interpolateRound);
7386 };
7387 scale.clamp = function(x) {
7388 if (!arguments.length) return clamp;
7389 clamp = x;
7390 return rescale();
7391 };
7392 scale.interpolate = function(x) {
7393 if (!arguments.length) return interpolate;
7394 interpolate = x;
7395 return rescale();
7396 };
7397 scale.ticks = function(m) {
7398 return d3_scale_linearTicks(domain, m);
7399 };
7400 scale.tickFormat = function(m, format) {
7401 return d3_scale_linearTickFormat(domain, m, format);
7402 };
7403 scale.nice = function(m) {
7404 d3_scale_linearNice(domain, m);
7405 return rescale();
7406 };
7407 scale.copy = function() {
7408 return d3_scale_linear(domain, range, interpolate, clamp);
7409 };
7410 return rescale();
7411 }
7412 function d3_scale_linearRebind(scale, linear) {
7413 return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp");
7414 }
7415 function d3_scale_linearNice(domain, m) {
7416 return d3_scale_nice(domain, d3_scale_niceStep(d3_scale_linearTickRange(domain, m)[2]));
7417 }
7418 function d3_scale_linearTickRange(domain, m) {
7419 if (m == null) m = 10;
7420 var extent = d3_scaleExtent(domain), span = extent[1] - extent[0], step = Math.pow(10, Math.floor(Math.log(span / m) / Math.LN10)), err = m / span * step;
7421 if (err <= .15) step *= 10; else if (err <= .35) step *= 5; else if (err <= .75) step *= 2;
7422 extent[0] = Math.ceil(extent[0] / step) * step;
7423 extent[1] = Math.floor(extent[1] / step) * step + step * .5;
7424 extent[2] = step;
7425 return extent;
7426 }
7427 function d3_scale_linearTicks(domain, m) {
7428 return d3.range.apply(d3, d3_scale_linearTickRange(domain, m));
7429 }
7430 function d3_scale_linearTickFormat(domain, m, format) {
7431 var range = d3_scale_linearTickRange(domain, m);
7432 if (format) {
7433 var match = d3_format_re.exec(format);
7434 match.shift();
7435 if (match[8] === "s") {
7436 var prefix = d3.formatPrefix(Math.max(abs(range[0]), abs(range[1])));
7437 if (!match[7]) match[7] = "." + d3_scale_linearPrecision(prefix.scale(range[2]));
7438 match[8] = "f";
7439 format = d3.format(match.join(""));
7440 return function(d) {
7441 return format(prefix.scale(d)) + prefix.symbol;
7442 };
7443 }
7444 if (!match[7]) match[7] = "." + d3_scale_linearFormatPrecision(match[8], range);
7445 format = match.join("");
7446 } else {
7447 format = ",." + d3_scale_linearPrecision(range[2]) + "f";
7448 }
7449 return d3.format(format);
7450 }
7451 var d3_scale_linearFormatSignificant = {
7452 s: 1,
7453 g: 1,
7454 p: 1,
7455 r: 1,
7456 e: 1
7457 };
7458 function d3_scale_linearPrecision(value) {
7459 return -Math.floor(Math.log(value) / Math.LN10 + .01);
7460 }
7461 function d3_scale_linearFormatPrecision(type, range) {
7462 var p = d3_scale_linearPrecision(range[2]);
7463 return type in d3_scale_linearFormatSignificant ? Math.abs(p - d3_scale_linearPrecision(Math.max(abs(range[0]), abs(range[1])))) + +(type !== "e") : p - (type === "%") * 2;
7464 }
7465 d3.scale.log = function() {
7466 return d3_scale_log(d3.scale.linear().domain([ 0, 1 ]), 10, true, [ 1, 10 ]);
7467 };
7468 function d3_scale_log(linear, base, positive, domain) {
7469 function log(x) {
7470 return (positive ? Math.log(x < 0 ? 0 : x) : -Math.log(x > 0 ? 0 : -x)) / Math.log(base);
7471 }
7472 function pow(x) {
7473 return positive ? Math.pow(base, x) : -Math.pow(base, -x);
7474 }
7475 function scale(x) {
7476 return linear(log(x));
7477 }
7478 scale.invert = function(x) {
7479 return pow(linear.invert(x));
7480 };
7481 scale.domain = function(x) {
7482 if (!arguments.length) return domain;
7483 positive = x[0] >= 0;
7484 linear.domain((domain = x.map(Number)).map(log));
7485 return scale;
7486 };
7487 scale.base = function(_) {
7488 if (!arguments.length) return base;
7489 base = +_;
7490 linear.domain(domain.map(log));
7491 return scale;
7492 };
7493 scale.nice = function() {
7494 var niced = d3_scale_nice(domain.map(log), positive ? Math : d3_scale_logNiceNegative);
7495 linear.domain(niced);
7496 domain = niced.map(pow);
7497 return scale;
7498 };
7499 scale.ticks = function() {
7500 var extent = d3_scaleExtent(domain), ticks = [], u = extent[0], v = extent[1], i = Math.floor(log(u)), j = Math.ceil(log(v)), n = base % 1 ? 2 : base;
7501 if (isFinite(j - i)) {
7502 if (positive) {
7503 for (;i < j; i++) for (var k = 1; k < n; k++) ticks.push(pow(i) * k);
7504 ticks.push(pow(i));
7505 } else {
7506 ticks.push(pow(i));
7507 for (;i++ < j; ) for (var k = n - 1; k > 0; k--) ticks.push(pow(i) * k);
7508 }
7509 for (i = 0; ticks[i] < u; i++) {}
7510 for (j = ticks.length; ticks[j - 1] > v; j--) {}
7511 ticks = ticks.slice(i, j);
7512 }
7513 return ticks;
7514 };
7515 scale.tickFormat = function(n, format) {
7516 if (!arguments.length) return d3_scale_logFormat;
7517 if (arguments.length < 2) format = d3_scale_logFormat; else if (typeof format !== "function") format = d3.format(format);
7518 var k = Math.max(.1, n / scale.ticks().length), f = positive ? (e = 1e-12, Math.ceil) : (e = -1e-12,
7519 Math.floor), e;
7520 return function(d) {
7521 return d / pow(f(log(d) + e)) <= k ? format(d) : "";
7522 };
7523 };
7524 scale.copy = function() {
7525 return d3_scale_log(linear.copy(), base, positive, domain);
7526 };
7527 return d3_scale_linearRebind(scale, linear);
7528 }
7529 var d3_scale_logFormat = d3.format(".0e"), d3_scale_logNiceNegative = {
7530 floor: function(x) {
7531 return -Math.ceil(-x);
7532 },
7533 ceil: function(x) {
7534 return -Math.floor(-x);
7535 }
7536 };
7537 d3.scale.pow = function() {
7538 return d3_scale_pow(d3.scale.linear(), 1, [ 0, 1 ]);
7539 };
7540 function d3_scale_pow(linear, exponent, domain) {
7541 var powp = d3_scale_powPow(exponent), powb = d3_scale_powPow(1 / exponent);
7542 function scale(x) {
7543 return linear(powp(x));
7544 }
7545 scale.invert = function(x) {
7546 return powb(linear.invert(x));
7547 };
7548 scale.domain = function(x) {
7549 if (!arguments.length) return domain;
7550 linear.domain((domain = x.map(Number)).map(powp));
7551 return scale;
7552 };
7553 scale.ticks = function(m) {
7554 return d3_scale_linearTicks(domain, m);
7555 };
7556 scale.tickFormat = function(m, format) {
7557 return d3_scale_linearTickFormat(domain, m, format);
7558 };
7559 scale.nice = function(m) {
7560 return scale.domain(d3_scale_linearNice(domain, m));
7561 };
7562 scale.exponent = function(x) {
7563 if (!arguments.length) return exponent;
7564 powp = d3_scale_powPow(exponent = x);
7565 powb = d3_scale_powPow(1 / exponent);
7566 linear.domain(domain.map(powp));
7567 return scale;
7568 };
7569 scale.copy = function() {
7570 return d3_scale_pow(linear.copy(), exponent, domain);
7571 };
7572 return d3_scale_linearRebind(scale, linear);
7573 }
7574 function d3_scale_powPow(e) {
7575 return function(x) {
7576 return x < 0 ? -Math.pow(-x, e) : Math.pow(x, e);
7577 };
7578 }
7579 d3.scale.sqrt = function() {
7580 return d3.scale.pow().exponent(.5);
7581 };
7582 d3.scale.ordinal = function() {
7583 return d3_scale_ordinal([], {
7584 t: "range",
7585 a: [ [] ]
7586 });
7587 };
7588 function d3_scale_ordinal(domain, ranger) {
7589 var index, range, rangeBand;
7590 function scale(x) {
7591 return range[((index.get(x) || (ranger.t === "range" ? index.set(x, domain.push(x)) : NaN)) - 1) % range.length];
7592 }
7593 function steps(start, step) {
7594 return d3.range(domain.length).map(function(i) {
7595 return start + step * i;
7596 });
7597 }
7598 scale.domain = function(x) {
7599 if (!arguments.length) return domain;
7600 domain = [];
7601 index = new d3_Map();
7602 var i = -1, n = x.length, xi;
7603 while (++i < n) if (!index.has(xi = x[i])) index.set(xi, domain.push(xi));
7604 return scale[ranger.t].apply(scale, ranger.a);
7605 };
7606 scale.range = function(x) {
7607 if (!arguments.length) return range;
7608 range = x;
7609 rangeBand = 0;
7610 ranger = {
7611 t: "range",
7612 a: arguments
7613 };
7614 return scale;
7615 };
7616 scale.rangePoints = function(x, padding) {
7617 if (arguments.length < 2) padding = 0;
7618 var start = x[0], stop = x[1], step = (stop - start) / (Math.max(1, domain.length - 1) + padding);
7619 range = steps(domain.length < 2 ? (start + stop) / 2 : start + step * padding / 2, step);
7620 rangeBand = 0;
7621 ranger = {
7622 t: "rangePoints",
7623 a: arguments
7624 };
7625 return scale;
7626 };
7627 scale.rangeBands = function(x, padding, outerPadding) {
7628 if (arguments.length < 2) padding = 0;
7629 if (arguments.length < 3) outerPadding = padding;
7630 var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = (stop - start) / (domain.length - padding + 2 * outerPadding);
7631 range = steps(start + step * outerPadding, step);
7632 if (reverse) range.reverse();
7633 rangeBand = step * (1 - padding);
7634 ranger = {
7635 t: "rangeBands",
7636 a: arguments
7637 };
7638 return scale;
7639 };
7640 scale.rangeRoundBands = function(x, padding, outerPadding) {
7641 if (arguments.length < 2) padding = 0;
7642 if (arguments.length < 3) outerPadding = padding;
7643 var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = Math.floor((stop - start) / (domain.length - padding + 2 * outerPadding)), error = stop - start - (domain.length - padding) * step;
7644 range = steps(start + Math.round(error / 2), step);
7645 if (reverse) range.reverse();
7646 rangeBand = Math.round(step * (1 - padding));
7647 ranger = {
7648 t: "rangeRoundBands",
7649 a: arguments
7650 };
7651 return scale;
7652 };
7653 scale.rangeBand = function() {
7654 return rangeBand;
7655 };
7656 scale.rangeExtent = function() {
7657 return d3_scaleExtent(ranger.a[0]);
7658 };
7659 scale.copy = function() {
7660 return d3_scale_ordinal(domain, ranger);
7661 };
7662 return scale.domain(domain);
7663 }
7664 d3.scale.category10 = function() {
7665 return d3.scale.ordinal().range(d3_category10);
7666 };
7667 d3.scale.category20 = function() {
7668 return d3.scale.ordinal().range(d3_category20);
7669 };
7670 d3.scale.category20b = function() {
7671 return d3.scale.ordinal().range(d3_category20b);
7672 };
7673 d3.scale.category20c = function() {
7674 return d3.scale.ordinal().range(d3_category20c);
7675 };
7676 var d3_category10 = [ 2062260, 16744206, 2924588, 14034728, 9725885, 9197131, 14907330, 8355711, 12369186, 1556175 ].map(d3_rgbString);
7677 var d3_category20 = [ 2062260, 11454440, 16744206, 16759672, 2924588, 10018698, 14034728, 16750742, 9725885, 12955861, 9197131, 12885140, 14907330, 16234194, 8355711, 13092807, 12369186, 14408589, 1556175, 10410725 ].map(d3_rgbString);
7678 var d3_category20b = [ 3750777, 5395619, 7040719, 10264286, 6519097, 9216594, 11915115, 13556636, 9202993, 12426809, 15186514, 15190932, 8666169, 11356490, 14049643, 15177372, 8077683, 10834324, 13528509, 14589654 ].map(d3_rgbString);
7679 var d3_category20c = [ 3244733, 7057110, 10406625, 13032431, 15095053, 16616764, 16625259, 16634018, 3253076, 7652470, 10607003, 13101504, 7695281, 10394312, 12369372, 14342891, 6513507, 9868950, 12434877, 14277081 ].map(d3_rgbString);
7680 d3.scale.quantile = function() {
7681 return d3_scale_quantile([], []);
7682 };
7683 function d3_scale_quantile(domain, range) {
7684 var thresholds;
7685 function rescale() {
7686 var k = 0, q = range.length;
7687 thresholds = [];
7688 while (++k < q) thresholds[k - 1] = d3.quantile(domain, k / q);
7689 return scale;
7690 }
7691 function scale(x) {
7692 if (!isNaN(x = +x)) return range[d3.bisect(thresholds, x)];
7693 }
7694 scale.domain = function(x) {
7695 if (!arguments.length) return domain;
7696 domain = x.map(d3_number).filter(d3_numeric).sort(d3_ascending);
7697 return rescale();
7698 };
7699 scale.range = function(x) {
7700 if (!arguments.length) return range;
7701 range = x;
7702 return rescale();
7703 };
7704 scale.quantiles = function() {
7705 return thresholds;
7706 };
7707 scale.invertExtent = function(y) {
7708 y = range.indexOf(y);
7709 return y < 0 ? [ NaN, NaN ] : [ y > 0 ? thresholds[y - 1] : domain[0], y < thresholds.length ? thresholds[y] : domain[domain.length - 1] ];
7710 };
7711 scale.copy = function() {
7712 return d3_scale_quantile(domain, range);
7713 };
7714 return rescale();
7715 }
7716 d3.scale.quantize = function() {
7717 return d3_scale_quantize(0, 1, [ 0, 1 ]);
7718 };
7719 function d3_scale_quantize(x0, x1, range) {
7720 var kx, i;
7721 function scale(x) {
7722 return range[Math.max(0, Math.min(i, Math.floor(kx * (x - x0))))];
7723 }
7724 function rescale() {
7725 kx = range.length / (x1 - x0);
7726 i = range.length - 1;
7727 return scale;
7728 }
7729 scale.domain = function(x) {
7730 if (!arguments.length) return [ x0, x1 ];
7731 x0 = +x[0];
7732 x1 = +x[x.length - 1];
7733 return rescale();
7734 };
7735 scale.range = function(x) {
7736 if (!arguments.length) return range;
7737 range = x;
7738 return rescale();
7739 };
7740 scale.invertExtent = function(y) {
7741 y = range.indexOf(y);
7742 y = y < 0 ? NaN : y / kx + x0;
7743 return [ y, y + 1 / kx ];
7744 };
7745 scale.copy = function() {
7746 return d3_scale_quantize(x0, x1, range);
7747 };
7748 return rescale();
7749 }
7750 d3.scale.threshold = function() {
7751 return d3_scale_threshold([ .5 ], [ 0, 1 ]);
7752 };
7753 function d3_scale_threshold(domain, range) {
7754 function scale(x) {
7755 if (x <= x) return range[d3.bisect(domain, x)];
7756 }
7757 scale.domain = function(_) {
7758 if (!arguments.length) return domain;
7759 domain = _;
7760 return scale;
7761 };
7762 scale.range = function(_) {
7763 if (!arguments.length) return range;
7764 range = _;
7765 return scale;
7766 };
7767 scale.invertExtent = function(y) {
7768 y = range.indexOf(y);
7769 return [ domain[y - 1], domain[y] ];
7770 };
7771 scale.copy = function() {
7772 return d3_scale_threshold(domain, range);
7773 };
7774 return scale;
7775 }
7776 d3.scale.identity = function() {
7777 return d3_scale_identity([ 0, 1 ]);
7778 };
7779 function d3_scale_identity(domain) {
7780 function identity(x) {
7781 return +x;
7782 }
7783 identity.invert = identity;
7784 identity.domain = identity.range = function(x) {
7785 if (!arguments.length) return domain;
7786 domain = x.map(identity);
7787 return identity;
7788 };
7789 identity.ticks = function(m) {
7790 return d3_scale_linearTicks(domain, m);
7791 };
7792 identity.tickFormat = function(m, format) {
7793 return d3_scale_linearTickFormat(domain, m, format);
7794 };
7795 identity.copy = function() {
7796 return d3_scale_identity(domain);
7797 };
7798 return identity;
7799 }
7800 d3.svg = {};
7801 d3.svg.arc = function() {
7802 var innerRadius = d3_svg_arcInnerRadius, outerRadius = d3_svg_arcOuterRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle;
7803 function arc() {
7804 var r0 = innerRadius.apply(this, arguments), r1 = outerRadius.apply(this, arguments), a0 = startAngle.apply(this, arguments) + d3_svg_arcOffset, a1 = endAngle.apply(this, arguments) + d3_svg_arcOffset, da = (a1 < a0 && (da = a0,
7805 a0 = a1, a1 = da), a1 - a0), df = da < π ? "0" : "1", c0 = Math.cos(a0), s0 = Math.sin(a0), c1 = Math.cos(a1), s1 = Math.sin(a1);
7806 return da >= d3_svg_arcMax ? r0 ? "M0," + r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + -r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + r1 + "M0," + r0 + "A" + r0 + "," + r0 + " 0 1,0 0," + -r0 + "A" + r0 + "," + r0 + " 0 1,0 0," + r0 + "Z" : "M0," + r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + -r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + r1 + "Z" : r0 ? "M" + r1 * c0 + "," + r1 * s0 + "A" + r1 + "," + r1 + " 0 " + df + ",1 " + r1 * c1 + "," + r1 * s1 + "L" + r0 * c1 + "," + r0 * s1 + "A" + r0 + "," + r0 + " 0 " + df + ",0 " + r0 * c0 + "," + r0 * s0 + "Z" : "M" + r1 * c0 + "," + r1 * s0 + "A" + r1 + "," + r1 + " 0 " + df + ",1 " + r1 * c1 + "," + r1 * s1 + "L0,0" + "Z";
7807 }
7808 arc.innerRadius = function(v) {
7809 if (!arguments.length) return innerRadius;
7810 innerRadius = d3_functor(v);
7811 return arc;
7812 };
7813 arc.outerRadius = function(v) {
7814 if (!arguments.length) return outerRadius;
7815 outerRadius = d3_functor(v);
7816 return arc;
7817 };
7818 arc.startAngle = function(v) {
7819 if (!arguments.length) return startAngle;
7820 startAngle = d3_functor(v);
7821 return arc;
7822 };
7823 arc.endAngle = function(v) {
7824 if (!arguments.length) return endAngle;
7825 endAngle = d3_functor(v);
7826 return arc;
7827 };
7828 arc.centroid = function() {
7829 var r = (innerRadius.apply(this, arguments) + outerRadius.apply(this, arguments)) / 2, a = (startAngle.apply(this, arguments) + endAngle.apply(this, arguments)) / 2 + d3_svg_arcOffset;
7830 return [ Math.cos(a) * r, Math.sin(a) * r ];
7831 };
7832 return arc;
7833 };
7834 var d3_svg_arcOffset = -halfπ, d3_svg_arcMax = τ - ε;
7835 function d3_svg_arcInnerRadius(d) {
7836 return d.innerRadius;
7837 }
7838 function d3_svg_arcOuterRadius(d) {
7839 return d.outerRadius;
7840 }
7841 function d3_svg_arcStartAngle(d) {
7842 return d.startAngle;
7843 }
7844 function d3_svg_arcEndAngle(d) {
7845 return d.endAngle;
7846 }
7847 function d3_svg_line(projection) {
7848 var x = d3_geom_pointX, y = d3_geom_pointY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, tension = .7;
7849 function line(data) {
7850 var segments = [], points = [], i = -1, n = data.length, d, fx = d3_functor(x), fy = d3_functor(y);
7851 function segment() {
7852 segments.push("M", interpolate(projection(points), tension));
7853 }
7854 while (++i < n) {
7855 if (defined.call(this, d = data[i], i)) {
7856 points.push([ +fx.call(this, d, i), +fy.call(this, d, i) ]);
7857 } else if (points.length) {
7858 segment();
7859 points = [];
7860 }
7861 }
7862 if (points.length) segment();
7863 return segments.length ? segments.join("") : null;
7864 }
7865 line.x = function(_) {
7866 if (!arguments.length) return x;
7867 x = _;
7868 return line;
7869 };
7870 line.y = function(_) {
7871 if (!arguments.length) return y;
7872 y = _;
7873 return line;
7874 };
7875 line.defined = function(_) {
7876 if (!arguments.length) return defined;
7877 defined = _;
7878 return line;
7879 };
7880 line.interpolate = function(_) {
7881 if (!arguments.length) return interpolateKey;
7882 if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key;
7883 return line;
7884 };
7885 line.tension = function(_) {
7886 if (!arguments.length) return tension;
7887 tension = _;
7888 return line;
7889 };
7890 return line;
7891 }
7892 d3.svg.line = function() {
7893 return d3_svg_line(d3_identity);
7894 };
7895 var d3_svg_lineInterpolators = d3.map({
7896 linear: d3_svg_lineLinear,
7897 "linear-closed": d3_svg_lineLinearClosed,
7898 step: d3_svg_lineStep,
7899 "step-before": d3_svg_lineStepBefore,
7900 "step-after": d3_svg_lineStepAfter,
7901 basis: d3_svg_lineBasis,
7902 "basis-open": d3_svg_lineBasisOpen,
7903 "basis-closed": d3_svg_lineBasisClosed,
7904 bundle: d3_svg_lineBundle,
7905 cardinal: d3_svg_lineCardinal,
7906 "cardinal-open": d3_svg_lineCardinalOpen,
7907 "cardinal-closed": d3_svg_lineCardinalClosed,
7908 monotone: d3_svg_lineMonotone
7909 });
7910 d3_svg_lineInterpolators.forEach(function(key, value) {
7911 value.key = key;
7912 value.closed = /-closed$/.test(key);
7913 });
7914 function d3_svg_lineLinear(points) {
7915 return points.join("L");
7916 }
7917 function d3_svg_lineLinearClosed(points) {
7918 return d3_svg_lineLinear(points) + "Z";
7919 }
7920 function d3_svg_lineStep(points) {
7921 var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ];
7922 while (++i < n) path.push("H", (p[0] + (p = points[i])[0]) / 2, "V", p[1]);
7923 if (n > 1) path.push("H", p[0]);
7924 return path.join("");
7925 }
7926 function d3_svg_lineStepBefore(points) {
7927 var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ];
7928 while (++i < n) path.push("V", (p = points[i])[1], "H", p[0]);
7929 return path.join("");
7930 }
7931 function d3_svg_lineStepAfter(points) {
7932 var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ];
7933 while (++i < n) path.push("H", (p = points[i])[0], "V", p[1]);
7934 return path.join("");
7935 }
7936 function d3_svg_lineCardinalOpen(points, tension) {
7937 return points.length < 4 ? d3_svg_lineLinear(points) : points[1] + d3_svg_lineHermite(points.slice(1, points.length - 1), d3_svg_lineCardinalTangents(points, tension));
7938 }
7939 function d3_svg_lineCardinalClosed(points, tension) {
7940 return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite((points.push(points[0]),
7941 points), d3_svg_lineCardinalTangents([ points[points.length - 2] ].concat(points, [ points[1] ]), tension));
7942 }
7943 function d3_svg_lineCardinal(points, tension) {
7944 return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineCardinalTangents(points, tension));
7945 }
7946 function d3_svg_lineHermite(points, tangents) {
7947 if (tangents.length < 1 || points.length != tangents.length && points.length != tangents.length + 2) {
7948 return d3_svg_lineLinear(points);
7949 }
7950 var quad = points.length != tangents.length, path = "", p0 = points[0], p = points[1], t0 = tangents[0], t = t0, pi = 1;
7951 if (quad) {
7952 path += "Q" + (p[0] - t0[0] * 2 / 3) + "," + (p[1] - t0[1] * 2 / 3) + "," + p[0] + "," + p[1];
7953 p0 = points[1];
7954 pi = 2;
7955 }
7956 if (tangents.length > 1) {
7957 t = tangents[1];
7958 p = points[pi];
7959 pi++;
7960 path += "C" + (p0[0] + t0[0]) + "," + (p0[1] + t0[1]) + "," + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1];
7961 for (var i = 2; i < tangents.length; i++, pi++) {
7962 p = points[pi];
7963 t = tangents[i];
7964 path += "S" + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1];
7965 }
7966 }
7967 if (quad) {
7968 var lp = points[pi];
7969 path += "Q" + (p[0] + t[0] * 2 / 3) + "," + (p[1] + t[1] * 2 / 3) + "," + lp[0] + "," + lp[1];
7970 }
7971 return path;
7972 }
7973 function d3_svg_lineCardinalTangents(points, tension) {
7974 var tangents = [], a = (1 - tension) / 2, p0, p1 = points[0], p2 = points[1], i = 1, n = points.length;
7975 while (++i < n) {
7976 p0 = p1;
7977 p1 = p2;
7978 p2 = points[i];
7979 tangents.push([ a * (p2[0] - p0[0]), a * (p2[1] - p0[1]) ]);
7980 }
7981 return tangents;
7982 }
7983 function d3_svg_lineBasis(points) {
7984 if (points.length < 3) return d3_svg_lineLinear(points);
7985 var i = 1, n = points.length, pi = points[0], x0 = pi[0], y0 = pi[1], px = [ x0, x0, x0, (pi = points[1])[0] ], py = [ y0, y0, y0, pi[1] ], path = [ x0, ",", y0, "L", d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ];
7986 points.push(points[n - 1]);
7987 while (++i <= n) {
7988 pi = points[i];
7989 px.shift();
7990 px.push(pi[0]);
7991 py.shift();
7992 py.push(pi[1]);
7993 d3_svg_lineBasisBezier(path, px, py);
7994 }
7995 points.pop();
7996 path.push("L", pi);
7997 return path.join("");
7998 }
7999 function d3_svg_lineBasisOpen(points) {
8000 if (points.length < 4) return d3_svg_lineLinear(points);
8001 var path = [], i = -1, n = points.length, pi, px = [ 0 ], py = [ 0 ];
8002 while (++i < 3) {
8003 pi = points[i];
8004 px.push(pi[0]);
8005 py.push(pi[1]);
8006 }
8007 path.push(d3_svg_lineDot4(d3_svg_lineBasisBezier3, px) + "," + d3_svg_lineDot4(d3_svg_lineBasisBezier3, py));
8008 --i;
8009 while (++i < n) {
8010 pi = points[i];
8011 px.shift();
8012 px.push(pi[0]);
8013 py.shift();
8014 py.push(pi[1]);
8015 d3_svg_lineBasisBezier(path, px, py);
8016 }
8017 return path.join("");
8018 }
8019 function d3_svg_lineBasisClosed(points) {
8020 var path, i = -1, n = points.length, m = n + 4, pi, px = [], py = [];
8021 while (++i < 4) {
8022 pi = points[i % n];
8023 px.push(pi[0]);
8024 py.push(pi[1]);
8025 }
8026 path = [ d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ];
8027 --i;
8028 while (++i < m) {
8029 pi = points[i % n];
8030 px.shift();
8031 px.push(pi[0]);
8032 py.shift();
8033 py.push(pi[1]);
8034 d3_svg_lineBasisBezier(path, px, py);
8035 }
8036 return path.join("");
8037 }
8038 function d3_svg_lineBundle(points, tension) {
8039 var n = points.length - 1;
8040 if (n) {
8041 var x0 = points[0][0], y0 = points[0][1], dx = points[n][0] - x0, dy = points[n][1] - y0, i = -1, p, t;
8042 while (++i <= n) {
8043 p = points[i];
8044 t = i / n;
8045 p[0] = tension * p[0] + (1 - tension) * (x0 + t * dx);
8046 p[1] = tension * p[1] + (1 - tension) * (y0 + t * dy);
8047 }
8048 }
8049 return d3_svg_lineBasis(points);
8050 }
8051 function d3_svg_lineDot4(a, b) {
8052 return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
8053 }
8054 var d3_svg_lineBasisBezier1 = [ 0, 2 / 3, 1 / 3, 0 ], d3_svg_lineBasisBezier2 = [ 0, 1 / 3, 2 / 3, 0 ], d3_svg_lineBasisBezier3 = [ 0, 1 / 6, 2 / 3, 1 / 6 ];
8055 function d3_svg_lineBasisBezier(path, x, y) {
8056 path.push("C", d3_svg_lineDot4(d3_svg_lineBasisBezier1, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier1, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, y));
8057 }
8058 function d3_svg_lineSlope(p0, p1) {
8059 return (p1[1] - p0[1]) / (p1[0] - p0[0]);
8060 }
8061 function d3_svg_lineFiniteDifferences(points) {
8062 var i = 0, j = points.length - 1, m = [], p0 = points[0], p1 = points[1], d = m[0] = d3_svg_lineSlope(p0, p1);
8063 while (++i < j) {
8064 m[i] = (d + (d = d3_svg_lineSlope(p0 = p1, p1 = points[i + 1]))) / 2;
8065 }
8066 m[i] = d;
8067 return m;
8068 }
8069 function d3_svg_lineMonotoneTangents(points) {
8070 var tangents = [], d, a, b, s, m = d3_svg_lineFiniteDifferences(points), i = -1, j = points.length - 1;
8071 while (++i < j) {
8072 d = d3_svg_lineSlope(points[i], points[i + 1]);
8073 if (abs(d) < ε) {
8074 m[i] = m[i + 1] = 0;
8075 } else {
8076 a = m[i] / d;
8077 b = m[i + 1] / d;
8078 s = a * a + b * b;
8079 if (s > 9) {
8080 s = d * 3 / Math.sqrt(s);
8081 m[i] = s * a;
8082 m[i + 1] = s * b;
8083 }
8084 }
8085 }
8086 i = -1;
8087 while (++i <= j) {
8088 s = (points[Math.min(j, i + 1)][0] - points[Math.max(0, i - 1)][0]) / (6 * (1 + m[i] * m[i]));
8089 tangents.push([ s || 0, m[i] * s || 0 ]);
8090 }
8091 return tangents;
8092 }
8093 function d3_svg_lineMonotone(points) {
8094 return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineMonotoneTangents(points));
8095 }
8096 d3.svg.line.radial = function() {
8097 var line = d3_svg_line(d3_svg_lineRadial);
8098 line.radius = line.x, delete line.x;
8099 line.angle = line.y, delete line.y;
8100 return line;
8101 };
8102 function d3_svg_lineRadial(points) {
8103 var point, i = -1, n = points.length, r, a;
8104 while (++i < n) {
8105 point = points[i];
8106 r = point[0];
8107 a = point[1] + d3_svg_arcOffset;
8108 point[0] = r * Math.cos(a);
8109 point[1] = r * Math.sin(a);
8110 }
8111 return points;
8112 }
8113 function d3_svg_area(projection) {
8114 var x0 = d3_geom_pointX, x1 = d3_geom_pointX, y0 = 0, y1 = d3_geom_pointY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, interpolateReverse = interpolate, L = "L", tension = .7;
8115 function area(data) {
8116 var segments = [], points0 = [], points1 = [], i = -1, n = data.length, d, fx0 = d3_functor(x0), fy0 = d3_functor(y0), fx1 = x0 === x1 ? function() {
8117 return x;
8118 } : d3_functor(x1), fy1 = y0 === y1 ? function() {
8119 return y;
8120 } : d3_functor(y1), x, y;
8121 function segment() {
8122 segments.push("M", interpolate(projection(points1), tension), L, interpolateReverse(projection(points0.reverse()), tension), "Z");
8123 }
8124 while (++i < n) {
8125 if (defined.call(this, d = data[i], i)) {
8126 points0.push([ x = +fx0.call(this, d, i), y = +fy0.call(this, d, i) ]);
8127 points1.push([ +fx1.call(this, d, i), +fy1.call(this, d, i) ]);
8128 } else if (points0.length) {
8129 segment();
8130 points0 = [];
8131 points1 = [];
8132 }
8133 }
8134 if (points0.length) segment();
8135 return segments.length ? segments.join("") : null;
8136 }
8137 area.x = function(_) {
8138 if (!arguments.length) return x1;
8139 x0 = x1 = _;
8140 return area;
8141 };
8142 area.x0 = function(_) {
8143 if (!arguments.length) return x0;
8144 x0 = _;
8145 return area;
8146 };
8147 area.x1 = function(_) {
8148 if (!arguments.length) return x1;
8149 x1 = _;
8150 return area;
8151 };
8152 area.y = function(_) {
8153 if (!arguments.length) return y1;
8154 y0 = y1 = _;
8155 return area;
8156 };
8157 area.y0 = function(_) {
8158 if (!arguments.length) return y0;
8159 y0 = _;
8160 return area;
8161 };
8162 area.y1 = function(_) {
8163 if (!arguments.length) return y1;
8164 y1 = _;
8165 return area;
8166 };
8167 area.defined = function(_) {
8168 if (!arguments.length) return defined;
8169 defined = _;
8170 return area;
8171 };
8172 area.interpolate = function(_) {
8173 if (!arguments.length) return interpolateKey;
8174 if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key;
8175 interpolateReverse = interpolate.reverse || interpolate;
8176 L = interpolate.closed ? "M" : "L";
8177 return area;
8178 };
8179 area.tension = function(_) {
8180 if (!arguments.length) return tension;
8181 tension = _;
8182 return area;
8183 };
8184 return area;
8185 }
8186 d3_svg_lineStepBefore.reverse = d3_svg_lineStepAfter;
8187 d3_svg_lineStepAfter.reverse = d3_svg_lineStepBefore;
8188 d3.svg.area = function() {
8189 return d3_svg_area(d3_identity);
8190 };
8191 d3.svg.area.radial = function() {
8192 var area = d3_svg_area(d3_svg_lineRadial);
8193 area.radius = area.x, delete area.x;
8194 area.innerRadius = area.x0, delete area.x0;
8195 area.outerRadius = area.x1, delete area.x1;
8196 area.angle = area.y, delete area.y;
8197 area.startAngle = area.y0, delete area.y0;
8198 area.endAngle = area.y1, delete area.y1;
8199 return area;
8200 };
8201 d3.svg.chord = function() {
8202 var source = d3_source, target = d3_target, radius = d3_svg_chordRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle;
8203 function chord(d, i) {
8204 var s = subgroup(this, source, d, i), t = subgroup(this, target, d, i);
8205 return "M" + s.p0 + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t) ? curve(s.r, s.p1, s.r, s.p0) : curve(s.r, s.p1, t.r, t.p0) + arc(t.r, t.p1, t.a1 - t.a0) + curve(t.r, t.p1, s.r, s.p0)) + "Z";
8206 }
8207 function subgroup(self, f, d, i) {
8208 var subgroup = f.call(self, d, i), r = radius.call(self, subgroup, i), a0 = startAngle.call(self, subgroup, i) + d3_svg_arcOffset, a1 = endAngle.call(self, subgroup, i) + d3_svg_arcOffset;
8209 return {
8210 r: r,
8211 a0: a0,
8212 a1: a1,
8213 p0: [ r * Math.cos(a0), r * Math.sin(a0) ],
8214 p1: [ r * Math.cos(a1), r * Math.sin(a1) ]
8215 };
8216 }
8217 function equals(a, b) {
8218 return a.a0 == b.a0 && a.a1 == b.a1;
8219 }
8220 function arc(r, p, a) {
8221 return "A" + r + "," + r + " 0 " + +(a > π) + ",1 " + p;
8222 }
8223 function curve(r0, p0, r1, p1) {
8224 return "Q 0,0 " + p1;
8225 }
8226 chord.radius = function(v) {
8227 if (!arguments.length) return radius;
8228 radius = d3_functor(v);
8229 return chord;
8230 };
8231 chord.source = function(v) {
8232 if (!arguments.length) return source;
8233 source = d3_functor(v);
8234 return chord;
8235 };
8236 chord.target = function(v) {
8237 if (!arguments.length) return target;
8238 target = d3_functor(v);
8239 return chord;
8240 };
8241 chord.startAngle = function(v) {
8242 if (!arguments.length) return startAngle;
8243 startAngle = d3_functor(v);
8244 return chord;
8245 };
8246 chord.endAngle = function(v) {
8247 if (!arguments.length) return endAngle;
8248 endAngle = d3_functor(v);
8249 return chord;
8250 };
8251 return chord;
8252 };
8253 function d3_svg_chordRadius(d) {
8254 return d.radius;
8255 }
8256 d3.svg.diagonal = function() {
8257 var source = d3_source, target = d3_target, projection = d3_svg_diagonalProjection;
8258 function diagonal(d, i) {
8259 var p0 = source.call(this, d, i), p3 = target.call(this, d, i), m = (p0.y + p3.y) / 2, p = [ p0, {
8260 x: p0.x,
8261 y: m
8262 }, {
8263 x: p3.x,
8264 y: m
8265 }, p3 ];
8266 p = p.map(projection);
8267 return "M" + p[0] + "C" + p[1] + " " + p[2] + " " + p[3];
8268 }
8269 diagonal.source = function(x) {
8270 if (!arguments.length) return source;
8271 source = d3_functor(x);
8272 return diagonal;
8273 };
8274 diagonal.target = function(x) {
8275 if (!arguments.length) return target;
8276 target = d3_functor(x);
8277 return diagonal;
8278 };
8279 diagonal.projection = function(x) {
8280 if (!arguments.length) return projection;
8281 projection = x;
8282 return diagonal;
8283 };
8284 return diagonal;
8285 };
8286 function d3_svg_diagonalProjection(d) {
8287 return [ d.x, d.y ];
8288 }
8289 d3.svg.diagonal.radial = function() {
8290 var diagonal = d3.svg.diagonal(), projection = d3_svg_diagonalProjection, projection_ = diagonal.projection;
8291 diagonal.projection = function(x) {
8292 return arguments.length ? projection_(d3_svg_diagonalRadialProjection(projection = x)) : projection;
8293 };
8294 return diagonal;
8295 };
8296 function d3_svg_diagonalRadialProjection(projection) {
8297 return function() {
8298 var d = projection.apply(this, arguments), r = d[0], a = d[1] + d3_svg_arcOffset;
8299 return [ r * Math.cos(a), r * Math.sin(a) ];
8300 };
8301 }
8302 d3.svg.symbol = function() {
8303 var type = d3_svg_symbolType, size = d3_svg_symbolSize;
8304 function symbol(d, i) {
8305 return (d3_svg_symbols.get(type.call(this, d, i)) || d3_svg_symbolCircle)(size.call(this, d, i));
8306 }
8307 symbol.type = function(x) {
8308 if (!arguments.length) return type;
8309 type = d3_functor(x);
8310 return symbol;
8311 };
8312 symbol.size = function(x) {
8313 if (!arguments.length) return size;
8314 size = d3_functor(x);
8315 return symbol;
8316 };
8317 return symbol;
8318 };
8319 function d3_svg_symbolSize() {
8320 return 64;
8321 }
8322 function d3_svg_symbolType() {
8323 return "circle";
8324 }
8325 function d3_svg_symbolCircle(size) {
8326 var r = Math.sqrt(size / π);
8327 return "M0," + r + "A" + r + "," + r + " 0 1,1 0," + -r + "A" + r + "," + r + " 0 1,1 0," + r + "Z";
8328 }
8329 var d3_svg_symbols = d3.map({
8330 circle: d3_svg_symbolCircle,
8331 cross: function(size) {
8332 var r = Math.sqrt(size / 5) / 2;
8333 return "M" + -3 * r + "," + -r + "H" + -r + "V" + -3 * r + "H" + r + "V" + -r + "H" + 3 * r + "V" + r + "H" + r + "V" + 3 * r + "H" + -r + "V" + r + "H" + -3 * r + "Z";
8334 },
8335 diamond: function(size) {
8336 var ry = Math.sqrt(size / (2 * d3_svg_symbolTan30)), rx = ry * d3_svg_symbolTan30;
8337 return "M0," + -ry + "L" + rx + ",0" + " 0," + ry + " " + -rx + ",0" + "Z";
8338 },
8339 square: function(size) {
8340 var r = Math.sqrt(size) / 2;
8341 return "M" + -r + "," + -r + "L" + r + "," + -r + " " + r + "," + r + " " + -r + "," + r + "Z";
8342 },
8343 "triangle-down": function(size) {
8344 var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2;
8345 return "M0," + ry + "L" + rx + "," + -ry + " " + -rx + "," + -ry + "Z";
8346 },
8347 "triangle-up": function(size) {
8348 var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2;
8349 return "M0," + -ry + "L" + rx + "," + ry + " " + -rx + "," + ry + "Z";
8350 }
8351 });
8352 d3.svg.symbolTypes = d3_svg_symbols.keys();
8353 var d3_svg_symbolSqrt3 = Math.sqrt(3), d3_svg_symbolTan30 = Math.tan(30 * d3_radians);
8354 function d3_transition(groups, id) {
8355 d3_subclass(groups, d3_transitionPrototype);
8356 groups.id = id;
8357 return groups;
8358 }
8359 var d3_transitionPrototype = [], d3_transitionId = 0, d3_transitionInheritId, d3_transitionInherit;
8360 d3_transitionPrototype.call = d3_selectionPrototype.call;
8361 d3_transitionPrototype.empty = d3_selectionPrototype.empty;
8362 d3_transitionPrototype.node = d3_selectionPrototype.node;
8363 d3_transitionPrototype.size = d3_selectionPrototype.size;
8364 d3.transition = function(selection) {
8365 return arguments.length ? d3_transitionInheritId ? selection.transition() : selection : d3_selectionRoot.transition();
8366 };
8367 d3.transition.prototype = d3_transitionPrototype;
8368 d3_transitionPrototype.select = function(selector) {
8369 var id = this.id, subgroups = [], subgroup, subnode, node;
8370 selector = d3_selection_selector(selector);
8371 for (var j = -1, m = this.length; ++j < m; ) {
8372 subgroups.push(subgroup = []);
8373 for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
8374 if ((node = group[i]) && (subnode = selector.call(node, node.__data__, i, j))) {
8375 if ("__data__" in node) subnode.__data__ = node.__data__;
8376 d3_transitionNode(subnode, i, id, node.__transition__[id]);
8377 subgroup.push(subnode);
8378 } else {
8379 subgroup.push(null);
8380 }
8381 }
8382 }
8383 return d3_transition(subgroups, id);
8384 };
8385 d3_transitionPrototype.selectAll = function(selector) {
8386 var id = this.id, subgroups = [], subgroup, subnodes, node, subnode, transition;
8387 selector = d3_selection_selectorAll(selector);
8388 for (var j = -1, m = this.length; ++j < m; ) {
8389 for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
8390 if (node = group[i]) {
8391 transition = node.__transition__[id];
8392 subnodes = selector.call(node, node.__data__, i, j);
8393 subgroups.push(subgroup = []);
8394 for (var k = -1, o = subnodes.length; ++k < o; ) {
8395 if (subnode = subnodes[k]) d3_transitionNode(subnode, k, id, transition);
8396 subgroup.push(subnode);
8397 }
8398 }
8399 }
8400 }
8401 return d3_transition(subgroups, id);
8402 };
8403 d3_transitionPrototype.filter = function(filter) {
8404 var subgroups = [], subgroup, group, node;
8405 if (typeof filter !== "function") filter = d3_selection_filter(filter);
8406 for (var j = 0, m = this.length; j < m; j++) {
8407 subgroups.push(subgroup = []);
8408 for (var group = this[j], i = 0, n = group.length; i < n; i++) {
8409 if ((node = group[i]) && filter.call(node, node.__data__, i, j)) {
8410 subgroup.push(node);
8411 }
8412 }
8413 }
8414 return d3_transition(subgroups, this.id);
8415 };
8416 d3_transitionPrototype.tween = function(name, tween) {
8417 var id = this.id;
8418 if (arguments.length < 2) return this.node().__transition__[id].tween.get(name);
8419 return d3_selection_each(this, tween == null ? function(node) {
8420 node.__transition__[id].tween.remove(name);
8421 } : function(node) {
8422 node.__transition__[id].tween.set(name, tween);
8423 });
8424 };
8425 function d3_transition_tween(groups, name, value, tween) {
8426 var id = groups.id;
8427 return d3_selection_each(groups, typeof value === "function" ? function(node, i, j) {
8428 node.__transition__[id].tween.set(name, tween(value.call(node, node.__data__, i, j)));
8429 } : (value = tween(value), function(node) {
8430 node.__transition__[id].tween.set(name, value);
8431 }));
8432 }
8433 d3_transitionPrototype.attr = function(nameNS, value) {
8434 if (arguments.length < 2) {
8435 for (value in nameNS) this.attr(value, nameNS[value]);
8436 return this;
8437 }
8438 var interpolate = nameNS == "transform" ? d3_interpolateTransform : d3_interpolate, name = d3.ns.qualify(nameNS);
8439 function attrNull() {
8440 this.removeAttribute(name);
8441 }
8442 function attrNullNS() {
8443 this.removeAttributeNS(name.space, name.local);
8444 }
8445 function attrTween(b) {
8446 return b == null ? attrNull : (b += "", function() {
8447 var a = this.getAttribute(name), i;
8448 return a !== b && (i = interpolate(a, b), function(t) {
8449 this.setAttribute(name, i(t));
8450 });
8451 });
8452 }
8453 function attrTweenNS(b) {
8454 return b == null ? attrNullNS : (b += "", function() {
8455 var a = this.getAttributeNS(name.space, name.local), i;
8456 return a !== b && (i = interpolate(a, b), function(t) {
8457 this.setAttributeNS(name.space, name.local, i(t));
8458 });
8459 });
8460 }
8461 return d3_transition_tween(this, "attr." + nameNS, value, name.local ? attrTweenNS : attrTween);
8462 };
8463 d3_transitionPrototype.attrTween = function(nameNS, tween) {
8464 var name = d3.ns.qualify(nameNS);
8465 function attrTween(d, i) {
8466 var f = tween.call(this, d, i, this.getAttribute(name));
8467 return f && function(t) {
8468 this.setAttribute(name, f(t));
8469 };
8470 }
8471 function attrTweenNS(d, i) {
8472 var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local));
8473 return f && function(t) {
8474 this.setAttributeNS(name.space, name.local, f(t));
8475 };
8476 }
8477 return this.tween("attr." + nameNS, name.local ? attrTweenNS : attrTween);
8478 };
8479 d3_transitionPrototype.style = function(name, value, priority) {
8480 var n = arguments.length;
8481 if (n < 3) {
8482 if (typeof name !== "string") {
8483 if (n < 2) value = "";
8484 for (priority in name) this.style(priority, name[priority], value);
8485 return this;
8486 }
8487 priority = "";
8488 }
8489 function styleNull() {
8490 this.style.removeProperty(name);
8491 }
8492 function styleString(b) {
8493 return b == null ? styleNull : (b += "", function() {
8494 var a = d3_window.getComputedStyle(this, null).getPropertyValue(name), i;
8495 return a !== b && (i = d3_interpolate(a, b), function(t) {
8496 this.style.setProperty(name, i(t), priority);
8497 });
8498 });
8499 }
8500 return d3_transition_tween(this, "style." + name, value, styleString);
8501 };
8502 d3_transitionPrototype.styleTween = function(name, tween, priority) {
8503 if (arguments.length < 3) priority = "";
8504 function styleTween(d, i) {
8505 var f = tween.call(this, d, i, d3_window.getComputedStyle(this, null).getPropertyValue(name));
8506 return f && function(t) {
8507 this.style.setProperty(name, f(t), priority);
8508 };
8509 }
8510 return this.tween("style." + name, styleTween);
8511 };
8512 d3_transitionPrototype.text = function(value) {
8513 return d3_transition_tween(this, "text", value, d3_transition_text);
8514 };
8515 function d3_transition_text(b) {
8516 if (b == null) b = "";
8517 return function() {
8518 this.textContent = b;
8519 };
8520 }
8521 d3_transitionPrototype.remove = function() {
8522 return this.each("end.transition", function() {
8523 var p;
8524 if (this.__transition__.count < 2 && (p = this.parentNode)) p.removeChild(this);
8525 });
8526 };
8527 d3_transitionPrototype.ease = function(value) {
8528 var id = this.id;
8529 if (arguments.length < 1) return this.node().__transition__[id].ease;
8530 if (typeof value !== "function") value = d3.ease.apply(d3, arguments);
8531 return d3_selection_each(this, function(node) {
8532 node.__transition__[id].ease = value;
8533 });
8534 };
8535 d3_transitionPrototype.delay = function(value) {
8536 var id = this.id;
8537 if (arguments.length < 1) return this.node().__transition__[id].delay;
8538 return d3_selection_each(this, typeof value === "function" ? function(node, i, j) {
8539 node.__transition__[id].delay = +value.call(node, node.__data__, i, j);
8540 } : (value = +value, function(node) {
8541 node.__transition__[id].delay = value;
8542 }));
8543 };
8544 d3_transitionPrototype.duration = function(value) {
8545 var id = this.id;
8546 if (arguments.length < 1) return this.node().__transition__[id].duration;
8547 return d3_selection_each(this, typeof value === "function" ? function(node, i, j) {
8548 node.__transition__[id].duration = Math.max(1, value.call(node, node.__data__, i, j));
8549 } : (value = Math.max(1, value), function(node) {
8550 node.__transition__[id].duration = value;
8551 }));
8552 };
8553 d3_transitionPrototype.each = function(type, listener) {
8554 var id = this.id;
8555 if (arguments.length < 2) {
8556 var inherit = d3_transitionInherit, inheritId = d3_transitionInheritId;
8557 d3_transitionInheritId = id;
8558 d3_selection_each(this, function(node, i, j) {
8559 d3_transitionInherit = node.__transition__[id];
8560 type.call(node, node.__data__, i, j);
8561 });
8562 d3_transitionInherit = inherit;
8563 d3_transitionInheritId = inheritId;
8564 } else {
8565 d3_selection_each(this, function(node) {
8566 var transition = node.__transition__[id];
8567 (transition.event || (transition.event = d3.dispatch("start", "end"))).on(type, listener);
8568 });
8569 }
8570 return this;
8571 };
8572 d3_transitionPrototype.transition = function() {
8573 var id0 = this.id, id1 = ++d3_transitionId, subgroups = [], subgroup, group, node, transition;
8574 for (var j = 0, m = this.length; j < m; j++) {
8575 subgroups.push(subgroup = []);
8576 for (var group = this[j], i = 0, n = group.length; i < n; i++) {
8577 if (node = group[i]) {
8578 transition = Object.create(node.__transition__[id0]);
8579 transition.delay += transition.duration;
8580 d3_transitionNode(node, i, id1, transition);
8581 }
8582 subgroup.push(node);
8583 }
8584 }
8585 return d3_transition(subgroups, id1);
8586 };
8587 function d3_transitionNode(node, i, id, inherit) {
8588 var lock = node.__transition__ || (node.__transition__ = {
8589 active: 0,
8590 count: 0
8591 }), transition = lock[id];
8592 if (!transition) {
8593 var time = inherit.time;
8594 transition = lock[id] = {
8595 tween: new d3_Map(),
8596 time: time,
8597 ease: inherit.ease,
8598 delay: inherit.delay,
8599 duration: inherit.duration
8600 };
8601 ++lock.count;
8602 d3.timer(function(elapsed) {
8603 var d = node.__data__, ease = transition.ease, delay = transition.delay, duration = transition.duration, timer = d3_timer_active, tweened = [];
8604 timer.t = delay + time;
8605 if (delay <= elapsed) return start(elapsed - delay);
8606 timer.c = start;
8607 function start(elapsed) {
8608 if (lock.active > id) return stop();
8609 lock.active = id;
8610 transition.event && transition.event.start.call(node, d, i);
8611 transition.tween.forEach(function(key, value) {
8612 if (value = value.call(node, d, i)) {
8613 tweened.push(value);
8614 }
8615 });
8616 d3.timer(function() {
8617 timer.c = tick(elapsed || 1) ? d3_true : tick;
8618 return 1;
8619 }, 0, time);
8620 }
8621 function tick(elapsed) {
8622 if (lock.active !== id) return stop();
8623 var t = elapsed / duration, e = ease(t), n = tweened.length;
8624 while (n > 0) {
8625 tweened[--n].call(node, e);
8626 }
8627 if (t >= 1) {
8628 transition.event && transition.event.end.call(node, d, i);
8629 return stop();
8630 }
8631 }
8632 function stop() {
8633 if (--lock.count) delete lock[id]; else delete node.__transition__;
8634 return 1;
8635 }
8636 }, 0, time);
8637 }
8638 }
8639 d3.svg.axis = function() {
8640 var scale = d3.scale.linear(), orient = d3_svg_axisDefaultOrient, innerTickSize = 6, outerTickSize = 6, tickPadding = 3, tickArguments_ = [ 10 ], tickValues = null, tickFormat_;
8641 function axis(g) {
8642 g.each(function() {
8643 var g = d3.select(this);
8644 var scale0 = this.__chart__ || scale, scale1 = this.__chart__ = scale.copy();
8645 var ticks = tickValues == null ? scale1.ticks ? scale1.ticks.apply(scale1, tickArguments_) : scale1.domain() : tickValues, tickFormat = tickFormat_ == null ? scale1.tickFormat ? scale1.tickFormat.apply(scale1, tickArguments_) : d3_identity : tickFormat_, tick = g.selectAll(".tick").data(ticks, scale1), tickEnter = tick.enter().insert("g", ".domain").attr("class", "tick").style("opacity", ε), tickExit = d3.transition(tick.exit()).style("opacity", ε).remove(), tickUpdate = d3.transition(tick.order()).style("opacity", 1), tickSpacing = Math.max(innerTickSize, 0) + tickPadding, tickTransform;
8646 var range = d3_scaleRange(scale1), path = g.selectAll(".domain").data([ 0 ]), pathUpdate = (path.enter().append("path").attr("class", "domain"),
8647 d3.transition(path));
8648 tickEnter.append("line");
8649 tickEnter.append("text");
8650 var lineEnter = tickEnter.select("line"), lineUpdate = tickUpdate.select("line"), text = tick.select("text").text(tickFormat), textEnter = tickEnter.select("text"), textUpdate = tickUpdate.select("text"), sign = orient === "top" || orient === "left" ? -1 : 1, x1, x2, y1, y2;
8651 if (orient === "bottom" || orient === "top") {
8652 tickTransform = d3_svg_axisX, x1 = "x", y1 = "y", x2 = "x2", y2 = "y2";
8653 text.attr("dy", sign < 0 ? "0em" : ".71em").style("text-anchor", "middle");
8654 pathUpdate.attr("d", "M" + range[0] + "," + sign * outerTickSize + "V0H" + range[1] + "V" + sign * outerTickSize);
8655 } else {
8656 tickTransform = d3_svg_axisY, x1 = "y", y1 = "x", x2 = "y2", y2 = "x2";
8657 text.attr("dy", ".32em").style("text-anchor", sign < 0 ? "end" : "start");
8658 pathUpdate.attr("d", "M" + sign * outerTickSize + "," + range[0] + "H0V" + range[1] + "H" + sign * outerTickSize);
8659 }
8660 lineEnter.attr(y2, sign * innerTickSize);
8661 textEnter.attr(y1, sign * tickSpacing);
8662 lineUpdate.attr(x2, 0).attr(y2, sign * innerTickSize);
8663 textUpdate.attr(x1, 0).attr(y1, sign * tickSpacing);
8664 if (scale1.rangeBand) {
8665 var x = scale1, dx = x.rangeBand() / 2;
8666 scale0 = scale1 = function(d) {
8667 return x(d) + dx;
8668 };
8669 } else if (scale0.rangeBand) {
8670 scale0 = scale1;
8671 } else {
8672 tickExit.call(tickTransform, scale1, scale0);
8673 }
8674 tickEnter.call(tickTransform, scale0, scale1);
8675 tickUpdate.call(tickTransform, scale1, scale1);
8676 });
8677 }
8678 axis.scale = function(x) {
8679 if (!arguments.length) return scale;
8680 scale = x;
8681 return axis;
8682 };
8683 axis.orient = function(x) {
8684 if (!arguments.length) return orient;
8685 orient = x in d3_svg_axisOrients ? x + "" : d3_svg_axisDefaultOrient;
8686 return axis;
8687 };
8688 axis.ticks = function() {
8689 if (!arguments.length) return tickArguments_;
8690 tickArguments_ = arguments;
8691 return axis;
8692 };
8693 axis.tickValues = function(x) {
8694 if (!arguments.length) return tickValues;
8695 tickValues = x;
8696 return axis;
8697 };
8698 axis.tickFormat = function(x) {
8699 if (!arguments.length) return tickFormat_;
8700 tickFormat_ = x;
8701 return axis;
8702 };
8703 axis.tickSize = function(x) {
8704 var n = arguments.length;
8705 if (!n) return innerTickSize;
8706 innerTickSize = +x;
8707 outerTickSize = +arguments[n - 1];
8708 return axis;
8709 };
8710 axis.innerTickSize = function(x) {
8711 if (!arguments.length) return innerTickSize;
8712 innerTickSize = +x;
8713 return axis;
8714 };
8715 axis.outerTickSize = function(x) {
8716 if (!arguments.length) return outerTickSize;
8717 outerTickSize = +x;
8718 return axis;
8719 };
8720 axis.tickPadding = function(x) {
8721 if (!arguments.length) return tickPadding;
8722 tickPadding = +x;
8723 return axis;
8724 };
8725 axis.tickSubdivide = function() {
8726 return arguments.length && axis;
8727 };
8728 return axis;
8729 };
8730 var d3_svg_axisDefaultOrient = "bottom", d3_svg_axisOrients = {
8731 top: 1,
8732 right: 1,
8733 bottom: 1,
8734 left: 1
8735 };
8736 function d3_svg_axisX(selection, x0, x1) {
8737 selection.attr("transform", function(d) {
8738 var v0 = x0(d);
8739 return "translate(" + (isFinite(v0) ? v0 : x1(d)) + ",0)";
8740 });
8741 }
8742 function d3_svg_axisY(selection, y0, y1) {
8743 selection.attr("transform", function(d) {
8744 var v0 = y0(d);
8745 return "translate(0," + (isFinite(v0) ? v0 : y1(d)) + ")";
8746 });
8747 }
8748 d3.svg.brush = function() {
8749 var event = d3_eventDispatch(brush, "brushstart", "brush", "brushend"), x = null, y = null, xExtent = [ 0, 0 ], yExtent = [ 0, 0 ], xExtentDomain, yExtentDomain, xClamp = true, yClamp = true, resizes = d3_svg_brushResizes[0];
8750 function brush(g) {
8751 g.each(function() {
8752 var g = d3.select(this).style("pointer-events", "all").style("-webkit-tap-highlight-color", "rgba(0,0,0,0)").on("mousedown.brush", brushstart).on("touchstart.brush", brushstart);
8753 var background = g.selectAll(".background").data([ 0 ]);
8754 background.enter().append("rect").attr("class", "background").style("visibility", "hidden").style("cursor", "crosshair");
8755 g.selectAll(".extent").data([ 0 ]).enter().append("rect").attr("class", "extent").style("cursor", "move");
8756 var resize = g.selectAll(".resize").data(resizes, d3_identity);
8757 resize.exit().remove();
8758 resize.enter().append("g").attr("class", function(d) {
8759 return "resize " + d;
8760 }).style("cursor", function(d) {
8761 return d3_svg_brushCursor[d];
8762 }).append("rect").attr("x", function(d) {
8763 return /[ew]$/.test(d) ? -3 : null;
8764 }).attr("y", function(d) {
8765 return /^[ns]/.test(d) ? -3 : null;
8766 }).attr("width", 6).attr("height", 6).style("visibility", "hidden");
8767 resize.style("display", brush.empty() ? "none" : null);
8768 var gUpdate = d3.transition(g), backgroundUpdate = d3.transition(background), range;
8769 if (x) {
8770 range = d3_scaleRange(x);
8771 backgroundUpdate.attr("x", range[0]).attr("width", range[1] - range[0]);
8772 redrawX(gUpdate);
8773 }
8774 if (y) {
8775 range = d3_scaleRange(y);
8776 backgroundUpdate.attr("y", range[0]).attr("height", range[1] - range[0]);
8777 redrawY(gUpdate);
8778 }
8779 redraw(gUpdate);
8780 });
8781 }
8782 brush.event = function(g) {
8783 g.each(function() {
8784 var event_ = event.of(this, arguments), extent1 = {
8785 x: xExtent,
8786 y: yExtent,
8787 i: xExtentDomain,
8788 j: yExtentDomain
8789 }, extent0 = this.__chart__ || extent1;
8790 this.__chart__ = extent1;
8791 if (d3_transitionInheritId) {
8792 d3.select(this).transition().each("start.brush", function() {
8793 xExtentDomain = extent0.i;
8794 yExtentDomain = extent0.j;
8795 xExtent = extent0.x;
8796 yExtent = extent0.y;
8797 event_({
8798 type: "brushstart"
8799 });
8800 }).tween("brush:brush", function() {
8801 var xi = d3_interpolateArray(xExtent, extent1.x), yi = d3_interpolateArray(yExtent, extent1.y);
8802 xExtentDomain = yExtentDomain = null;
8803 return function(t) {
8804 xExtent = extent1.x = xi(t);
8805 yExtent = extent1.y = yi(t);
8806 event_({
8807 type: "brush",
8808 mode: "resize"
8809 });
8810 };
8811 }).each("end.brush", function() {
8812 xExtentDomain = extent1.i;
8813 yExtentDomain = extent1.j;
8814 event_({
8815 type: "brush",
8816 mode: "resize"
8817 });
8818 event_({
8819 type: "brushend"
8820 });
8821 });
8822 } else {
8823 event_({
8824 type: "brushstart"
8825 });
8826 event_({
8827 type: "brush",
8828 mode: "resize"
8829 });
8830 event_({
8831 type: "brushend"
8832 });
8833 }
8834 });
8835 };
8836 function redraw(g) {
8837 g.selectAll(".resize").attr("transform", function(d) {
8838 return "translate(" + xExtent[+/e$/.test(d)] + "," + yExtent[+/^s/.test(d)] + ")";
8839 });
8840 }
8841 function redrawX(g) {
8842 g.select(".extent").attr("x", xExtent[0]);
8843 g.selectAll(".extent,.n>rect,.s>rect").attr("width", xExtent[1] - xExtent[0]);
8844 }
8845 function redrawY(g) {
8846 g.select(".extent").attr("y", yExtent[0]);
8847 g.selectAll(".extent,.e>rect,.w>rect").attr("height", yExtent[1] - yExtent[0]);
8848 }
8849 function brushstart() {
8850 var target = this, eventTarget = d3.select(d3.event.target), event_ = event.of(target, arguments), g = d3.select(target), resizing = eventTarget.datum(), resizingX = !/^(n|s)$/.test(resizing) && x, resizingY = !/^(e|w)$/.test(resizing) && y, dragging = eventTarget.classed("extent"), dragRestore = d3_event_dragSuppress(), center, origin = d3.mouse(target), offset;
8851 var w = d3.select(d3_window).on("keydown.brush", keydown).on("keyup.brush", keyup);
8852 if (d3.event.changedTouches) {
8853 w.on("touchmove.brush", brushmove).on("touchend.brush", brushend);
8854 } else {
8855 w.on("mousemove.brush", brushmove).on("mouseup.brush", brushend);
8856 }
8857 g.interrupt().selectAll("*").interrupt();
8858 if (dragging) {
8859 origin[0] = xExtent[0] - origin[0];
8860 origin[1] = yExtent[0] - origin[1];
8861 } else if (resizing) {
8862 var ex = +/w$/.test(resizing), ey = +/^n/.test(resizing);
8863 offset = [ xExtent[1 - ex] - origin[0], yExtent[1 - ey] - origin[1] ];
8864 origin[0] = xExtent[ex];
8865 origin[1] = yExtent[ey];
8866 } else if (d3.event.altKey) center = origin.slice();
8867 g.style("pointer-events", "none").selectAll(".resize").style("display", null);
8868 d3.select("body").style("cursor", eventTarget.style("cursor"));
8869 event_({
8870 type: "brushstart"
8871 });
8872 brushmove();
8873 function keydown() {
8874 if (d3.event.keyCode == 32) {
8875 if (!dragging) {
8876 center = null;
8877 origin[0] -= xExtent[1];
8878 origin[1] -= yExtent[1];
8879 dragging = 2;
8880 }
8881 d3_eventPreventDefault();
8882 }
8883 }
8884 function keyup() {
8885 if (d3.event.keyCode == 32 && dragging == 2) {
8886 origin[0] += xExtent[1];
8887 origin[1] += yExtent[1];
8888 dragging = 0;
8889 d3_eventPreventDefault();
8890 }
8891 }
8892 function brushmove() {
8893 var point = d3.mouse(target), moved = false;
8894 if (offset) {
8895 point[0] += offset[0];
8896 point[1] += offset[1];
8897 }
8898 if (!dragging) {
8899 if (d3.event.altKey) {
8900 if (!center) center = [ (xExtent[0] + xExtent[1]) / 2, (yExtent[0] + yExtent[1]) / 2 ];
8901 origin[0] = xExtent[+(point[0] < center[0])];
8902 origin[1] = yExtent[+(point[1] < center[1])];
8903 } else center = null;
8904 }
8905 if (resizingX && move1(point, x, 0)) {
8906 redrawX(g);
8907 moved = true;
8908 }
8909 if (resizingY && move1(point, y, 1)) {
8910 redrawY(g);
8911 moved = true;
8912 }
8913 if (moved) {
8914 redraw(g);
8915 event_({
8916 type: "brush",
8917 mode: dragging ? "move" : "resize"
8918 });
8919 }
8920 }
8921 function move1(point, scale, i) {
8922 var range = d3_scaleRange(scale), r0 = range[0], r1 = range[1], position = origin[i], extent = i ? yExtent : xExtent, size = extent[1] - extent[0], min, max;
8923 if (dragging) {
8924 r0 -= position;
8925 r1 -= size + position;
8926 }
8927 min = (i ? yClamp : xClamp) ? Math.max(r0, Math.min(r1, point[i])) : point[i];
8928 if (dragging) {
8929 max = (min += position) + size;
8930 } else {
8931 if (center) position = Math.max(r0, Math.min(r1, 2 * center[i] - min));
8932 if (position < min) {
8933 max = min;
8934 min = position;
8935 } else {
8936 max = position;
8937 }
8938 }
8939 if (extent[0] != min || extent[1] != max) {
8940 if (i) yExtentDomain = null; else xExtentDomain = null;
8941 extent[0] = min;
8942 extent[1] = max;
8943 return true;
8944 }
8945 }
8946 function brushend() {
8947 brushmove();
8948 g.style("pointer-events", "all").selectAll(".resize").style("display", brush.empty() ? "none" : null);
8949 d3.select("body").style("cursor", null);
8950 w.on("mousemove.brush", null).on("mouseup.brush", null).on("touchmove.brush", null).on("touchend.brush", null).on("keydown.brush", null).on("keyup.brush", null);
8951 dragRestore();
8952 event_({
8953 type: "brushend"
8954 });
8955 }
8956 }
8957 brush.x = function(z) {
8958 if (!arguments.length) return x;
8959 x = z;
8960 resizes = d3_svg_brushResizes[!x << 1 | !y];
8961 return brush;
8962 };
8963 brush.y = function(z) {
8964 if (!arguments.length) return y;
8965 y = z;
8966 resizes = d3_svg_brushResizes[!x << 1 | !y];
8967 return brush;
8968 };
8969 brush.clamp = function(z) {
8970 if (!arguments.length) return x && y ? [ xClamp, yClamp ] : x ? xClamp : y ? yClamp : null;
8971 if (x && y) xClamp = !!z[0], yClamp = !!z[1]; else if (x) xClamp = !!z; else if (y) yClamp = !!z;
8972 return brush;
8973 };
8974 brush.extent = function(z) {
8975 var x0, x1, y0, y1, t;
8976 if (!arguments.length) {
8977 if (x) {
8978 if (xExtentDomain) {
8979 x0 = xExtentDomain[0], x1 = xExtentDomain[1];
8980 } else {
8981 x0 = xExtent[0], x1 = xExtent[1];
8982 if (x.invert) x0 = x.invert(x0), x1 = x.invert(x1);
8983 if (x1 < x0) t = x0, x0 = x1, x1 = t;
8984 }
8985 }
8986 if (y) {
8987 if (yExtentDomain) {
8988 y0 = yExtentDomain[0], y1 = yExtentDomain[1];
8989 } else {
8990 y0 = yExtent[0], y1 = yExtent[1];
8991 if (y.invert) y0 = y.invert(y0), y1 = y.invert(y1);
8992 if (y1 < y0) t = y0, y0 = y1, y1 = t;
8993 }
8994 }
8995 return x && y ? [ [ x0, y0 ], [ x1, y1 ] ] : x ? [ x0, x1 ] : y && [ y0, y1 ];
8996 }
8997 if (x) {
8998 x0 = z[0], x1 = z[1];
8999 if (y) x0 = x0[0], x1 = x1[0];
9000 xExtentDomain = [ x0, x1 ];
9001 if (x.invert) x0 = x(x0), x1 = x(x1);
9002 if (x1 < x0) t = x0, x0 = x1, x1 = t;
9003 if (x0 != xExtent[0] || x1 != xExtent[1]) xExtent = [ x0, x1 ];
9004 }
9005 if (y) {
9006 y0 = z[0], y1 = z[1];
9007 if (x) y0 = y0[1], y1 = y1[1];
9008 yExtentDomain = [ y0, y1 ];
9009 if (y.invert) y0 = y(y0), y1 = y(y1);
9010 if (y1 < y0) t = y0, y0 = y1, y1 = t;
9011 if (y0 != yExtent[0] || y1 != yExtent[1]) yExtent = [ y0, y1 ];
9012 }
9013 return brush;
9014 };
9015 brush.clear = function() {
9016 if (!brush.empty()) {
9017 xExtent = [ 0, 0 ], yExtent = [ 0, 0 ];
9018 xExtentDomain = yExtentDomain = null;
9019 }
9020 return brush;
9021 };
9022 brush.empty = function() {
9023 return !!x && xExtent[0] == xExtent[1] || !!y && yExtent[0] == yExtent[1];
9024 };
9025 return d3.rebind(brush, event, "on");
9026 };
9027 var d3_svg_brushCursor = {
9028 n: "ns-resize",
9029 e: "ew-resize",
9030 s: "ns-resize",
9031 w: "ew-resize",
9032 nw: "nwse-resize",
9033 ne: "nesw-resize",
9034 se: "nwse-resize",
9035 sw: "nesw-resize"
9036 };
9037 var d3_svg_brushResizes = [ [ "n", "e", "s", "w", "nw", "ne", "se", "sw" ], [ "e", "w" ], [ "n", "s" ], [] ];
9038 var d3_time_format = d3_time.format = d3_locale_enUS.timeFormat;
9039 var d3_time_formatUtc = d3_time_format.utc;
9040 var d3_time_formatIso = d3_time_formatUtc("%Y-%m-%dT%H:%M:%S.%LZ");
9041 d3_time_format.iso = Date.prototype.toISOString && +new Date("2000-01-01T00:00:00.000Z") ? d3_time_formatIsoNative : d3_time_formatIso;
9042 function d3_time_formatIsoNative(date) {
9043 return date.toISOString();
9044 }
9045 d3_time_formatIsoNative.parse = function(string) {
9046 var date = new Date(string);
9047 return isNaN(date) ? null : date;
9048 };
9049 d3_time_formatIsoNative.toString = d3_time_formatIso.toString;
9050 d3_time.second = d3_time_interval(function(date) {
9051 return new d3_date(Math.floor(date / 1e3) * 1e3);
9052 }, function(date, offset) {
9053 date.setTime(date.getTime() + Math.floor(offset) * 1e3);
9054 }, function(date) {
9055 return date.getSeconds();
9056 });
9057 d3_time.seconds = d3_time.second.range;
9058 d3_time.seconds.utc = d3_time.second.utc.range;
9059 d3_time.minute = d3_time_interval(function(date) {
9060 return new d3_date(Math.floor(date / 6e4) * 6e4);
9061 }, function(date, offset) {
9062 date.setTime(date.getTime() + Math.floor(offset) * 6e4);
9063 }, function(date) {
9064 return date.getMinutes();
9065 });
9066 d3_time.minutes = d3_time.minute.range;
9067 d3_time.minutes.utc = d3_time.minute.utc.range;
9068 d3_time.hour = d3_time_interval(function(date) {
9069 var timezone = date.getTimezoneOffset() / 60;
9070 return new d3_date((Math.floor(date / 36e5 - timezone) + timezone) * 36e5);
9071 }, function(date, offset) {
9072 date.setTime(date.getTime() + Math.floor(offset) * 36e5);
9073 }, function(date) {
9074 return date.getHours();
9075 });
9076 d3_time.hours = d3_time.hour.range;
9077 d3_time.hours.utc = d3_time.hour.utc.range;
9078 d3_time.month = d3_time_interval(function(date) {
9079 date = d3_time.day(date);
9080 date.setDate(1);
9081 return date;
9082 }, function(date, offset) {
9083 date.setMonth(date.getMonth() + offset);
9084 }, function(date) {
9085 return date.getMonth();
9086 });
9087 d3_time.months = d3_time.month.range;
9088 d3_time.months.utc = d3_time.month.utc.range;
9089 function d3_time_scale(linear, methods, format) {
9090 function scale(x) {
9091 return linear(x);
9092 }
9093 scale.invert = function(x) {
9094 return d3_time_scaleDate(linear.invert(x));
9095 };
9096 scale.domain = function(x) {
9097 if (!arguments.length) return linear.domain().map(d3_time_scaleDate);
9098 linear.domain(x);
9099 return scale;
9100 };
9101 function tickMethod(extent, count) {
9102 var span = extent[1] - extent[0], target = span / count, i = d3.bisect(d3_time_scaleSteps, target);
9103 return i == d3_time_scaleSteps.length ? [ methods.year, d3_scale_linearTickRange(extent.map(function(d) {
9104 return d / 31536e6;
9105 }), count)[2] ] : !i ? [ d3_time_scaleMilliseconds, d3_scale_linearTickRange(extent, count)[2] ] : methods[target / d3_time_scaleSteps[i - 1] < d3_time_scaleSteps[i] / target ? i - 1 : i];
9106 }
9107 scale.nice = function(interval, skip) {
9108 var domain = scale.domain(), extent = d3_scaleExtent(domain), method = interval == null ? tickMethod(extent, 10) : typeof interval === "number" && tickMethod(extent, interval);
9109 if (method) interval = method[0], skip = method[1];
9110 function skipped(date) {
9111 return !isNaN(date) && !interval.range(date, d3_time_scaleDate(+date + 1), skip).length;
9112 }
9113 return scale.domain(d3_scale_nice(domain, skip > 1 ? {
9114 floor: function(date) {
9115 while (skipped(date = interval.floor(date))) date = d3_time_scaleDate(date - 1);
9116 return date;
9117 },
9118 ceil: function(date) {
9119 while (skipped(date = interval.ceil(date))) date = d3_time_scaleDate(+date + 1);
9120 return date;
9121 }
9122 } : interval));
9123 };
9124 scale.ticks = function(interval, skip) {
9125 var extent = d3_scaleExtent(scale.domain()), method = interval == null ? tickMethod(extent, 10) : typeof interval === "number" ? tickMethod(extent, interval) : !interval.range && [ {
9126 range: interval
9127 }, skip ];
9128 if (method) interval = method[0], skip = method[1];
9129 return interval.range(extent[0], d3_time_scaleDate(+extent[1] + 1), skip < 1 ? 1 : skip);
9130 };
9131 scale.tickFormat = function() {
9132 return format;
9133 };
9134 scale.copy = function() {
9135 return d3_time_scale(linear.copy(), methods, format);
9136 };
9137 return d3_scale_linearRebind(scale, linear);
9138 }
9139 function d3_time_scaleDate(t) {
9140 return new Date(t);
9141 }
9142 var d3_time_scaleSteps = [ 1e3, 5e3, 15e3, 3e4, 6e4, 3e5, 9e5, 18e5, 36e5, 108e5, 216e5, 432e5, 864e5, 1728e5, 6048e5, 2592e6, 7776e6, 31536e6 ];
9143 var d3_time_scaleLocalMethods = [ [ d3_time.second, 1 ], [ d3_time.second, 5 ], [ d3_time.second, 15 ], [ d3_time.second, 30 ], [ d3_time.minute, 1 ], [ d3_time.minute, 5 ], [ d3_time.minute, 15 ], [ d3_time.minute, 30 ], [ d3_time.hour, 1 ], [ d3_time.hour, 3 ], [ d3_time.hour, 6 ], [ d3_time.hour, 12 ], [ d3_time.day, 1 ], [ d3_time.day, 2 ], [ d3_time.week, 1 ], [ d3_time.month, 1 ], [ d3_time.month, 3 ], [ d3_time.year, 1 ] ];
9144 var d3_time_scaleLocalFormat = d3_time_format.multi([ [ ".%L", function(d) {
9145 return d.getMilliseconds();
9146 } ], [ ":%S", function(d) {
9147 return d.getSeconds();
9148 } ], [ "%I:%M", function(d) {
9149 return d.getMinutes();
9150 } ], [ "%I %p", function(d) {
9151 return d.getHours();
9152 } ], [ "%a %d", function(d) {
9153 return d.getDay() && d.getDate() != 1;
9154 } ], [ "%b %d", function(d) {
9155 return d.getDate() != 1;
9156 } ], [ "%B", function(d) {
9157 return d.getMonth();
9158 } ], [ "%Y", d3_true ] ]);
9159 var d3_time_scaleMilliseconds = {
9160 range: function(start, stop, step) {
9161 return d3.range(Math.ceil(start / step) * step, +stop, step).map(d3_time_scaleDate);
9162 },
9163 floor: d3_identity,
9164 ceil: d3_identity
9165 };
9166 d3_time_scaleLocalMethods.year = d3_time.year;
9167 d3_time.scale = function() {
9168 return d3_time_scale(d3.scale.linear(), d3_time_scaleLocalMethods, d3_time_scaleLocalFormat);
9169 };
9170 var d3_time_scaleUtcMethods = d3_time_scaleLocalMethods.map(function(m) {
9171 return [ m[0].utc, m[1] ];
9172 });
9173 var d3_time_scaleUtcFormat = d3_time_formatUtc.multi([ [ ".%L", function(d) {
9174 return d.getUTCMilliseconds();
9175 } ], [ ":%S", function(d) {
9176 return d.getUTCSeconds();
9177 } ], [ "%I:%M", function(d) {
9178 return d.getUTCMinutes();
9179 } ], [ "%I %p", function(d) {
9180 return d.getUTCHours();
9181 } ], [ "%a %d", function(d) {
9182 return d.getUTCDay() && d.getUTCDate() != 1;
9183 } ], [ "%b %d", function(d) {
9184 return d.getUTCDate() != 1;
9185 } ], [ "%B", function(d) {
9186 return d.getUTCMonth();
9187 } ], [ "%Y", d3_true ] ]);
9188 d3_time_scaleUtcMethods.year = d3_time.year.utc;
9189 d3_time.scale.utc = function() {
9190 return d3_time_scale(d3.scale.linear(), d3_time_scaleUtcMethods, d3_time_scaleUtcFormat);
9191 };
9192 d3.text = d3_xhrType(function(request) {
9193 return request.responseText;
9194 });
9195 d3.json = function(url, callback) {
9196 return d3_xhr(url, "application/json", d3_json, callback);
9197 };
9198 function d3_json(request) {
9199 return JSON.parse(request.responseText);
9200 }
9201 d3.html = function(url, callback) {
9202 return d3_xhr(url, "text/html", d3_html, callback);
9203 };
9204 function d3_html(request) {
9205 var range = d3_document.createRange();
9206 range.selectNode(d3_document.body);
9207 return range.createContextualFragment(request.responseText);
9208 }
9209 d3.xml = d3_xhrType(function(request) {
9210 return request.responseXML;
9211 });
9212 if (typeof define === "function" && define.amd) define(d3); else if (typeof module === "object" && module.exports) module.exports = d3;
9213 this.d3 = d3;
9214 }();
0 /*
1 Highcharts JS v3.0.6 (2013-10-04)
2
3 (c) 2009-2013 Torstein Hønsi
4
5 License: www.highcharts.com/license
6 */
7 (function(){function r(a,b){var c;a||(a={});for(c in b)a[c]=b[c];return a}function x(){var a,b=arguments.length,c={},d=function(a,b){var c,h;typeof a!=="object"&&(a={});for(h in b)b.hasOwnProperty(h)&&(c=b[h],a[h]=c&&typeof c==="object"&&Object.prototype.toString.call(c)!=="[object Array]"&&typeof c.nodeType!=="number"?d(a[h]||{},c):b[h]);return a};for(a=0;a<b;a++)c=d(c,arguments[a]);return c}function C(a,b){return parseInt(a,b||10)}function ea(a){return typeof a==="string"}function T(a){return typeof a===
8 "object"}function Ia(a){return Object.prototype.toString.call(a)==="[object Array]"}function sa(a){return typeof a==="number"}function na(a){return R.log(a)/R.LN10}function fa(a){return R.pow(10,a)}function ga(a,b){for(var c=a.length;c--;)if(a[c]===b){a.splice(c,1);break}}function u(a){return a!==w&&a!==null}function v(a,b,c){var d,e;if(ea(b))u(c)?a.setAttribute(b,c):a&&a.getAttribute&&(e=a.getAttribute(b));else if(u(b)&&T(b))for(d in b)a.setAttribute(d,b[d]);return e}function ja(a){return Ia(a)?
9 a:[a]}function o(){var a=arguments,b,c,d=a.length;for(b=0;b<d;b++)if(c=a[b],typeof c!=="undefined"&&c!==null)return c}function K(a,b){if(ta&&b&&b.opacity!==w)b.filter="alpha(opacity="+b.opacity*100+")";r(a.style,b)}function U(a,b,c,d,e){a=y.createElement(a);b&&r(a,b);e&&K(a,{padding:0,border:S,margin:0});c&&K(a,c);d&&d.appendChild(a);return a}function ha(a,b){var c=function(){};c.prototype=new a;r(c.prototype,b);return c}function Aa(a,b,c,d){var e=M.lang,a=+a||0,f=b===-1?(a.toString().split(".")[1]||
10 "").length:isNaN(b=N(b))?2:b,b=c===void 0?e.decimalPoint:c,d=d===void 0?e.thousandsSep:d,e=a<0?"-":"",c=String(C(a=N(a).toFixed(f))),g=c.length>3?c.length%3:0;return e+(g?c.substr(0,g)+d:"")+c.substr(g).replace(/(\d{3})(?=\d)/g,"$1"+d)+(f?b+N(a-c).toFixed(f).slice(2):"")}function Ba(a,b){return Array((b||2)+1-String(a).length).join(0)+a}function mb(a,b,c){var d=a[b];a[b]=function(){var a=Array.prototype.slice.call(arguments);a.unshift(d);return c.apply(this,a)}}function Ca(a,b){for(var c="{",d=!1,
11 e,f,g,h,i,j=[];(c=a.indexOf(c))!==-1;){e=a.slice(0,c);if(d){f=e.split(":");g=f.shift().split(".");i=g.length;e=b;for(h=0;h<i;h++)e=e[g[h]];if(f.length)f=f.join(":"),g=/\.([0-9])/,h=M.lang,i=void 0,/f$/.test(f)?(i=(i=f.match(g))?i[1]:-1,e=Aa(e,i,h.decimalPoint,f.indexOf(",")>-1?h.thousandsSep:"")):e=Xa(f,e)}j.push(e);a=a.slice(c+1);c=(d=!d)?"}":"{"}j.push(a);return j.join("")}function nb(a){return R.pow(10,P(R.log(a)/R.LN10))}function ob(a,b,c,d){var e,c=o(c,1);e=a/c;b||(b=[1,2,2.5,5,10],d&&d.allowDecimals===
12 !1&&(c===1?b=[1,2,5,10]:c<=0.1&&(b=[1/c])));for(d=0;d<b.length;d++)if(a=b[d],e<=(b[d]+(b[d+1]||b[d]))/2)break;a*=c;return a}function Cb(a,b){var c=b||[[Db,[1,2,5,10,20,25,50,100,200,500]],[pb,[1,2,5,10,15,30]],[Ya,[1,2,5,10,15,30]],[Qa,[1,2,3,4,6,8,12]],[ua,[1,2]],[Za,[1,2]],[Ra,[1,2,3,4,6]],[Da,null]],d=c[c.length-1],e=D[d[0]],f=d[1],g;for(g=0;g<c.length;g++)if(d=c[g],e=D[d[0]],f=d[1],c[g+1]&&a<=(e*f[f.length-1]+D[c[g+1][0]])/2)break;e===D[Da]&&a<5*e&&(f=[1,2,5]);c=ob(a/e,f,d[0]===Da?nb(a/e):1);
13 return{unitRange:e,count:c,unitName:d[0]}}function Eb(a,b,c,d){var e=[],f={},g=M.global.useUTC,h,i=new Date(b),j=a.unitRange,k=a.count;if(u(b)){j>=D[pb]&&(i.setMilliseconds(0),i.setSeconds(j>=D[Ya]?0:k*P(i.getSeconds()/k)));if(j>=D[Ya])i[Fb](j>=D[Qa]?0:k*P(i[qb]()/k));if(j>=D[Qa])i[Gb](j>=D[ua]?0:k*P(i[rb]()/k));if(j>=D[ua])i[sb](j>=D[Ra]?1:k*P(i[Sa]()/k));j>=D[Ra]&&(i[Hb](j>=D[Da]?0:k*P(i[$a]()/k)),h=i[ab]());j>=D[Da]&&(h-=h%k,i[Ib](h));if(j===D[Za])i[sb](i[Sa]()-i[tb]()+o(d,1));b=1;h=i[ab]();for(var d=
14 i.getTime(),l=i[$a](),m=i[Sa](),p=g?0:(864E5+i.getTimezoneOffset()*6E4)%864E5;d<c;)e.push(d),j===D[Da]?d=bb(h+b*k,0):j===D[Ra]?d=bb(h,l+b*k):!g&&(j===D[ua]||j===D[Za])?d=bb(h,l,m+b*k*(j===D[ua]?1:7)):d+=j*k,b++;e.push(d);n(ub(e,function(a){return j<=D[Qa]&&a%D[ua]===p}),function(a){f[a]=ua})}e.info=r(a,{higherRanks:f,totalRange:j*k});return e}function Jb(){this.symbol=this.color=0}function Kb(a,b){var c=a.length,d,e;for(e=0;e<c;e++)a[e].ss_i=e;a.sort(function(a,c){d=b(a,c);return d===0?a.ss_i-c.ss_i:
15 d});for(e=0;e<c;e++)delete a[e].ss_i}function Ja(a){for(var b=a.length,c=a[0];b--;)a[b]<c&&(c=a[b]);return c}function va(a){for(var b=a.length,c=a[0];b--;)a[b]>c&&(c=a[b]);return c}function Ka(a,b){for(var c in a)a[c]&&a[c]!==b&&a[c].destroy&&a[c].destroy(),delete a[c]}function Ta(a){cb||(cb=U(Ea));a&&cb.appendChild(a);cb.innerHTML=""}function ka(a,b){var c="Highcharts error #"+a+": www.highcharts.com/errors/"+a;if(b)throw c;else O.console&&console.log(c)}function ia(a){return parseFloat(a.toPrecision(14))}
16 function La(a,b){Fa=o(a,b.animation)}function Lb(){var a=M.global.useUTC,b=a?"getUTC":"get",c=a?"setUTC":"set";bb=a?Date.UTC:function(a,b,c,g,h,i){return(new Date(a,b,o(c,1),o(g,0),o(h,0),o(i,0))).getTime()};qb=b+"Minutes";rb=b+"Hours";tb=b+"Day";Sa=b+"Date";$a=b+"Month";ab=b+"FullYear";Fb=c+"Minutes";Gb=c+"Hours";sb=c+"Date";Hb=c+"Month";Ib=c+"FullYear"}function wa(){}function Ma(a,b,c,d){this.axis=a;this.pos=b;this.type=c||"";this.isNew=!0;!c&&!d&&this.addLabel()}function vb(a,b){this.axis=a;if(b)this.options=
17 b,this.id=b.id}function Mb(a,b,c,d,e,f){var g=a.chart.inverted;this.axis=a;this.isNegative=c;this.options=b;this.x=d;this.total=null;this.points={};this.stack=e;this.percent=f==="percent";this.alignOptions={align:b.align||(g?c?"left":"right":"center"),verticalAlign:b.verticalAlign||(g?"middle":c?"bottom":"top"),y:o(b.y,g?4:c?14:-6),x:o(b.x,g?c?-6:6:0)};this.textAlign=b.textAlign||(g?c?"right":"left":"center")}function db(){this.init.apply(this,arguments)}function wb(){this.init.apply(this,arguments)}
18 function xb(a,b){this.init(a,b)}function eb(a,b){this.init(a,b)}function yb(){this.init.apply(this,arguments)}var w,y=document,O=window,R=Math,t=R.round,P=R.floor,xa=R.ceil,s=R.max,I=R.min,N=R.abs,V=R.cos,ca=R.sin,ya=R.PI,Ua=ya*2/360,oa=navigator.userAgent,Nb=O.opera,ta=/msie/i.test(oa)&&!Nb,fb=y.documentMode===8,gb=/AppleWebKit/.test(oa),hb=/Firefox/.test(oa),Ob=/(Mobile|Android|Windows Phone)/.test(oa),za="http://www.w3.org/2000/svg",Z=!!y.createElementNS&&!!y.createElementNS(za,"svg").createSVGRect,
19 Ub=hb&&parseInt(oa.split("Firefox/")[1],10)<4,$=!Z&&!ta&&!!y.createElement("canvas").getContext,Va,ib=y.documentElement.ontouchstart!==w,Pb={},zb=0,cb,M,Xa,Fa,Ab,D,pa=function(){},Ga=[],Ea="div",S="none",Qb="rgba(192,192,192,"+(Z?1.0E-4:0.002)+")",Db="millisecond",pb="second",Ya="minute",Qa="hour",ua="day",Za="week",Ra="month",Da="year",Rb="stroke-width",bb,qb,rb,tb,Sa,$a,ab,Fb,Gb,sb,Hb,Ib,W={};O.Highcharts=O.Highcharts?ka(16,!0):{};Xa=function(a,b,c){if(!u(b)||isNaN(b))return"Invalid date";var a=
20 o(a,"%Y-%m-%d %H:%M:%S"),d=new Date(b),e,f=d[rb](),g=d[tb](),h=d[Sa](),i=d[$a](),j=d[ab](),k=M.lang,l=k.weekdays,d=r({a:l[g].substr(0,3),A:l[g],d:Ba(h),e:h,b:k.shortMonths[i],B:k.months[i],m:Ba(i+1),y:j.toString().substr(2,2),Y:j,H:Ba(f),I:Ba(f%12||12),l:f%12||12,M:Ba(d[qb]()),p:f<12?"AM":"PM",P:f<12?"am":"pm",S:Ba(d.getSeconds()),L:Ba(t(b%1E3),3)},Highcharts.dateFormats);for(e in d)for(;a.indexOf("%"+e)!==-1;)a=a.replace("%"+e,typeof d[e]==="function"?d[e](b):d[e]);return c?a.substr(0,1).toUpperCase()+
21 a.substr(1):a};Jb.prototype={wrapColor:function(a){if(this.color>=a)this.color=0},wrapSymbol:function(a){if(this.symbol>=a)this.symbol=0}};D=function(){for(var a=0,b=arguments,c=b.length,d={};a<c;a++)d[b[a++]]=b[a];return d}(Db,1,pb,1E3,Ya,6E4,Qa,36E5,ua,864E5,Za,6048E5,Ra,26784E5,Da,31556952E3);Ab={init:function(a,b,c){var b=b||"",d=a.shift,e=b.indexOf("C")>-1,f=e?7:3,g,b=b.split(" "),c=[].concat(c),h,i,j=function(a){for(g=a.length;g--;)a[g]==="M"&&a.splice(g+1,0,a[g+1],a[g+2],a[g+1],a[g+2])};e&&
22 (j(b),j(c));a.isArea&&(h=b.splice(b.length-6,6),i=c.splice(c.length-6,6));if(d<=c.length/f&&b.length===c.length)for(;d--;)c=[].concat(c).splice(0,f).concat(c);a.shift=0;if(b.length)for(a=c.length;b.length<a;)d=[].concat(b).splice(b.length-f,f),e&&(d[f-6]=d[f-2],d[f-5]=d[f-1]),b=b.concat(d);h&&(b=b.concat(h),c=c.concat(i));return[b,c]},step:function(a,b,c,d){var e=[],f=a.length;if(c===1)e=d;else if(f===b.length&&c<1)for(;f--;)d=parseFloat(a[f]),e[f]=isNaN(d)?a[f]:c*parseFloat(b[f]-d)+d;else e=b;return e}};
23 (function(a){O.HighchartsAdapter=O.HighchartsAdapter||a&&{init:function(b){var c=a.fx,d=c.step,e,f=a.Tween,g=f&&f.propHooks;e=a.cssHooks.opacity;a.extend(a.easing,{easeOutQuad:function(a,b,c,d,e){return-d*(b/=e)*(b-2)+c}});a.each(["cur","_default","width","height","opacity"],function(a,b){var e=d,k,l;b==="cur"?e=c.prototype:b==="_default"&&f&&(e=g[b],b="set");(k=e[b])&&(e[b]=function(c){c=a?c:this;if(c.prop!=="align")return l=c.elem,l.attr?l.attr(c.prop,b==="cur"?w:c.now):k.apply(this,arguments)})});
24 mb(e,"get",function(a,b,c){return b.attr?b.opacity||0:a.call(this,b,c)});e=function(a){var c=a.elem,d;if(!a.started)d=b.init(c,c.d,c.toD),a.start=d[0],a.end=d[1],a.started=!0;c.attr("d",b.step(a.start,a.end,a.pos,c.toD))};f?g.d={set:e}:d.d=e;this.each=Array.prototype.forEach?function(a,b){return Array.prototype.forEach.call(a,b)}:function(a,b){for(var c=0,d=a.length;c<d;c++)if(b.call(a[c],a[c],c,a)===!1)return c};a.fn.highcharts=function(){var a="Chart",b=arguments,c,d;ea(b[0])&&(a=b[0],b=Array.prototype.slice.call(b,
25 1));c=b[0];if(c!==w)c.chart=c.chart||{},c.chart.renderTo=this[0],new Highcharts[a](c,b[1]),d=this;c===w&&(d=Ga[v(this[0],"data-highcharts-chart")]);return d}},getScript:a.getScript,inArray:a.inArray,adapterRun:function(b,c){return a(b)[c]()},grep:a.grep,map:function(a,c){for(var d=[],e=0,f=a.length;e<f;e++)d[e]=c.call(a[e],a[e],e,a);return d},offset:function(b){return a(b).offset()},addEvent:function(b,c,d){a(b).bind(c,d)},removeEvent:function(b,c,d){var e=y.removeEventListener?"removeEventListener":
26 "detachEvent";y[e]&&b&&!b[e]&&(b[e]=function(){});a(b).unbind(c,d)},fireEvent:function(b,c,d,e){var f=a.Event(c),g="detached"+c,h;!ta&&d&&(delete d.layerX,delete d.layerY);r(f,d);b[c]&&(b[g]=b[c],b[c]=null);a.each(["preventDefault","stopPropagation"],function(a,b){var c=f[b];f[b]=function(){try{c.call(f)}catch(a){b==="preventDefault"&&(h=!0)}}});a(b).trigger(f);b[g]&&(b[c]=b[g],b[g]=null);e&&!f.isDefaultPrevented()&&!h&&e(f)},washMouseEvent:function(a){var c=a.originalEvent||a;if(c.pageX===w)c.pageX=
27 a.pageX,c.pageY=a.pageY;return c},animate:function(b,c,d){var e=a(b);if(!b.style)b.style={};if(c.d)b.toD=c.d,c.d=1;e.stop();c.opacity!==w&&b.attr&&(c.opacity+="px");e.animate(c,d)},stop:function(b){a(b).stop()}}})(O.jQuery);var X=O.HighchartsAdapter,G=X||{};X&&X.init.call(X,Ab);var jb=G.adapterRun,Vb=G.getScript,qa=G.inArray,n=G.each,ub=G.grep,Wb=G.offset,Na=G.map,J=G.addEvent,aa=G.removeEvent,z=G.fireEvent,Xb=G.washMouseEvent,Bb=G.animate,Wa=G.stop,G={enabled:!0,x:0,y:15,style:{color:"#666",cursor:"default",
28 fontSize:"11px",lineHeight:"14px"}};M={colors:"#2f7ed8,#0d233a,#8bbc21,#910000,#1aadce,#492970,#f28f43,#77a1e5,#c42525,#a6c96a".split(","),symbols:["circle","diamond","square","triangle","triangle-down"],lang:{loading:"Loading...",months:"January,February,March,April,May,June,July,August,September,October,November,December".split(","),shortMonths:"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec".split(","),weekdays:"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday".split(","),decimalPoint:".",
29 numericSymbols:"k,M,G,T,P,E".split(","),resetZoom:"Reset zoom",resetZoomTitle:"Reset zoom level 1:1",thousandsSep:","},global:{useUTC:!0,canvasToolsURL:"http://code.highcharts.com/3.0.6/modules/canvas-tools.js",VMLRadialGradientURL:"http://code.highcharts.com/3.0.6/gfx/vml-radial-gradient.png"},chart:{borderColor:"#4572A7",borderRadius:5,defaultSeriesType:"line",ignoreHiddenSeries:!0,spacing:[10,10,15,10],style:{fontFamily:'"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif',
30 fontSize:"12px"},backgroundColor:"#FFFFFF",plotBorderColor:"#C0C0C0",resetZoomButton:{theme:{zIndex:20},position:{align:"right",x:-10,y:10}}},title:{text:"Chart title",align:"center",margin:15,style:{color:"#274b6d",fontSize:"16px"}},subtitle:{text:"",align:"center",style:{color:"#4d759e"}},plotOptions:{line:{allowPointSelect:!1,showCheckbox:!1,animation:{duration:1E3},events:{},lineWidth:2,marker:{enabled:!0,lineWidth:0,radius:4,lineColor:"#FFFFFF",states:{hover:{enabled:!0},select:{fillColor:"#FFFFFF",
31 lineColor:"#000000",lineWidth:2}}},point:{events:{}},dataLabels:x(G,{align:"center",enabled:!1,formatter:function(){return this.y===null?"":Aa(this.y,-1)},verticalAlign:"bottom",y:0}),cropThreshold:300,pointRange:0,showInLegend:!0,states:{hover:{marker:{}},select:{marker:{}}},stickyTracking:!0}},labels:{style:{position:"absolute",color:"#3E576F"}},legend:{enabled:!0,align:"center",layout:"horizontal",labelFormatter:function(){return this.name},borderWidth:1,borderColor:"#909090",borderRadius:5,navigation:{activeColor:"#274b6d",
32 inactiveColor:"#CCC"},shadow:!1,itemStyle:{cursor:"pointer",color:"#274b6d",fontSize:"12px"},itemHoverStyle:{color:"#000"},itemHiddenStyle:{color:"#CCC"},itemCheckboxStyle:{position:"absolute",width:"13px",height:"13px"},symbolWidth:16,symbolPadding:5,verticalAlign:"bottom",x:0,y:0,title:{style:{fontWeight:"bold"}}},loading:{labelStyle:{fontWeight:"bold",position:"relative",top:"1em"},style:{position:"absolute",backgroundColor:"white",opacity:0.5,textAlign:"center"}},tooltip:{enabled:!0,animation:Z,
33 backgroundColor:"rgba(255, 255, 255, .85)",borderWidth:1,borderRadius:3,dateTimeLabelFormats:{millisecond:"%A, %b %e, %H:%M:%S.%L",second:"%A, %b %e, %H:%M:%S",minute:"%A, %b %e, %H:%M",hour:"%A, %b %e, %H:%M",day:"%A, %b %e, %Y",week:"Week from %A, %b %e, %Y",month:"%B %Y",year:"%Y"},headerFormat:'<span style="font-size: 10px">{point.key}</span><br/>',pointFormat:'<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b><br/>',shadow:!0,snap:Ob?25:10,style:{color:"#333333",cursor:"default",
34 fontSize:"12px",padding:"8px",whiteSpace:"nowrap"}},credits:{enabled:!0,text:"Highcharts.com",href:"http://www.highcharts.com",position:{align:"right",x:-10,verticalAlign:"bottom",y:-5},style:{cursor:"pointer",color:"#909090",fontSize:"9px"}}};var Y=M.plotOptions,X=Y.line;Lb();var ra=function(a){var b=[],c,d;(function(a){a&&a.stops?d=Na(a.stops,function(a){return ra(a[1])}):(c=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/.exec(a))?b=[C(c[1]),C(c[2]),
35 C(c[3]),parseFloat(c[4],10)]:(c=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(a))?b=[C(c[1],16),C(c[2],16),C(c[3],16),1]:(c=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(a))&&(b=[C(c[1]),C(c[2]),C(c[3]),1])})(a);return{get:function(c){var f;d?(f=x(a),f.stops=[].concat(f.stops),n(d,function(a,b){f.stops[b]=[f.stops[b][0],a.get(c)]})):f=b&&!isNaN(b[0])?c==="rgb"?"rgb("+b[0]+","+b[1]+","+b[2]+")":c==="a"?b[3]:"rgba("+b.join(",")+")":a;return f},brighten:function(a){if(d)n(d,
36 function(b){b.brighten(a)});else if(sa(a)&&a!==0){var c;for(c=0;c<3;c++)b[c]+=C(a*255),b[c]<0&&(b[c]=0),b[c]>255&&(b[c]=255)}return this},rgba:b,setOpacity:function(a){b[3]=a;return this}}};wa.prototype={init:function(a,b){this.element=b==="span"?U(b):y.createElementNS(za,b);this.renderer=a;this.attrSetters={}},opacity:1,animate:function(a,b,c){b=o(b,Fa,!0);Wa(this);if(b){b=x(b);if(c)b.complete=c;Bb(this,a,b)}else this.attr(a),c&&c()},attr:function(a,b){var c,d,e,f,g=this.element,h=g.nodeName.toLowerCase(),
37 i=this.renderer,j,k=this.attrSetters,l=this.shadows,m,p,q=this;ea(a)&&u(b)&&(c=a,a={},a[c]=b);if(ea(a))c=a,h==="circle"?c={x:"cx",y:"cy"}[c]||c:c==="strokeWidth"&&(c="stroke-width"),q=v(g,c)||this[c]||0,c!=="d"&&c!=="visibility"&&c!=="fill"&&(q=parseFloat(q));else{for(c in a)if(j=!1,d=a[c],e=k[c]&&k[c].call(this,d,c),e!==!1){e!==w&&(d=e);if(c==="d")d&&d.join&&(d=d.join(" ")),/(NaN| {2}|^$)/.test(d)&&(d="M 0 0");else if(c==="x"&&h==="text")for(e=0;e<g.childNodes.length;e++)f=g.childNodes[e],v(f,"x")===
38 v(g,"x")&&v(f,"x",d);else if(this.rotation&&(c==="x"||c==="y"))p=!0;else if(c==="fill")d=i.color(d,g,c);else if(h==="circle"&&(c==="x"||c==="y"))c={x:"cx",y:"cy"}[c]||c;else if(h==="rect"&&c==="r")v(g,{rx:d,ry:d}),j=!0;else if(c==="translateX"||c==="translateY"||c==="rotation"||c==="verticalAlign"||c==="scaleX"||c==="scaleY")j=p=!0;else if(c==="stroke")d=i.color(d,g,c);else if(c==="dashstyle")if(c="stroke-dasharray",d=d&&d.toLowerCase(),d==="solid")d=S;else{if(d){d=d.replace("shortdashdotdot","3,1,1,1,1,1,").replace("shortdashdot",
39 "3,1,1,1").replace("shortdot","1,1,").replace("shortdash","3,1,").replace("longdash","8,3,").replace(/dot/g,"1,3,").replace("dash","4,3,").replace(/,$/,"").split(",");for(e=d.length;e--;)d[e]=C(d[e])*o(a["stroke-width"],this["stroke-width"]);d=d.join(",")}}else if(c==="width")d=C(d);else if(c==="align")c="text-anchor",d={left:"start",center:"middle",right:"end"}[d];else if(c==="title")e=g.getElementsByTagName("title")[0],e||(e=y.createElementNS(za,"title"),g.appendChild(e)),e.textContent=d;c==="strokeWidth"&&
40 (c="stroke-width");if(c==="stroke-width"||c==="stroke"){this[c]=d;if(this.stroke&&this["stroke-width"])v(g,"stroke",this.stroke),v(g,"stroke-width",this["stroke-width"]),this.hasStroke=!0;else if(c==="stroke-width"&&d===0&&this.hasStroke)g.removeAttribute("stroke"),this.hasStroke=!1;j=!0}this.symbolName&&/^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(c)&&(m||(this.symbolAttr(a),m=!0),j=!0);if(l&&/^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(c))for(e=l.length;e--;)v(l[e],
41 c,c==="height"?s(d-(l[e].cutHeight||0),0):d);if((c==="width"||c==="height")&&h==="rect"&&d<0)d=0;this[c]=d;c==="text"?(d!==this.textStr&&delete this.bBox,this.textStr=d,this.added&&i.buildText(this)):j||v(g,c,d)}p&&this.updateTransform()}return q},addClass:function(a){var b=this.element,c=v(b,"class")||"";c.indexOf(a)===-1&&v(b,"class",c+" "+a);return this},symbolAttr:function(a){var b=this;n("x,y,r,start,end,width,height,innerR,anchorX,anchorY".split(","),function(c){b[c]=o(a[c],b[c])});b.attr({d:b.renderer.symbols[b.symbolName](b.x,
42 b.y,b.width,b.height,b)})},clip:function(a){return this.attr("clip-path",a?"url("+this.renderer.url+"#"+a.id+")":S)},crisp:function(a,b,c,d,e){var f,g={},h={},i,a=a||this.strokeWidth||this.attr&&this.attr("stroke-width")||0;i=t(a)%2/2;h.x=P(b||this.x||0)+i;h.y=P(c||this.y||0)+i;h.width=P((d||this.width||0)-2*i);h.height=P((e||this.height||0)-2*i);h.strokeWidth=a;for(f in h)this[f]!==h[f]&&(this[f]=g[f]=h[f]);return g},css:function(a){var b=this.element,c=a&&a.width&&b.nodeName.toLowerCase()==="text",
43 d,e="",f=function(a,b){return"-"+b.toLowerCase()};if(a&&a.color)a.fill=a.color;this.styles=a=r(this.styles,a);$&&c&&delete a.width;if(ta&&!Z)c&&delete a.width,K(this.element,a);else{for(d in a)e+=d.replace(/([A-Z])/g,f)+":"+a[d]+";";v(b,"style",e)}c&&this.added&&this.renderer.buildText(this);return this},on:function(a,b){var c=this,d=c.element;ib&&a==="click"?(d.ontouchstart=function(a){c.touchEventFired=Date.now();a.preventDefault();b.call(d,a)},d.onclick=function(a){(oa.indexOf("Android")===-1||
44 Date.now()-(c.touchEventFired||0)>1100)&&b.call(d,a)}):d["on"+a]=b;return this},setRadialReference:function(a){this.element.radialReference=a;return this},translate:function(a,b){return this.attr({translateX:a,translateY:b})},invert:function(){this.inverted=!0;this.updateTransform();return this},htmlCss:function(a){var b=this.element;if(b=a&&b.tagName==="SPAN"&&a.width)delete a.width,this.textWidth=b,this.updateTransform();this.styles=r(this.styles,a);K(this.element,a);return this},htmlGetBBox:function(){var a=
45 this.element,b=this.bBox;if(!b){if(a.nodeName==="text")a.style.position="absolute";b=this.bBox={x:a.offsetLeft,y:a.offsetTop,width:a.offsetWidth,height:a.offsetHeight}}return b},htmlUpdateTransform:function(){if(this.added){var a=this.renderer,b=this.element,c=this.translateX||0,d=this.translateY||0,e=this.x||0,f=this.y||0,g=this.textAlign||"left",h={left:0,center:0.5,right:1}[g],i=g&&g!=="left",j=this.shadows;K(b,{marginLeft:c,marginTop:d});j&&n(j,function(a){K(a,{marginLeft:c+1,marginTop:d+1})});
46 this.inverted&&n(b.childNodes,function(c){a.invertChild(c,b)});if(b.tagName==="SPAN"){var k,l,j=this.rotation,m;k=0;var p=1,q=0,ba;m=C(this.textWidth);var A=this.xCorr||0,L=this.yCorr||0,Sb=[j,g,b.innerHTML,this.textWidth].join(",");if(Sb!==this.cTT){u(j)&&(k=j*Ua,p=V(k),q=ca(k),this.setSpanRotation(j,q,p));k=o(this.elemWidth,b.offsetWidth);l=o(this.elemHeight,b.offsetHeight);if(k>m&&/[ \-]/.test(b.textContent||b.innerText))K(b,{width:m+"px",display:"block",whiteSpace:"normal"}),k=m;m=a.fontMetrics(b.style.fontSize).b;
47 A=p<0&&-k;L=q<0&&-l;ba=p*q<0;A+=q*m*(ba?1-h:h);L-=p*m*(j?ba?h:1-h:1);i&&(A-=k*h*(p<0?-1:1),j&&(L-=l*h*(q<0?-1:1)),K(b,{textAlign:g}));this.xCorr=A;this.yCorr=L}K(b,{left:e+A+"px",top:f+L+"px"});if(gb)l=b.offsetHeight;this.cTT=Sb}}else this.alignOnAdd=!0},setSpanRotation:function(a){var b={};b[ta?"-ms-transform":gb?"-webkit-transform":hb?"MozTransform":Nb?"-o-transform":""]=b.transform="rotate("+a+"deg)";K(this.element,b)},updateTransform:function(){var a=this.translateX||0,b=this.translateY||0,c=
48 this.scaleX,d=this.scaleY,e=this.inverted,f=this.rotation;e&&(a+=this.attr("width"),b+=this.attr("height"));a=["translate("+a+","+b+")"];e?a.push("rotate(90) scale(-1,1)"):f&&a.push("rotate("+f+" "+(this.x||0)+" "+(this.y||0)+")");(u(c)||u(d))&&a.push("scale("+o(c,1)+" "+o(d,1)+")");a.length&&v(this.element,"transform",a.join(" "))},toFront:function(){var a=this.element;a.parentNode.appendChild(a);return this},align:function(a,b,c){var d,e,f,g,h={};e=this.renderer;f=e.alignedObjects;if(a){if(this.alignOptions=
49 a,this.alignByTranslate=b,!c||ea(c))this.alignTo=d=c||"renderer",ga(f,this),f.push(this),c=null}else a=this.alignOptions,b=this.alignByTranslate,d=this.alignTo;c=o(c,e[d],e);d=a.align;e=a.verticalAlign;f=(c.x||0)+(a.x||0);g=(c.y||0)+(a.y||0);if(d==="right"||d==="center")f+=(c.width-(a.width||0))/{right:1,center:2}[d];h[b?"translateX":"x"]=t(f);if(e==="bottom"||e==="middle")g+=(c.height-(a.height||0))/({bottom:1,middle:2}[e]||1);h[b?"translateY":"y"]=t(g);this[this.placed?"animate":"attr"](h);this.placed=
50 !0;this.alignAttr=h;return this},getBBox:function(){var a=this.bBox,b=this.renderer,c,d=this.rotation;c=this.element;var e=this.styles,f=d*Ua;if(!a){if(c.namespaceURI===za||b.forExport){try{a=c.getBBox?r({},c.getBBox()):{width:c.offsetWidth,height:c.offsetHeight}}catch(g){}if(!a||a.width<0)a={width:0,height:0}}else a=this.htmlGetBBox();if(b.isSVG){b=a.width;c=a.height;if(ta&&e&&e.fontSize==="11px"&&c.toPrecision(3)==="22.7")a.height=c=14;if(d)a.width=N(c*ca(f))+N(b*V(f)),a.height=N(c*V(f))+N(b*ca(f))}this.bBox=
51 a}return a},show:function(){return this.attr({visibility:"visible"})},hide:function(){return this.attr({visibility:"hidden"})},fadeOut:function(a){var b=this;b.animate({opacity:0},{duration:a||150,complete:function(){b.hide()}})},add:function(a){var b=this.renderer,c=a||b,d=c.element||b.box,e=d.childNodes,f=this.element,g=v(f,"zIndex"),h;if(a)this.parentGroup=a;this.parentInverted=a&&a.inverted;this.textStr!==void 0&&b.buildText(this);if(g)c.handleZ=!0,g=C(g);if(c.handleZ)for(c=0;c<e.length;c++)if(a=
52 e[c],b=v(a,"zIndex"),a!==f&&(C(b)>g||!u(g)&&u(b))){d.insertBefore(f,a);h=!0;break}h||d.appendChild(f);this.added=!0;z(this,"add");return this},safeRemoveChild:function(a){var b=a.parentNode;b&&b.removeChild(a)},destroy:function(){var a=this,b=a.element||{},c=a.shadows,d=a.renderer.isSVG&&b.nodeName==="SPAN"&&b.parentNode,e,f;b.onclick=b.onmouseout=b.onmouseover=b.onmousemove=b.point=null;Wa(a);if(a.clipPath)a.clipPath=a.clipPath.destroy();if(a.stops){for(f=0;f<a.stops.length;f++)a.stops[f]=a.stops[f].destroy();
53 a.stops=null}a.safeRemoveChild(b);for(c&&n(c,function(b){a.safeRemoveChild(b)});d&&d.childNodes.length===0;)b=d.parentNode,a.safeRemoveChild(d),d=b;a.alignTo&&ga(a.renderer.alignedObjects,a);for(e in a)delete a[e];return null},shadow:function(a,b,c){var d=[],e,f,g=this.element,h,i,j,k;if(a){i=o(a.width,3);j=(a.opacity||0.15)/i;k=this.parentInverted?"(-1,-1)":"("+o(a.offsetX,1)+", "+o(a.offsetY,1)+")";for(e=1;e<=i;e++){f=g.cloneNode(0);h=i*2+1-2*e;v(f,{isShadow:"true",stroke:a.color||"black","stroke-opacity":j*
54 e,"stroke-width":h,transform:"translate"+k,fill:S});if(c)v(f,"height",s(v(f,"height")-h,0)),f.cutHeight=h;b?b.element.appendChild(f):g.parentNode.insertBefore(f,g);d.push(f)}this.shadows=d}return this}};var Ha=function(){this.init.apply(this,arguments)};Ha.prototype={Element:wa,init:function(a,b,c,d){var e=location,f,g;f=this.createElement("svg").attr({version:"1.1"});g=f.element;a.appendChild(g);a.innerHTML.indexOf("xmlns")===-1&&v(g,"xmlns",za);this.isSVG=!0;this.box=g;this.boxWrapper=f;this.alignedObjects=
55 [];this.url=(hb||gb)&&y.getElementsByTagName("base").length?e.href.replace(/#.*?$/,"").replace(/([\('\)])/g,"\\$1").replace(/ /g,"%20"):"";this.createElement("desc").add().element.appendChild(y.createTextNode("Created with Highcharts 3.0.6"));this.defs=this.createElement("defs").add();this.forExport=d;this.gradients={};this.setSize(b,c,!1);var h;if(hb&&a.getBoundingClientRect)this.subPixelFix=b=function(){K(a,{left:0,top:0});h=a.getBoundingClientRect();K(a,{left:xa(h.left)-h.left+"px",top:xa(h.top)-
56 h.top+"px"})},b(),J(O,"resize",b)},isHidden:function(){return!this.boxWrapper.getBBox().width},destroy:function(){var a=this.defs;this.box=null;this.boxWrapper=this.boxWrapper.destroy();Ka(this.gradients||{});this.gradients=null;if(a)this.defs=a.destroy();this.subPixelFix&&aa(O,"resize",this.subPixelFix);return this.alignedObjects=null},createElement:function(a){var b=new this.Element;b.init(this,a);return b},draw:function(){},buildText:function(a){for(var b=a.element,c=this,d=c.forExport,e=o(a.textStr,
57 "").toString().replace(/<(b|strong)>/g,'<span style="font-weight:bold">').replace(/<(i|em)>/g,'<span style="font-style:italic">').replace(/<a/g,"<span").replace(/<\/(b|strong|i|em|a)>/g,"</span>").split(/<br.*?>/g),f=b.childNodes,g=/style="([^"]+)"/,h=/href="(http[^"]+)"/,i=v(b,"x"),j=a.styles,k=j&&j.width&&C(j.width),l=j&&j.lineHeight,m=f.length;m--;)b.removeChild(f[m]);k&&!a.added&&this.box.appendChild(b);e[e.length-1]===""&&e.pop();n(e,function(e,f){var m,o=0,e=e.replace(/<span/g,"|||<span").replace(/<\/span>/g,
58 "</span>|||");m=e.split("|||");n(m,function(e){if(e!==""||m.length===1){var p={},n=y.createElementNS(za,"tspan"),s;g.test(e)&&(s=e.match(g)[1].replace(/(;| |^)color([ :])/,"$1fill$2"),v(n,"style",s));h.test(e)&&!d&&(v(n,"onclick",'location.href="'+e.match(h)[1]+'"'),K(n,{cursor:"pointer"}));e=(e.replace(/<(.|\n)*?>/g,"")||" ").replace(/&lt;/g,"<").replace(/&gt;/g,">");if(e!==" "&&(n.appendChild(y.createTextNode(e)),o?p.dx=0:p.x=i,v(n,p),!o&&f&&(!Z&&d&&K(n,{display:"block"}),v(n,"dy",l||c.fontMetrics(/px$/.test(n.style.fontSize)?
59 n.style.fontSize:j.fontSize).h,gb&&n.offsetHeight)),b.appendChild(n),o++,k))for(var e=e.replace(/([^\^])-/g,"$1- ").split(" "),u,t,p=a._clipHeight,E=[],w=C(l||16),B=1;e.length||E.length;)delete a.bBox,u=a.getBBox(),t=u.width,u=t>k,!u||e.length===1?(e=E,E=[],e.length&&(B++,p&&B*w>p?(e=["..."],a.attr("title",a.textStr)):(n=y.createElementNS(za,"tspan"),v(n,{dy:w,x:i}),s&&v(n,"style",s),b.appendChild(n),t>k&&(k=t)))):(n.removeChild(n.firstChild),E.unshift(e.pop())),e.length&&n.appendChild(y.createTextNode(e.join(" ").replace(/- /g,
60 "-")))}})})},button:function(a,b,c,d,e,f,g,h){var i=this.label(a,b,c,null,null,null,null,null,"button"),j=0,k,l,m,p,q,n,a={x1:0,y1:0,x2:0,y2:1},e=x({"stroke-width":1,stroke:"#CCCCCC",fill:{linearGradient:a,stops:[[0,"#FEFEFE"],[1,"#F6F6F6"]]},r:2,padding:5,style:{color:"black"}},e);m=e.style;delete e.style;f=x(e,{stroke:"#68A",fill:{linearGradient:a,stops:[[0,"#FFF"],[1,"#ACF"]]}},f);p=f.style;delete f.style;g=x(e,{stroke:"#68A",fill:{linearGradient:a,stops:[[0,"#9BD"],[1,"#CDF"]]}},g);q=g.style;
61 delete g.style;h=x(e,{style:{color:"#CCC"}},h);n=h.style;delete h.style;J(i.element,ta?"mouseover":"mouseenter",function(){j!==3&&i.attr(f).css(p)});J(i.element,ta?"mouseout":"mouseleave",function(){j!==3&&(k=[e,f,g][j],l=[m,p,q][j],i.attr(k).css(l))});i.setState=function(a){(i.state=j=a)?a===2?i.attr(g).css(q):a===3&&i.attr(h).css(n):i.attr(e).css(m)};return i.on("click",function(){j!==3&&d.call(i)}).attr(e).css(r({cursor:"default"},m))},crispLine:function(a,b){a[1]===a[4]&&(a[1]=a[4]=t(a[1])-b%
62 2/2);a[2]===a[5]&&(a[2]=a[5]=t(a[2])+b%2/2);return a},path:function(a){var b={fill:S};Ia(a)?b.d=a:T(a)&&r(b,a);return this.createElement("path").attr(b)},circle:function(a,b,c){a=T(a)?a:{x:a,y:b,r:c};return this.createElement("circle").attr(a)},arc:function(a,b,c,d,e,f){if(T(a))b=a.y,c=a.r,d=a.innerR,e=a.start,f=a.end,a=a.x;a=this.symbol("arc",a||0,b||0,c||0,c||0,{innerR:d||0,start:e||0,end:f||0});a.r=c;return a},rect:function(a,b,c,d,e,f){e=T(a)?a.r:e;e=this.createElement("rect").attr({rx:e,ry:e,
63 fill:S});return e.attr(T(a)?a:e.crisp(f,a,b,s(c,0),s(d,0)))},setSize:function(a,b,c){var d=this.alignedObjects,e=d.length;this.width=a;this.height=b;for(this.boxWrapper[o(c,!0)?"animate":"attr"]({width:a,height:b});e--;)d[e].align()},g:function(a){var b=this.createElement("g");return u(a)?b.attr({"class":"highcharts-"+a}):b},image:function(a,b,c,d,e){var f={preserveAspectRatio:S};arguments.length>1&&r(f,{x:b,y:c,width:d,height:e});f=this.createElement("image").attr(f);f.element.setAttributeNS?f.element.setAttributeNS("http://www.w3.org/1999/xlink",
64 "href",a):f.element.setAttribute("hc-svg-href",a);return f},symbol:function(a,b,c,d,e,f){var g,h=this.symbols[a],h=h&&h(t(b),t(c),d,e,f),i=/^url\((.*?)\)$/,j,k;if(h)g=this.path(h),r(g,{symbolName:a,x:b,y:c,width:d,height:e}),f&&r(g,f);else if(i.test(a))k=function(a,b){a.element&&(a.attr({width:b[0],height:b[1]}),a.alignByTranslate||a.translate(t((d-b[0])/2),t((e-b[1])/2)))},j=a.match(i)[1],a=Pb[j],g=this.image(j).attr({x:b,y:c}),g.isImg=!0,a?k(g,a):(g.attr({width:0,height:0}),U("img",{onload:function(){k(g,
65 Pb[j]=[this.width,this.height])},src:j}));return g},symbols:{circle:function(a,b,c,d){var e=0.166*c;return["M",a+c/2,b,"C",a+c+e,b,a+c+e,b+d,a+c/2,b+d,"C",a-e,b+d,a-e,b,a+c/2,b,"Z"]},square:function(a,b,c,d){return["M",a,b,"L",a+c,b,a+c,b+d,a,b+d,"Z"]},triangle:function(a,b,c,d){return["M",a+c/2,b,"L",a+c,b+d,a,b+d,"Z"]},"triangle-down":function(a,b,c,d){return["M",a,b,"L",a+c,b,a+c/2,b+d,"Z"]},diamond:function(a,b,c,d){return["M",a+c/2,b,"L",a+c,b+d/2,a+c/2,b+d,a,b+d/2,"Z"]},arc:function(a,b,c,d,
66 e){var f=e.start,c=e.r||c||d,g=e.end-0.001,d=e.innerR,h=e.open,i=V(f),j=ca(f),k=V(g),g=ca(g),e=e.end-f<ya?0:1;return["M",a+c*i,b+c*j,"A",c,c,0,e,1,a+c*k,b+c*g,h?"M":"L",a+d*k,b+d*g,"A",d,d,0,e,0,a+d*i,b+d*j,h?"":"Z"]}},clipRect:function(a,b,c,d){var e="highcharts-"+zb++,f=this.createElement("clipPath").attr({id:e}).add(this.defs),a=this.rect(a,b,c,d,0).add(f);a.id=e;a.clipPath=f;return a},color:function(a,b,c){var d=this,e,f=/^rgba/,g,h,i,j,k,l,m,p=[];a&&a.linearGradient?g="linearGradient":a&&a.radialGradient&&
67 (g="radialGradient");if(g){c=a[g];h=d.gradients;j=a.stops;b=b.radialReference;Ia(c)&&(a[g]=c={x1:c[0],y1:c[1],x2:c[2],y2:c[3],gradientUnits:"userSpaceOnUse"});g==="radialGradient"&&b&&!u(c.gradientUnits)&&(c=x(c,{cx:b[0]-b[2]/2+c.cx*b[2],cy:b[1]-b[2]/2+c.cy*b[2],r:c.r*b[2],gradientUnits:"userSpaceOnUse"}));for(m in c)m!=="id"&&p.push(m,c[m]);for(m in j)p.push(j[m]);p=p.join(",");h[p]?a=h[p].id:(c.id=a="highcharts-"+zb++,h[p]=i=d.createElement(g).attr(c).add(d.defs),i.stops=[],n(j,function(a){f.test(a[1])?
68 (e=ra(a[1]),k=e.get("rgb"),l=e.get("a")):(k=a[1],l=1);a=d.createElement("stop").attr({offset:a[0],"stop-color":k,"stop-opacity":l}).add(i);i.stops.push(a)}));return"url("+d.url+"#"+a+")"}else return f.test(a)?(e=ra(a),v(b,c+"-opacity",e.get("a")),e.get("rgb")):(b.removeAttribute(c+"-opacity"),a)},text:function(a,b,c,d){var e=M.chart.style,f=$||!Z&&this.forExport;if(d&&!this.forExport)return this.html(a,b,c);b=t(o(b,0));c=t(o(c,0));a=this.createElement("text").attr({x:b,y:c,text:a}).css({fontFamily:e.fontFamily,
69 fontSize:e.fontSize});f&&a.css({position:"absolute"});a.x=b;a.y=c;return a},html:function(a,b,c){var d=M.chart.style,e=this.createElement("span"),f=e.attrSetters,g=e.element,h=e.renderer;f.text=function(a){a!==g.innerHTML&&delete this.bBox;g.innerHTML=a;return!1};f.x=f.y=f.align=function(a,b){b==="align"&&(b="textAlign");e[b]=a;e.htmlUpdateTransform();return!1};e.attr({text:a,x:t(b),y:t(c)}).css({position:"absolute",whiteSpace:"nowrap",fontFamily:d.fontFamily,fontSize:d.fontSize});e.css=e.htmlCss;
70 if(h.isSVG)e.add=function(a){var b,c=h.box.parentNode,d=[];if(a){if(b=a.div,!b){for(;a;)d.push(a),a=a.parentGroup;n(d.reverse(),function(a){var d;b=a.div=a.div||U(Ea,{className:v(a.element,"class")},{position:"absolute",left:(a.translateX||0)+"px",top:(a.translateY||0)+"px"},b||c);d=b.style;r(a.attrSetters,{translateX:function(a){d.left=a+"px"},translateY:function(a){d.top=a+"px"},visibility:function(a,b){d[b]=a}})})}}else b=c;b.appendChild(g);e.added=!0;e.alignOnAdd&&e.htmlUpdateTransform();return e};
71 return e},fontMetrics:function(a){var a=C(a||11),a=a<24?a+4:t(a*1.2),b=t(a*0.8);return{h:a,b:b}},label:function(a,b,c,d,e,f,g,h,i){function j(){var a,b;a=o.element.style;L=(Oa===void 0||la===void 0||q.styles.textAlign)&&o.getBBox();q.width=(Oa||L.width||0)+2*da+kb;q.height=(la||L.height||0)+2*da;v=da+p.fontMetrics(a&&a.fontSize).b;if(C){if(!A)a=t(-s*da),b=h?-v:0,q.box=A=d?p.symbol(d,a,b,q.width,q.height):p.rect(a,b,q.width,q.height,0,lb[Rb]),A.add(q);A.isImg||A.attr(x({width:q.width,height:q.height},
72 lb));lb=null}}function k(){var a=q.styles,a=a&&a.textAlign,b=kb+da*(1-s),c;c=h?0:v;if(u(Oa)&&(a==="center"||a==="right"))b+={center:0.5,right:1}[a]*(Oa-L.width);(b!==o.x||c!==o.y)&&o.attr({x:b,y:c});o.x=b;o.y=c}function l(a,b){A?A.attr(a,b):lb[a]=b}function m(){o.add(q);q.attr({text:a,x:b,y:c});A&&u(e)&&q.attr({anchorX:e,anchorY:f})}var p=this,q=p.g(i),o=p.text("",0,0,g).attr({zIndex:1}),A,L,s=0,da=3,kb=0,Oa,la,E,H,B=0,lb={},v,g=q.attrSetters,C;J(q,"add",m);g.width=function(a){Oa=a;return!1};g.height=
73 function(a){la=a;return!1};g.padding=function(a){u(a)&&a!==da&&(da=a,k());return!1};g.paddingLeft=function(a){u(a)&&a!==kb&&(kb=a,k());return!1};g.align=function(a){s={left:0,center:0.5,right:1}[a];return!1};g.text=function(a,b){o.attr(b,a);j();k();return!1};g[Rb]=function(a,b){C=!0;B=a%2/2;l(b,a);return!1};g.stroke=g.fill=g.r=function(a,b){b==="fill"&&(C=!0);l(b,a);return!1};g.anchorX=function(a,b){e=a;l(b,a+B-E);return!1};g.anchorY=function(a,b){f=a;l(b,a-H);return!1};g.x=function(a){q.x=a;a-=s*
74 ((Oa||L.width)+da);E=t(a);q.attr("translateX",E);return!1};g.y=function(a){H=q.y=t(a);q.attr("translateY",H);return!1};var y=q.css;return r(q,{css:function(a){if(a){var b={},a=x(a);n("fontSize,fontWeight,fontFamily,color,lineHeight,width,textDecoration,textShadow".split(","),function(c){a[c]!==w&&(b[c]=a[c],delete a[c])});o.css(b)}return y.call(q,a)},getBBox:function(){return{width:L.width+2*da,height:L.height+2*da,x:L.x-da,y:L.y-da}},shadow:function(a){A&&A.shadow(a);return q},destroy:function(){aa(q,
75 "add",m);aa(q.element,"mouseenter");aa(q.element,"mouseleave");o&&(o=o.destroy());A&&(A=A.destroy());wa.prototype.destroy.call(q);q=p=j=k=l=m=null}})}};Va=Ha;var F;if(!Z&&!$){Highcharts.VMLElement=F={init:function(a,b){var c=["<",b,' filled="f" stroked="f"'],d=["position: ","absolute",";"],e=b===Ea;(b==="shape"||e)&&d.push("left:0;top:0;width:1px;height:1px;");d.push("visibility: ",e?"hidden":"visible");c.push(' style="',d.join(""),'"/>');if(b)c=e||b==="span"||b==="img"?c.join(""):a.prepVML(c),this.element=
76 U(c);this.renderer=a;this.attrSetters={}},add:function(a){var b=this.renderer,c=this.element,d=b.box,d=a?a.element||a:d;a&&a.inverted&&b.invertChild(c,d);d.appendChild(c);this.added=!0;this.alignOnAdd&&!this.deferUpdateTransform&&this.updateTransform();z(this,"add");return this},updateTransform:wa.prototype.htmlUpdateTransform,setSpanRotation:function(a,b,c){K(this.element,{filter:a?["progid:DXImageTransform.Microsoft.Matrix(M11=",c,", M12=",-b,", M21=",b,", M22=",c,", sizingMethod='auto expand')"].join(""):
77 S})},pathToVML:function(a){for(var b=a.length,c=[],d;b--;)if(sa(a[b]))c[b]=t(a[b]*10)-5;else if(a[b]==="Z")c[b]="x";else if(c[b]=a[b],a.isArc&&(a[b]==="wa"||a[b]==="at"))d=a[b]==="wa"?1:-1,c[b+5]===c[b+7]&&(c[b+7]-=d),c[b+6]===c[b+8]&&(c[b+8]-=d);return c.join(" ")||"x"},attr:function(a,b){var c,d,e,f=this.element||{},g=f.style,h=f.nodeName,i=this.renderer,j=this.symbolName,k,l=this.shadows,m,p=this.attrSetters,q=this;ea(a)&&u(b)&&(c=a,a={},a[c]=b);if(ea(a))c=a,q=c==="strokeWidth"||c==="stroke-width"?
78 this.strokeweight:this[c];else for(c in a)if(d=a[c],m=!1,e=p[c]&&p[c].call(this,d,c),e!==!1&&d!==null){e!==w&&(d=e);if(j&&/^(x|y|r|start|end|width|height|innerR|anchorX|anchorY)/.test(c))k||(this.symbolAttr(a),k=!0),m=!0;else if(c==="d"){d=d||[];this.d=d.join(" ");f.path=d=this.pathToVML(d);if(l)for(e=l.length;e--;)l[e].path=l[e].cutOff?this.cutOffPath(d,l[e].cutOff):d;m=!0}else if(c==="visibility"){if(l)for(e=l.length;e--;)l[e].style[c]=d;h==="DIV"&&(d=d==="hidden"?"-999em":0,fb||(g[c]=d?"visible":
79 "hidden"),c="top");g[c]=d;m=!0}else if(c==="zIndex")d&&(g[c]=d),m=!0;else if(qa(c,["x","y","width","height"])!==-1)this[c]=d,c==="x"||c==="y"?c={x:"left",y:"top"}[c]:d=s(0,d),this.updateClipping?(this[c]=d,this.updateClipping()):g[c]=d,m=!0;else if(c==="class"&&h==="DIV")f.className=d;else if(c==="stroke")d=i.color(d,f,c),c="strokecolor";else if(c==="stroke-width"||c==="strokeWidth")f.stroked=d?!0:!1,c="strokeweight",this[c]=d,sa(d)&&(d+="px");else if(c==="dashstyle")(f.getElementsByTagName("stroke")[0]||
80 U(i.prepVML(["<stroke/>"]),null,null,f))[c]=d||"solid",this.dashstyle=d,m=!0;else if(c==="fill")if(h==="SPAN")g.color=d;else{if(h!=="IMG")f.filled=d!==S?!0:!1,d=i.color(d,f,c,this),c="fillcolor"}else if(c==="opacity")m=!0;else if(h==="shape"&&c==="rotation")this[c]=f.style[c]=d,f.style.left=-t(ca(d*Ua)+1)+"px",f.style.top=t(V(d*Ua))+"px";else if(c==="translateX"||c==="translateY"||c==="rotation")this[c]=d,this.updateTransform(),m=!0;else if(c==="text")this.bBox=null,f.innerHTML=d,m=!0;m||(fb?f[c]=
81 d:v(f,c,d))}return q},clip:function(a){var b=this,c;a?(c=a.members,ga(c,b),c.push(b),b.destroyClip=function(){ga(c,b)},a=a.getCSS(b)):(b.destroyClip&&b.destroyClip(),a={clip:fb?"inherit":"rect(auto)"});return b.css(a)},css:wa.prototype.htmlCss,safeRemoveChild:function(a){a.parentNode&&Ta(a)},destroy:function(){this.destroyClip&&this.destroyClip();return wa.prototype.destroy.apply(this)},on:function(a,b){this.element["on"+a]=function(){var a=O.event;a.target=a.srcElement;b(a)};return this},cutOffPath:function(a,
82 b){var c,a=a.split(/[ ,]/);c=a.length;if(c===9||c===11)a[c-4]=a[c-2]=C(a[c-2])-10*b;return a.join(" ")},shadow:function(a,b,c){var d=[],e,f=this.element,g=this.renderer,h,i=f.style,j,k=f.path,l,m,p,q;k&&typeof k.value!=="string"&&(k="x");m=k;if(a){p=o(a.width,3);q=(a.opacity||0.15)/p;for(e=1;e<=3;e++){l=p*2+1-2*e;c&&(m=this.cutOffPath(k.value,l+0.5));j=['<shape isShadow="true" strokeweight="',l,'" filled="false" path="',m,'" coordsize="10 10" style="',f.style.cssText,'" />'];h=U(g.prepVML(j),null,
83 {left:C(i.left)+o(a.offsetX,1),top:C(i.top)+o(a.offsetY,1)});if(c)h.cutOff=l+1;j=['<stroke color="',a.color||"black",'" opacity="',q*e,'"/>'];U(g.prepVML(j),null,null,h);b?b.element.appendChild(h):f.parentNode.insertBefore(h,f);d.push(h)}this.shadows=d}return this}};F=ha(wa,F);var ma={Element:F,isIE8:oa.indexOf("MSIE 8.0")>-1,init:function(a,b,c){var d,e;this.alignedObjects=[];d=this.createElement(Ea);e=d.element;e.style.position="relative";a.appendChild(d.element);this.isVML=!0;this.box=e;this.boxWrapper=
84 d;this.setSize(b,c,!1);y.namespaces.hcv||(y.namespaces.add("hcv","urn:schemas-microsoft-com:vml"),(y.styleSheets.length?y.styleSheets[0]:y.createStyleSheet()).cssText+="hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke{ behavior:url(#default#VML); display: inline-block; } ")},isHidden:function(){return!this.box.offsetWidth},clipRect:function(a,b,c,d){var e=this.createElement(),f=T(a);return r(e,{members:[],left:(f?a.x:a)+1,top:(f?a.y:b)+1,width:(f?a.width:c)-1,height:(f?a.height:d)-1,getCSS:function(a){var b=
85 a.element,c=b.nodeName,a=a.inverted,d=this.top-(c==="shape"?b.offsetTop:0),e=this.left,b=e+this.width,f=d+this.height,d={clip:"rect("+t(a?e:d)+"px,"+t(a?f:b)+"px,"+t(a?b:f)+"px,"+t(a?d:e)+"px)"};!a&&fb&&c==="DIV"&&r(d,{width:b+"px",height:f+"px"});return d},updateClipping:function(){n(e.members,function(a){a.css(e.getCSS(a))})}})},color:function(a,b,c,d){var e=this,f,g=/^rgba/,h,i,j=S;a&&a.linearGradient?i="gradient":a&&a.radialGradient&&(i="pattern");if(i){var k,l,m=a.linearGradient||a.radialGradient,
86 p,q,o,A,L,s="",a=a.stops,u,t=[],w=function(){h=['<fill colors="'+t.join(",")+'" opacity="',o,'" o:opacity2="',q,'" type="',i,'" ',s,'focus="100%" method="any" />'];U(e.prepVML(h),null,null,b)};p=a[0];u=a[a.length-1];p[0]>0&&a.unshift([0,p[1]]);u[0]<1&&a.push([1,u[1]]);n(a,function(a,b){g.test(a[1])?(f=ra(a[1]),k=f.get("rgb"),l=f.get("a")):(k=a[1],l=1);t.push(a[0]*100+"% "+k);b?(o=l,A=k):(q=l,L=k)});if(c==="fill")if(i==="gradient")c=m.x1||m[0]||0,a=m.y1||m[1]||0,p=m.x2||m[2]||0,m=m.y2||m[3]||0,s='angle="'+
87 (90-R.atan((m-a)/(p-c))*180/ya)+'"',w();else{var j=m.r,r=j*2,E=j*2,H=m.cx,B=m.cy,x=b.radialReference,v,j=function(){x&&(v=d.getBBox(),H+=(x[0]-v.x)/v.width-0.5,B+=(x[1]-v.y)/v.height-0.5,r*=x[2]/v.width,E*=x[2]/v.height);s='src="'+M.global.VMLRadialGradientURL+'" size="'+r+","+E+'" origin="0.5,0.5" position="'+H+","+B+'" color2="'+L+'" ';w()};d.added?j():J(d,"add",j);j=A}else j=k}else if(g.test(a)&&b.tagName!=="IMG")f=ra(a),h=["<",c,' opacity="',f.get("a"),'"/>'],U(this.prepVML(h),null,null,b),j=
88 f.get("rgb");else{j=b.getElementsByTagName(c);if(j.length)j[0].opacity=1,j[0].type="solid";j=a}return j},prepVML:function(a){var b=this.isIE8,a=a.join("");b?(a=a.replace("/>",' xmlns="urn:schemas-microsoft-com:vml" />'),a=a.indexOf('style="')===-1?a.replace("/>",' style="display:inline-block;behavior:url(#default#VML);" />'):a.replace('style="','style="display:inline-block;behavior:url(#default#VML);')):a=a.replace("<","<hcv:");return a},text:Ha.prototype.html,path:function(a){var b={coordsize:"10 10"};
89 Ia(a)?b.d=a:T(a)&&r(b,a);return this.createElement("shape").attr(b)},circle:function(a,b,c){var d=this.symbol("circle");if(T(a))c=a.r,b=a.y,a=a.x;d.isCircle=!0;d.r=c;return d.attr({x:a,y:b})},g:function(a){var b;a&&(b={className:"highcharts-"+a,"class":"highcharts-"+a});return this.createElement(Ea).attr(b)},image:function(a,b,c,d,e){var f=this.createElement("img").attr({src:a});arguments.length>1&&f.attr({x:b,y:c,width:d,height:e});return f},rect:function(a,b,c,d,e,f){var g=this.symbol("rect");g.r=
90 T(a)?a.r:e;return g.attr(T(a)?a:g.crisp(f,a,b,s(c,0),s(d,0)))},invertChild:function(a,b){var c=b.style;K(a,{flip:"x",left:C(c.width)-1,top:C(c.height)-1,rotation:-90})},symbols:{arc:function(a,b,c,d,e){var f=e.start,g=e.end,h=e.r||c||d,c=e.innerR,d=V(f),i=ca(f),j=V(g),k=ca(g);if(g-f===0)return["x"];f=["wa",a-h,b-h,a+h,b+h,a+h*d,b+h*i,a+h*j,b+h*k];e.open&&!c&&f.push("e","M",a,b);f.push("at",a-c,b-c,a+c,b+c,a+c*j,b+c*k,a+c*d,b+c*i,"x","e");f.isArc=!0;return f},circle:function(a,b,c,d,e){e&&(c=d=2*e.r);
91 e&&e.isCircle&&(a-=c/2,b-=d/2);return["wa",a,b,a+c,b+d,a+c,b+d/2,a+c,b+d/2,"e"]},rect:function(a,b,c,d,e){var f=a+c,g=b+d,h;!u(e)||!e.r?f=Ha.prototype.symbols.square.apply(0,arguments):(h=I(e.r,c,d),f=["M",a+h,b,"L",f-h,b,"wa",f-2*h,b,f,b+2*h,f-h,b,f,b+h,"L",f,g-h,"wa",f-2*h,g-2*h,f,g,f,g-h,f-h,g,"L",a+h,g,"wa",a,g-2*h,a+2*h,g,a+h,g,a,g-h,"L",a,b+h,"wa",a,b,a+2*h,b+2*h,a,b+h,a+h,b,"x","e"]);return f}}};Highcharts.VMLRenderer=F=function(){this.init.apply(this,arguments)};F.prototype=x(Ha.prototype,
92 ma);Va=F}var Tb;if($)Highcharts.CanVGRenderer=F=function(){za="http://www.w3.org/1999/xhtml"},F.prototype.symbols={},Tb=function(){function a(){var a=b.length,d;for(d=0;d<a;d++)b[d]();b=[]}var b=[];return{push:function(c,d){b.length===0&&Vb(d,a);b.push(c)}}}(),Va=F;Ma.prototype={addLabel:function(){var a=this.axis,b=a.options,c=a.chart,d=a.horiz,e=a.categories,f=a.series[0]&&a.series[0].names,g=this.pos,h=b.labels,i=a.tickPositions,d=d&&e&&!h.step&&!h.staggerLines&&!h.rotation&&c.plotWidth/i.length||
93 !d&&(c.margin[3]||c.chartWidth*0.33),j=g===i[0],k=g===i[i.length-1],l,f=e?o(e[g],f&&f[g],g):g,e=this.label,m=i.info;a.isDatetimeAxis&&m&&(l=b.dateTimeLabelFormats[m.higherRanks[g]||m.unitName]);this.isFirst=j;this.isLast=k;b=a.labelFormatter.call({axis:a,chart:c,isFirst:j,isLast:k,dateTimeLabelFormat:l,value:a.isLog?ia(fa(f)):f});g=d&&{width:s(1,t(d-2*(h.padding||10)))+"px"};g=r(g,h.style);if(u(e))e&&e.attr({text:b}).css(g);else{l={align:a.labelAlign};if(sa(h.rotation))l.rotation=h.rotation;if(d&&
94 h.ellipsis)l._clipHeight=a.len/i.length;this.label=u(b)&&h.enabled?c.renderer.text(b,0,0,h.useHTML).attr(l).css(g).add(a.labelGroup):null}},getLabelSize:function(){var a=this.label,b=this.axis;return a?(this.labelBBox=a.getBBox())[b.horiz?"height":"width"]:0},getLabelSides:function(){var a=this.axis,b=this.labelBBox.width,a=b*{left:0,center:0.5,right:1}[a.labelAlign]-a.options.labels.x;return[-a,b-a]},handleOverflow:function(a,b){var c=!0,d=this.axis,e=d.chart,f=this.isFirst,g=this.isLast,h=b.x,i=
95 d.reversed,j=d.tickPositions;if(f||g){var k=this.getLabelSides(),l=k[0],k=k[1],e=e.plotLeft,m=e+d.len,j=(d=d.ticks[j[a+(f?1:-1)]])&&d.label.xy&&d.label.xy.x+d.getLabelSides()[f?0:1];f&&!i||g&&i?h+l<e&&(h=e-l,d&&h+k>j&&(c=!1)):h+k>m&&(h=m-k,d&&h+l<j&&(c=!1));b.x=h}return c},getPosition:function(a,b,c,d){var e=this.axis,f=e.chart,g=d&&f.oldChartHeight||f.chartHeight;return{x:a?e.translate(b+c,null,null,d)+e.transB:e.left+e.offset+(e.opposite?(d&&f.oldChartWidth||f.chartWidth)-e.right-e.left:0),y:a?
96 g-e.bottom+e.offset-(e.opposite?e.height:0):g-e.translate(b+c,null,null,d)-e.transB}},getLabelPosition:function(a,b,c,d,e,f,g,h){var i=this.axis,j=i.transA,k=i.reversed,l=i.staggerLines,m=i.chart.renderer.fontMetrics(e.style.fontSize).b,p=e.rotation,a=a+e.x-(f&&d?f*j*(k?-1:1):0),b=b+e.y-(f&&!d?f*j*(k?1:-1):0);p&&i.side===2&&(b-=m-m*V(p*Ua));!u(e.y)&&!p&&(b+=m-c.getBBox().height/2);l&&(b+=g/(h||1)%l*(i.labelOffset/l));return{x:a,y:b}},getMarkPath:function(a,b,c,d,e,f){return f.crispLine(["M",a,b,"L",
97 a+(e?0:-c),b+(e?c:0)],d)},render:function(a,b,c){var d=this.axis,e=d.options,f=d.chart.renderer,g=d.horiz,h=this.type,i=this.label,j=this.pos,k=e.labels,l=this.gridLine,m=h?h+"Grid":"grid",p=h?h+"Tick":"tick",q=e[m+"LineWidth"],n=e[m+"LineColor"],A=e[m+"LineDashStyle"],s=e[p+"Length"],m=e[p+"Width"]||0,u=e[p+"Color"],t=e[p+"Position"],p=this.mark,r=k.step,v=!0,x=d.tickmarkOffset,E=this.getPosition(g,j,x,b),H=E.x,E=E.y,B=g&&H===d.pos+d.len||!g&&E===d.pos?-1:1,C=d.staggerLines;this.isActive=!0;if(q){j=
98 d.getPlotLinePath(j+x,q*B,b,!0);if(l===w){l={stroke:n,"stroke-width":q};if(A)l.dashstyle=A;if(!h)l.zIndex=1;if(b)l.opacity=0;this.gridLine=l=q?f.path(j).attr(l).add(d.gridGroup):null}if(!b&&l&&j)l[this.isNew?"attr":"animate"]({d:j,opacity:c})}if(m&&s)t==="inside"&&(s=-s),d.opposite&&(s=-s),b=this.getMarkPath(H,E,s,m*B,g,f),p?p.animate({d:b,opacity:c}):this.mark=f.path(b).attr({stroke:u,"stroke-width":m,opacity:c}).add(d.axisGroup);if(i&&!isNaN(H))i.xy=E=this.getLabelPosition(H,E,i,g,k,x,a,r),this.isFirst&&
99 !this.isLast&&!o(e.showFirstLabel,1)||this.isLast&&!this.isFirst&&!o(e.showLastLabel,1)?v=!1:!C&&g&&k.overflow==="justify"&&!this.handleOverflow(a,E)&&(v=!1),r&&a%r&&(v=!1),v&&!isNaN(E.y)?(E.opacity=c,i[this.isNew?"attr":"animate"](E),this.isNew=!1):i.attr("y",-9999)},destroy:function(){Ka(this,this.axis)}};vb.prototype={render:function(){var a=this,b=a.axis,c=b.horiz,d=(b.pointRange||0)/2,e=a.options,f=e.label,g=a.label,h=e.width,i=e.to,j=e.from,k=u(j)&&u(i),l=e.value,m=e.dashStyle,p=a.svgElem,q=
100 [],n,A=e.color,L=e.zIndex,t=e.events,w=b.chart.renderer;b.isLog&&(j=na(j),i=na(i),l=na(l));if(h){if(q=b.getPlotLinePath(l,h),d={stroke:A,"stroke-width":h},m)d.dashstyle=m}else if(k){if(j=s(j,b.min-d),i=I(i,b.max+d),q=b.getPlotBandPath(j,i,e),d={fill:A},e.borderWidth)d.stroke=e.borderColor,d["stroke-width"]=e.borderWidth}else return;if(u(L))d.zIndex=L;if(p)q?p.animate({d:q},null,p.onGetPath):(p.hide(),p.onGetPath=function(){p.show()});else if(q&&q.length&&(a.svgElem=p=w.path(q).attr(d).add(),t))for(n in e=
101 function(b){p.on(b,function(c){t[b].apply(a,[c])})},t)e(n);if(f&&u(f.text)&&q&&q.length&&b.width>0&&b.height>0){f=x({align:c&&k&&"center",x:c?!k&&4:10,verticalAlign:!c&&k&&"middle",y:c?k?16:10:k?6:-4,rotation:c&&!k&&90},f);if(!g)a.label=g=w.text(f.text,0,0,f.useHTML).attr({align:f.textAlign||f.align,rotation:f.rotation,zIndex:L}).css(f.style).add();b=[q[1],q[4],o(q[6],q[1])];q=[q[2],q[5],o(q[7],q[2])];c=Ja(b);k=Ja(q);g.align(f,!1,{x:c,y:k,width:va(b)-c,height:va(q)-k});g.show()}else g&&g.hide();return a},
102 destroy:function(){ga(this.axis.plotLinesAndBands,this);delete this.axis;Ka(this)}};Mb.prototype={destroy:function(){Ka(this,this.axis)},render:function(a){var b=this.options,c=b.format,c=c?Ca(c,this):b.formatter.call(this);this.label?this.label.attr({text:c,visibility:"hidden"}):this.label=this.axis.chart.renderer.text(c,0,0,b.useHTML).css(b.style).attr({align:this.textAlign,rotation:b.rotation,visibility:"hidden"}).add(a)},setOffset:function(a,b){var c=this.axis,d=c.chart,e=d.inverted,f=this.isNegative,
103 g=c.translate(this.percent?100:this.total,0,0,0,1),c=c.translate(0),c=N(g-c),h=d.xAxis[0].translate(this.x)+a,i=d.plotHeight,f={x:e?f?g:g-c:h,y:e?i-h-b:f?i-g-c:i-g,width:e?c:b,height:e?b:c};if(e=this.label)e.align(this.alignOptions,null,f),f=e.alignAttr,e.attr({visibility:this.options.crop===!1||d.isInsidePlot(f.x,f.y)?Z?"inherit":"visible":"hidden"})}};db.prototype={defaultOptions:{dateTimeLabelFormats:{millisecond:"%H:%M:%S.%L",second:"%H:%M:%S",minute:"%H:%M",hour:"%H:%M",day:"%e. %b",week:"%e. %b",
104 month:"%b '%y",year:"%Y"},endOnTick:!1,gridLineColor:"#C0C0C0",labels:G,lineColor:"#C0D0E0",lineWidth:1,minPadding:0.01,maxPadding:0.01,minorGridLineColor:"#E0E0E0",minorGridLineWidth:1,minorTickColor:"#A0A0A0",minorTickLength:2,minorTickPosition:"outside",startOfWeek:1,startOnTick:!1,tickColor:"#C0D0E0",tickLength:5,tickmarkPlacement:"between",tickPixelInterval:100,tickPosition:"outside",tickWidth:1,title:{align:"middle",style:{color:"#4d759e",fontWeight:"bold"}},type:"linear"},defaultYAxisOptions:{endOnTick:!0,
105 gridLineWidth:1,tickPixelInterval:72,showLastLabel:!0,labels:{x:-8,y:3},lineWidth:0,maxPadding:0.05,minPadding:0.05,startOnTick:!0,tickWidth:0,title:{rotation:270,text:"Values"},stackLabels:{enabled:!1,formatter:function(){return Aa(this.total,-1)},style:G.style}},defaultLeftAxisOptions:{labels:{x:-8,y:null},title:{rotation:270}},defaultRightAxisOptions:{labels:{x:8,y:null},title:{rotation:90}},defaultBottomAxisOptions:{labels:{x:0,y:14},title:{rotation:0}},defaultTopAxisOptions:{labels:{x:0,y:-5},
106 title:{rotation:0}},init:function(a,b){var c=b.isX;this.horiz=a.inverted?!c:c;this.xOrY=(this.isXAxis=c)?"x":"y";this.opposite=b.opposite;this.side=this.horiz?this.opposite?0:2:this.opposite?1:3;this.setOptions(b);var d=this.options,e=d.type;this.labelFormatter=d.labels.formatter||this.defaultLabelFormatter;this.userOptions=b;this.minPixelPadding=0;this.chart=a;this.reversed=d.reversed;this.zoomEnabled=d.zoomEnabled!==!1;this.categories=d.categories||e==="category";this.isLog=e==="logarithmic";this.isDatetimeAxis=
107 e==="datetime";this.isLinked=u(d.linkedTo);this.tickmarkOffset=this.categories&&d.tickmarkPlacement==="between"?0.5:0;this.ticks={};this.minorTicks={};this.plotLinesAndBands=[];this.alternateBands={};this.len=0;this.minRange=this.userMinRange=d.minRange||d.maxZoom;this.range=d.range;this.offset=d.offset||0;this.stacks={};this.oldStacks={};this.stackExtremes={};this.min=this.max=null;var f,d=this.options.events;qa(this,a.axes)===-1&&(a.axes.push(this),a[c?"xAxis":"yAxis"].push(this));this.series=this.series||
108 [];if(a.inverted&&c&&this.reversed===w)this.reversed=!0;this.removePlotLine=this.removePlotBand=this.removePlotBandOrLine;for(f in d)J(this,f,d[f]);if(this.isLog)this.val2lin=na,this.lin2val=fa},setOptions:function(a){this.options=x(this.defaultOptions,this.isXAxis?{}:this.defaultYAxisOptions,[this.defaultTopAxisOptions,this.defaultRightAxisOptions,this.defaultBottomAxisOptions,this.defaultLeftAxisOptions][this.side],x(M[this.isXAxis?"xAxis":"yAxis"],a))},update:function(a,b){var c=this.chart,a=c.options[this.xOrY+
109 "Axis"][this.options.index]=x(this.userOptions,a);this.destroy(!0);this._addedPlotLB=this.userMin=this.userMax=w;this.init(c,r(a,{events:w}));c.isDirtyBox=!0;o(b,!0)&&c.redraw()},remove:function(a){var b=this.chart,c=this.xOrY+"Axis";n(this.series,function(a){a.remove(!1)});ga(b.axes,this);ga(b[c],this);b.options[c].splice(this.options.index,1);n(b[c],function(a,b){a.options.index=b});this.destroy();b.isDirtyBox=!0;o(a,!0)&&b.redraw()},defaultLabelFormatter:function(){var a=this.axis,b=this.value,
110 c=a.categories,d=this.dateTimeLabelFormat,e=M.lang.numericSymbols,f=e&&e.length,g,h=a.options.labels.format,a=a.isLog?b:a.tickInterval;if(h)g=Ca(h,this);else if(c)g=b;else if(d)g=Xa(d,b);else if(f&&a>=1E3)for(;f--&&g===w;)c=Math.pow(1E3,f+1),a>=c&&e[f]!==null&&(g=Aa(b/c,-1)+e[f]);g===w&&(g=b>=1E3?Aa(b,0):Aa(b,-1));return g},getSeriesExtremes:function(){var a=this,b=a.chart;a.hasVisibleSeries=!1;a.dataMin=a.dataMax=null;a.stackExtremes={};a.buildStacks();n(a.series,function(c){if(c.visible||!b.options.chart.ignoreHiddenSeries){var d;
111 d=c.options.threshold;var e;a.hasVisibleSeries=!0;a.isLog&&d<=0&&(d=null);if(a.isXAxis){if(d=c.xData,d.length)a.dataMin=I(o(a.dataMin,d[0]),Ja(d)),a.dataMax=s(o(a.dataMax,d[0]),va(d))}else{c.getExtremes();e=c.dataMax;c=c.dataMin;if(u(c)&&u(e))a.dataMin=I(o(a.dataMin,c),c),a.dataMax=s(o(a.dataMax,e),e);if(u(d))if(a.dataMin>=d)a.dataMin=d,a.ignoreMinPadding=!0;else if(a.dataMax<d)a.dataMax=d,a.ignoreMaxPadding=!0}}})},translate:function(a,b,c,d,e,f){var g=this.len,h=1,i=0,j=d?this.oldTransA:this.transA,
112 d=d?this.oldMin:this.min,k=this.minPixelPadding,e=(this.options.ordinal||this.isLog&&e)&&this.lin2val;if(!j)j=this.transA;c&&(h*=-1,i=g);this.reversed&&(h*=-1,i-=h*g);b?(a=a*h+i,a-=k,a=a/j+d,e&&(a=this.lin2val(a))):(e&&(a=this.val2lin(a)),f==="between"&&(f=0.5),a=h*(a-d)*j+i+h*k+(sa(f)?j*f*this.pointRange:0));return a},toPixels:function(a,b){return this.translate(a,!1,!this.horiz,null,!0)+(b?0:this.pos)},toValue:function(a,b){return this.translate(a-(b?0:this.pos),!0,!this.horiz,null,!0)},getPlotLinePath:function(a,
113 b,c,d){var e=this.chart,f=this.left,g=this.top,h,i,j,a=this.translate(a,null,null,c),k=c&&e.oldChartHeight||e.chartHeight,l=c&&e.oldChartWidth||e.chartWidth,m;h=this.transB;c=i=t(a+h);h=j=t(k-a-h);if(isNaN(a))m=!0;else if(this.horiz){if(h=g,j=k-this.bottom,c<f||c>f+this.width)m=!0}else if(c=f,i=l-this.right,h<g||h>g+this.height)m=!0;return m&&!d?null:e.renderer.crispLine(["M",c,h,"L",i,j],b||0)},getPlotBandPath:function(a,b){var c=this.getPlotLinePath(b),d=this.getPlotLinePath(a);d&&c?d.push(c[4],
114 c[5],c[1],c[2]):d=null;return d},getLinearTickPositions:function(a,b,c){for(var d,b=ia(P(b/a)*a),c=ia(xa(c/a)*a),e=[];b<=c;){e.push(b);b=ia(b+a);if(b===d)break;d=b}return e},getLogTickPositions:function(a,b,c,d){var e=this.options,f=this.len,g=[];if(!d)this._minorAutoInterval=null;if(a>=0.5)a=t(a),g=this.getLinearTickPositions(a,b,c);else if(a>=0.08)for(var f=P(b),h,i,j,k,l,e=a>0.3?[1,2,4]:a>0.15?[1,2,4,6,8]:[1,2,3,4,5,6,7,8,9];f<c+1&&!l;f++){i=e.length;for(h=0;h<i&&!l;h++)j=na(fa(f)*e[h]),j>b&&(!d||
115 k<=c)&&g.push(k),k>c&&(l=!0),k=j}else if(b=fa(b),c=fa(c),a=e[d?"minorTickInterval":"tickInterval"],a=o(a==="auto"?null:a,this._minorAutoInterval,(c-b)*(e.tickPixelInterval/(d?5:1))/((d?f/this.tickPositions.length:f)||1)),a=ob(a,null,nb(a)),g=Na(this.getLinearTickPositions(a,b,c),na),!d)this._minorAutoInterval=a/5;if(!d)this.tickInterval=a;return g},getMinorTickPositions:function(){var a=this.options,b=this.tickPositions,c=this.minorTickInterval,d=[],e;if(this.isLog){e=b.length;for(a=1;a<e;a++)d=d.concat(this.getLogTickPositions(c,
116 b[a-1],b[a],!0))}else if(this.isDatetimeAxis&&a.minorTickInterval==="auto")d=d.concat(Eb(Cb(c),this.min,this.max,a.startOfWeek)),d[0]<this.min&&d.shift();else for(b=this.min+(b[0]-this.min)%c;b<=this.max;b+=c)d.push(b);return d},adjustForMinRange:function(){var a=this.options,b=this.min,c=this.max,d,e=this.dataMax-this.dataMin>=this.minRange,f,g,h,i,j;if(this.isXAxis&&this.minRange===w&&!this.isLog)u(a.min)||u(a.max)?this.minRange=null:(n(this.series,function(a){i=a.xData;for(g=j=a.xIncrement?1:i.length-
117 1;g>0;g--)if(h=i[g]-i[g-1],f===w||h<f)f=h}),this.minRange=I(f*5,this.dataMax-this.dataMin));if(c-b<this.minRange){var k=this.minRange;d=(k-c+b)/2;d=[b-d,o(a.min,b-d)];if(e)d[2]=this.dataMin;b=va(d);c=[b+k,o(a.max,b+k)];if(e)c[2]=this.dataMax;c=Ja(c);c-b<k&&(d[0]=c-k,d[1]=o(a.min,c-k),b=va(d))}this.min=b;this.max=c},setAxisTranslation:function(a){var b=this.max-this.min,c=0,d,e=0,f=0,g=this.linkedParent,h=this.transA;if(this.isXAxis)g?(e=g.minPointOffset,f=g.pointRangePadding):n(this.series,function(a){var g=
118 a.pointRange,h=a.options.pointPlacement,l=a.closestPointRange;g>b&&(g=0);c=s(c,g);e=s(e,ea(h)?0:g/2);f=s(f,h==="on"?0:g);!a.noSharedTooltip&&u(l)&&(d=u(d)?I(d,l):l)}),g=this.ordinalSlope&&d?this.ordinalSlope/d:1,this.minPointOffset=e*=g,this.pointRangePadding=f*=g,this.pointRange=I(c,b),this.closestPointRange=d;if(a)this.oldTransA=h;this.translationSlope=this.transA=h=this.len/(b+f||1);this.transB=this.horiz?this.left:this.bottom;this.minPixelPadding=h*e},setTickPositions:function(a){var b=this,c=
119 b.chart,d=b.options,e=b.isLog,f=b.isDatetimeAxis,g=b.isXAxis,h=b.isLinked,i=b.options.tickPositioner,j=d.maxPadding,k=d.minPadding,l=d.tickInterval,m=d.minTickInterval,p=d.tickPixelInterval,q,ba=b.categories;h?(b.linkedParent=c[g?"xAxis":"yAxis"][d.linkedTo],c=b.linkedParent.getExtremes(),b.min=o(c.min,c.dataMin),b.max=o(c.max,c.dataMax),d.type!==b.linkedParent.options.type&&ka(11,1)):(b.min=o(b.userMin,d.min,b.dataMin),b.max=o(b.userMax,d.max,b.dataMax));if(e)!a&&I(b.min,o(b.dataMin,b.min))<=0&&
120 ka(10,1),b.min=ia(na(b.min)),b.max=ia(na(b.max));if(b.range&&(b.userMin=b.min=s(b.min,b.max-b.range),b.userMax=b.max,a))b.range=null;b.beforePadding&&b.beforePadding();b.adjustForMinRange();if(!ba&&!b.usePercentage&&!h&&u(b.min)&&u(b.max)&&(c=b.max-b.min)){if(!u(d.min)&&!u(b.userMin)&&k&&(b.dataMin<0||!b.ignoreMinPadding))b.min-=c*k;if(!u(d.max)&&!u(b.userMax)&&j&&(b.dataMax>0||!b.ignoreMaxPadding))b.max+=c*j}b.min===b.max||b.min===void 0||b.max===void 0?b.tickInterval=1:h&&!l&&p===b.linkedParent.options.tickPixelInterval?
121 b.tickInterval=b.linkedParent.tickInterval:(b.tickInterval=o(l,ba?1:(b.max-b.min)*p/s(b.len,p)),!u(l)&&b.len<p&&!this.isRadial&&(q=!0,b.tickInterval/=4));g&&!a&&n(b.series,function(a){a.processData(b.min!==b.oldMin||b.max!==b.oldMax)});b.setAxisTranslation(!0);b.beforeSetTickPositions&&b.beforeSetTickPositions();if(b.postProcessTickInterval)b.tickInterval=b.postProcessTickInterval(b.tickInterval);if(b.pointRange)b.tickInterval=s(b.pointRange,b.tickInterval);if(!l&&b.tickInterval<m)b.tickInterval=
122 m;if(!f&&!e&&!l)b.tickInterval=ob(b.tickInterval,null,nb(b.tickInterval),d);b.minorTickInterval=d.minorTickInterval==="auto"&&b.tickInterval?b.tickInterval/5:d.minorTickInterval;b.tickPositions=a=d.tickPositions?[].concat(d.tickPositions):i&&i.apply(b,[b.min,b.max]);if(!a)!b.ordinalPositions&&(b.max-b.min)/b.tickInterval>s(2*b.len,200)&&ka(19,!0),a=f?(b.getNonLinearTimeTicks||Eb)(Cb(b.tickInterval,d.units),b.min,b.max,d.startOfWeek,b.ordinalPositions,b.closestPointRange,!0):e?b.getLogTickPositions(b.tickInterval,
123 b.min,b.max):b.getLinearTickPositions(b.tickInterval,b.min,b.max),q&&a.splice(1,a.length-2),b.tickPositions=a;if(!h)e=a[0],f=a[a.length-1],h=b.minPointOffset||0,d.startOnTick?b.min=e:b.min-h>e&&a.shift(),d.endOnTick?b.max=f:b.max+h<f&&a.pop(),a.length===1&&(b.min-=0.001,b.max+=0.001)},setMaxTicks:function(){var a=this.chart,b=a.maxTicks||{},c=this.tickPositions,d=this._maxTicksKey=[this.xOrY,this.pos,this.len].join("-");if(!this.isLinked&&!this.isDatetimeAxis&&c&&c.length>(b[d]||0)&&this.options.alignTicks!==
124 !1)b[d]=c.length;a.maxTicks=b},adjustTickAmount:function(){var a=this._maxTicksKey,b=this.tickPositions,c=this.chart.maxTicks;if(c&&c[a]&&!this.isDatetimeAxis&&!this.categories&&!this.isLinked&&this.options.alignTicks!==!1){var d=this.tickAmount,e=b.length;this.tickAmount=a=c[a];if(e<a){for(;b.length<a;)b.push(ia(b[b.length-1]+this.tickInterval));this.transA*=(e-1)/(a-1);this.max=b[b.length-1]}if(u(d)&&a!==d)this.isDirty=!0}},setScale:function(){var a=this.stacks,b,c,d,e;this.oldMin=this.min;this.oldMax=
125 this.max;this.oldAxisLength=this.len;this.setAxisSize();e=this.len!==this.oldAxisLength;n(this.series,function(a){if(a.isDirtyData||a.isDirty||a.xAxis.isDirty)d=!0});if(e||d||this.isLinked||this.forceRedraw||this.userMin!==this.oldUserMin||this.userMax!==this.oldUserMax){if(!this.isXAxis)for(b in a)delete a[b];this.forceRedraw=!1;this.getSeriesExtremes();this.setTickPositions();this.oldUserMin=this.userMin;this.oldUserMax=this.userMax;if(!this.isDirty)this.isDirty=e||this.min!==this.oldMin||this.max!==
126 this.oldMax}else if(!this.isXAxis){if(this.oldStacks)a=this.stacks=this.oldStacks;for(b in a)for(c in a[b])a[b][c].cum=a[b][c].total}this.setMaxTicks()},setExtremes:function(a,b,c,d,e){var f=this,g=f.chart,c=o(c,!0),e=r(e,{min:a,max:b});z(f,"setExtremes",e,function(){f.userMin=a;f.userMax=b;f.eventArgs=e;f.isDirtyExtremes=!0;c&&g.redraw(d)})},zoom:function(a,b){this.allowZoomOutside||(u(this.dataMin)&&a<=this.dataMin&&(a=w),u(this.dataMax)&&b>=this.dataMax&&(b=w));this.displayBtn=a!==w||b!==w;this.setExtremes(a,
127 b,!1,w,{trigger:"zoom"});return!0},setAxisSize:function(){var a=this.chart,b=this.options,c=b.offsetLeft||0,d=b.offsetRight||0,e=this.horiz,f,g;this.left=g=o(b.left,a.plotLeft+c);this.top=f=o(b.top,a.plotTop);this.width=c=o(b.width,a.plotWidth-c+d);this.height=b=o(b.height,a.plotHeight);this.bottom=a.chartHeight-b-f;this.right=a.chartWidth-c-g;this.len=s(e?c:b,0);this.pos=e?g:f},getExtremes:function(){var a=this.isLog;return{min:a?ia(fa(this.min)):this.min,max:a?ia(fa(this.max)):this.max,dataMin:this.dataMin,
128 dataMax:this.dataMax,userMin:this.userMin,userMax:this.userMax}},getThreshold:function(a){var b=this.isLog,c=b?fa(this.min):this.min,b=b?fa(this.max):this.max;c>a||a===null?a=c:b<a&&(a=b);return this.translate(a,0,1,0,1)},addPlotBand:function(a){this.addPlotBandOrLine(a,"plotBands")},addPlotLine:function(a){this.addPlotBandOrLine(a,"plotLines")},addPlotBandOrLine:function(a,b){var c=(new vb(this,a)).render(),d=this.userOptions;c&&(b&&(d[b]=d[b]||[],d[b].push(a)),this.plotLinesAndBands.push(c));return c},
129 autoLabelAlign:function(a){a=(o(a,0)-this.side*90+720)%360;return a>15&&a<165?"right":a>195&&a<345?"left":"center"},getOffset:function(){var a=this,b=a.chart,c=b.renderer,d=a.options,e=a.tickPositions,f=a.ticks,g=a.horiz,h=a.side,i=b.inverted?[1,0,3,2][h]:h,j,k=0,l,m=0,p=d.title,q=d.labels,ba=0,A=b.axisOffset,L=b.clipOffset,t=[-1,1,1,-1][h],r,v=1,x=o(q.maxStaggerLines,5),la,E,H,B;a.hasData=j=a.hasVisibleSeries||u(a.min)&&u(a.max)&&!!e;a.showAxis=b=j||o(d.showEmpty,!0);a.staggerLines=a.horiz&&q.staggerLines;
130 if(!a.axisGroup)a.gridGroup=c.g("grid").attr({zIndex:d.gridZIndex||1}).add(),a.axisGroup=c.g("axis").attr({zIndex:d.zIndex||2}).add(),a.labelGroup=c.g("axis-labels").attr({zIndex:q.zIndex||7}).add();if(j||a.isLinked){a.labelAlign=o(q.align||a.autoLabelAlign(q.rotation));n(e,function(b){f[b]?f[b].addLabel():f[b]=new Ma(a,b)});if(a.horiz&&!a.staggerLines&&x&&!q.rotation){for(r=a.reversed?[].concat(e).reverse():e;v<x;){j=[];la=!1;for(q=0;q<r.length;q++)E=r[q],H=(H=f[E].label&&f[E].label.getBBox())?H.width:
131 0,B=q%v,H&&(E=a.translate(E),j[B]!==w&&E<j[B]&&(la=!0),j[B]=E+H);if(la)v++;else break}if(v>1)a.staggerLines=v}n(e,function(b){if(h===0||h===2||{1:"left",3:"right"}[h]===a.labelAlign)ba=s(f[b].getLabelSize(),ba)});if(a.staggerLines)ba*=a.staggerLines,a.labelOffset=ba}else for(r in f)f[r].destroy(),delete f[r];if(p&&p.text&&p.enabled!==!1){if(!a.axisTitle)a.axisTitle=c.text(p.text,0,0,p.useHTML).attr({zIndex:7,rotation:p.rotation||0,align:p.textAlign||{low:"left",middle:"center",high:"right"}[p.align]}).css(p.style).add(a.axisGroup),
132 a.axisTitle.isNew=!0;if(b)k=a.axisTitle.getBBox()[g?"height":"width"],m=o(p.margin,g?5:10),l=p.offset;a.axisTitle[b?"show":"hide"]()}a.offset=t*o(d.offset,A[h]);a.axisTitleMargin=o(l,ba+m+(h!==2&&ba&&t*d.labels[g?"y":"x"]));A[h]=s(A[h],a.axisTitleMargin+k+t*a.offset);L[i]=s(L[i],P(d.lineWidth/2)*2)},getLinePath:function(a){var b=this.chart,c=this.opposite,d=this.offset,e=this.horiz,f=this.left+(c?this.width:0)+d,d=b.chartHeight-this.bottom-(c?this.height:0)+d;c&&(a*=-1);return b.renderer.crispLine(["M",
133 e?this.left:f,e?d:this.top,"L",e?b.chartWidth-this.right:f,e?d:b.chartHeight-this.bottom],a)},getTitlePosition:function(){var a=this.horiz,b=this.left,c=this.top,d=this.len,e=this.options.title,f=a?b:c,g=this.opposite,h=this.offset,i=C(e.style.fontSize||12),d={low:f+(a?0:d),middle:f+d/2,high:f+(a?d:0)}[e.align],b=(a?c+this.height:b)+(a?1:-1)*(g?-1:1)*this.axisTitleMargin+(this.side===2?i:0);return{x:a?d:b+(g?this.width:0)+h+(e.x||0),y:a?b-(g?this.height:0)+h:d+(e.y||0)}},render:function(){var a=this,
134 b=a.chart,c=b.renderer,d=a.options,e=a.isLog,f=a.isLinked,g=a.tickPositions,h=a.axisTitle,i=a.stacks,j=a.ticks,k=a.minorTicks,l=a.alternateBands,m=d.stackLabels,p=d.alternateGridColor,q=a.tickmarkOffset,o=d.lineWidth,A,s=b.hasRendered&&u(a.oldMin)&&!isNaN(a.oldMin);A=a.hasData;var t=a.showAxis,r,v;n([j,k,l],function(a){for(var b in a)a[b].isActive=!1});if(A||f)if(a.minorTickInterval&&!a.categories&&n(a.getMinorTickPositions(),function(b){k[b]||(k[b]=new Ma(a,b,"minor"));s&&k[b].isNew&&k[b].render(null,
135 !0);k[b].render(null,!1,1)}),g.length&&(n(g.slice(1).concat([g[0]]),function(b,c){c=c===g.length-1?0:c+1;if(!f||b>=a.min&&b<=a.max)j[b]||(j[b]=new Ma(a,b)),s&&j[b].isNew&&j[b].render(c,!0),j[b].render(c,!1,1)}),q&&a.min===0&&(j[-1]||(j[-1]=new Ma(a,-1,null,!0)),j[-1].render(-1))),p&&n(g,function(b,c){if(c%2===0&&b<a.max)l[b]||(l[b]=new vb(a)),r=b+q,v=g[c+1]!==w?g[c+1]+q:a.max,l[b].options={from:e?fa(r):r,to:e?fa(v):v,color:p},l[b].render(),l[b].isActive=!0}),!a._addedPlotLB)n((d.plotLines||[]).concat(d.plotBands||
136 []),function(b){a.addPlotBandOrLine(b)}),a._addedPlotLB=!0;n([j,k,l],function(a){var c,d,e=[],f=Fa?Fa.duration||500:0,g=function(){for(d=e.length;d--;)a[e[d]]&&!a[e[d]].isActive&&(a[e[d]].destroy(),delete a[e[d]])};for(c in a)if(!a[c].isActive)a[c].render(c,!1,0),a[c].isActive=!1,e.push(c);a===l||!b.hasRendered||!f?g():f&&setTimeout(g,f)});if(o)A=a.getLinePath(o),a.axisLine?a.axisLine.animate({d:A}):a.axisLine=c.path(A).attr({stroke:d.lineColor,"stroke-width":o,zIndex:7}).add(a.axisGroup),a.axisLine[t?
137 "show":"hide"]();if(h&&t)h[h.isNew?"attr":"animate"](a.getTitlePosition()),h.isNew=!1;if(m&&m.enabled){var x,la,d=a.stackTotalGroup;if(!d)a.stackTotalGroup=d=c.g("stack-labels").attr({visibility:"visible",zIndex:6}).add();d.translate(b.plotLeft,b.plotTop);for(x in i)for(la in c=i[x],c)c[la].render(d)}a.isDirty=!1},removePlotBandOrLine:function(a){for(var b=this.plotLinesAndBands,c=this.options,d=this.userOptions,e=b.length;e--;)b[e].id===a&&b[e].destroy();n([c.plotLines||[],d.plotLines||[],c.plotBands||
138 [],d.plotBands||[]],function(b){for(e=b.length;e--;)b[e].id===a&&ga(b,b[e])})},setTitle:function(a,b){this.update({title:a},b)},redraw:function(){var a=this.chart.pointer;a.reset&&a.reset(!0);this.render();n(this.plotLinesAndBands,function(a){a.render()});n(this.series,function(a){a.isDirty=!0})},buildStacks:function(){var a=this.series,b=a.length;if(!this.isXAxis){for(;b--;)a[b].setStackedPoints();if(this.usePercentage)for(b=0;b<a.length;b++)a[b].setPercentStacks()}},setCategories:function(a,b){this.update({categories:a},
139 b)},destroy:function(a){var b=this,c=b.stacks,d,e=b.plotLinesAndBands;a||aa(b);for(d in c)Ka(c[d]),c[d]=null;n([b.ticks,b.minorTicks,b.alternateBands],function(a){Ka(a)});for(a=e.length;a--;)e[a].destroy();n("stackTotalGroup,axisLine,axisGroup,gridGroup,labelGroup,axisTitle".split(","),function(a){b[a]&&(b[a]=b[a].destroy())})}};wb.prototype={init:function(a,b){var c=b.borderWidth,d=b.style,e=C(d.padding);this.chart=a;this.options=b;this.crosshairs=[];this.now={x:0,y:0};this.isHidden=!0;this.label=
140 a.renderer.label("",0,0,b.shape,null,null,b.useHTML,null,"tooltip").attr({padding:e,fill:b.backgroundColor,"stroke-width":c,r:b.borderRadius,zIndex:8}).css(d).css({padding:0}).add().attr({y:-999});$||this.label.shadow(b.shadow);this.shared=b.shared},destroy:function(){n(this.crosshairs,function(a){a&&a.destroy()});if(this.label)this.label=this.label.destroy();clearTimeout(this.hideTimer);clearTimeout(this.tooltipTimeout)},move:function(a,b,c,d){var e=this,f=e.now,g=e.options.animation!==!1&&!e.isHidden;
141 r(f,{x:g?(2*f.x+a)/3:a,y:g?(f.y+b)/2:b,anchorX:g?(2*f.anchorX+c)/3:c,anchorY:g?(f.anchorY+d)/2:d});e.label.attr(f);if(g&&(N(a-f.x)>1||N(b-f.y)>1))clearTimeout(this.tooltipTimeout),this.tooltipTimeout=setTimeout(function(){e&&e.move(a,b,c,d)},32)},hide:function(){var a=this,b;clearTimeout(this.hideTimer);if(!this.isHidden)b=this.chart.hoverPoints,this.hideTimer=setTimeout(function(){a.label.fadeOut();a.isHidden=!0},o(this.options.hideDelay,500)),b&&n(b,function(a){a.setState()}),this.chart.hoverPoints=
142 null},hideCrosshairs:function(){n(this.crosshairs,function(a){a&&a.hide()})},getAnchor:function(a,b){var c,d=this.chart,e=d.inverted,f=d.plotTop,g=0,h=0,i,a=ja(a);c=a[0].tooltipPos;this.followPointer&&b&&(b.chartX===w&&(b=d.pointer.normalize(b)),c=[b.chartX-d.plotLeft,b.chartY-f]);c||(n(a,function(a){i=a.series.yAxis;g+=a.plotX;h+=(a.plotLow?(a.plotLow+a.plotHigh)/2:a.plotY)+(!e&&i?i.top-f:0)}),g/=a.length,h/=a.length,c=[e?d.plotWidth-h:g,this.shared&&!e&&a.length>1&&b?b.chartY-f:e?d.plotHeight-g:
143 h]);return Na(c,t)},getPosition:function(a,b,c){var d=this.chart,e=d.plotLeft,f=d.plotTop,g=d.plotWidth,h=d.plotHeight,i=o(this.options.distance,12),j=c.plotX,c=c.plotY,d=j+e+(d.inverted?i:-a-i),k=c-b+f+15,l;d<7&&(d=e+s(j,0)+i);d+a>e+g&&(d-=d+a-(e+g),k=c-b+f-i,l=!0);k<f+5&&(k=f+5,l&&c>=k&&c<=k+b&&(k=c+f+i));k+b>f+h&&(k=s(f,f+h-b-i));return{x:d,y:k}},defaultFormatter:function(a){var b=this.points||ja(this),c=b[0].series,d;d=[c.tooltipHeaderFormatter(b[0])];n(b,function(a){c=a.series;d.push(c.tooltipFormatter&&
144 c.tooltipFormatter(a)||a.point.tooltipFormatter(c.tooltipOptions.pointFormat))});d.push(a.options.footerFormat||"");return d.join("")},refresh:function(a,b){var c=this.chart,d=this.label,e=this.options,f,g,h={},i,j=[];i=e.formatter||this.defaultFormatter;var h=c.hoverPoints,k,l=e.crosshairs,m=this.shared;clearTimeout(this.hideTimer);this.followPointer=ja(a)[0].series.tooltipOptions.followPointer;g=this.getAnchor(a,b);f=g[0];g=g[1];m&&(!a.series||!a.series.noSharedTooltip)?(c.hoverPoints=a,h&&n(h,
145 function(a){a.setState()}),n(a,function(a){a.setState("hover");j.push(a.getLabelConfig())}),h={x:a[0].category,y:a[0].y},h.points=j,a=a[0]):h=a.getLabelConfig();i=i.call(h,this);h=a.series;i===!1?this.hide():(this.isHidden&&(Wa(d),d.attr("opacity",1).show()),d.attr({text:i}),k=e.borderColor||a.color||h.color||"#606060",d.attr({stroke:k}),this.updatePosition({plotX:f,plotY:g}),this.isHidden=!1);if(l){l=ja(l);for(d=l.length;d--;)if(m=a.series,e=m[d?"yAxis":"xAxis"],l[d]&&e)if(h=d?o(a.stackY,a.y):a.x,
146 e.isLog&&(h=na(h)),d===1&&m.modifyValue&&(h=m.modifyValue(h)),e=e.getPlotLinePath(h,1),this.crosshairs[d])this.crosshairs[d].attr({d:e,visibility:"visible"});else{h={"stroke-width":l[d].width||1,stroke:l[d].color||"#C0C0C0",zIndex:l[d].zIndex||2};if(l[d].dashStyle)h.dashstyle=l[d].dashStyle;this.crosshairs[d]=c.renderer.path(e).attr(h).add()}}z(c,"tooltipRefresh",{text:i,x:f+c.plotLeft,y:g+c.plotTop,borderColor:k})},updatePosition:function(a){var b=this.chart,c=this.label,c=(this.options.positioner||
147 this.getPosition).call(this,c.width,c.height,a);this.move(t(c.x),t(c.y),a.plotX+b.plotLeft,a.plotY+b.plotTop)}};xb.prototype={init:function(a,b){var c=b.chart,d=c.events,e=$?"":c.zoomType,c=a.inverted,f;this.options=b;this.chart=a;this.zoomX=f=/x/.test(e);this.zoomY=e=/y/.test(e);this.zoomHor=f&&!c||e&&c;this.zoomVert=e&&!c||f&&c;this.runChartClick=d&&!!d.click;this.pinchDown=[];this.lastValidTouch={};if(b.tooltip.enabled)a.tooltip=new wb(a,b.tooltip);this.setDOMEvents()},normalize:function(a,b){var c,
148 d,a=a||O.event;if(!a.target)a.target=a.srcElement;a=Xb(a);d=a.touches?a.touches.item(0):a;if(!b)this.chartPosition=b=Wb(this.chart.container);d.pageX===w?(c=s(a.x,a.clientX-b.left),d=a.y):(c=d.pageX-b.left,d=d.pageY-b.top);return r(a,{chartX:t(c),chartY:t(d)})},getCoordinates:function(a){var b={xAxis:[],yAxis:[]};n(this.chart.axes,function(c){b[c.isXAxis?"xAxis":"yAxis"].push({axis:c,value:c.toValue(a[c.horiz?"chartX":"chartY"])})});return b},getIndex:function(a){var b=this.chart;return b.inverted?
149 b.plotHeight+b.plotTop-a.chartY:a.chartX-b.plotLeft},runPointActions:function(a){var b=this.chart,c=b.series,d=b.tooltip,e,f=b.hoverPoint,g=b.hoverSeries,h,i,j=b.chartWidth,k=this.getIndex(a);if(d&&this.options.tooltip.shared&&(!g||!g.noSharedTooltip)){e=[];h=c.length;for(i=0;i<h;i++)if(c[i].visible&&c[i].options.enableMouseTracking!==!1&&!c[i].noSharedTooltip&&c[i].tooltipPoints.length&&(b=c[i].tooltipPoints[k])&&b.series)b._dist=N(k-b.clientX),j=I(j,b._dist),e.push(b);for(h=e.length;h--;)e[h]._dist>
150 j&&e.splice(h,1);if(e.length&&e[0].clientX!==this.hoverX)d.refresh(e,a),this.hoverX=e[0].clientX}if(g&&g.tracker){if((b=g.tooltipPoints[k])&&b!==f)b.onMouseOver(a)}else d&&d.followPointer&&!d.isHidden&&(a=d.getAnchor([{}],a),d.updatePosition({plotX:a[0],plotY:a[1]}))},reset:function(a){var b=this.chart,c=b.hoverSeries,d=b.hoverPoint,e=b.tooltip,b=e&&e.shared?b.hoverPoints:d;(a=a&&e&&b)&&ja(b)[0].plotX===w&&(a=!1);if(a)e.refresh(b);else{if(d)d.onMouseOut();if(c)c.onMouseOut();e&&(e.hide(),e.hideCrosshairs());
151 this.hoverX=null}},scaleGroups:function(a,b){var c=this.chart,d;n(c.series,function(e){d=a||e.getPlotBox();e.xAxis&&e.xAxis.zoomEnabled&&(e.group.attr(d),e.markerGroup&&(e.markerGroup.attr(d),e.markerGroup.clip(b?c.clipRect:null)),e.dataLabelsGroup&&e.dataLabelsGroup.attr(d))});c.clipRect.attr(b||c.clipBox)},pinchTranslateDirection:function(a,b,c,d,e,f,g){var h=this.chart,i=a?"x":"y",j=a?"X":"Y",k="chart"+j,l=a?"width":"height",m=h["plot"+(a?"Left":"Top")],p,q,o=1,n=h.inverted,s=h.bounds[a?"h":"v"],
152 t=b.length===1,u=b[0][k],r=c[0][k],w=!t&&b[1][k],v=!t&&c[1][k],x,c=function(){!t&&N(u-w)>20&&(o=N(r-v)/N(u-w));q=(m-r)/o+u;p=h["plot"+(a?"Width":"Height")]/o};c();b=q;b<s.min?(b=s.min,x=!0):b+p>s.max&&(b=s.max-p,x=!0);x?(r-=0.8*(r-g[i][0]),t||(v-=0.8*(v-g[i][1])),c()):g[i]=[r,v];n||(f[i]=q-m,f[l]=p);f=n?1/o:o;e[l]=p;e[i]=b;d[n?a?"scaleY":"scaleX":"scale"+j]=o;d["translate"+j]=f*m+(r-f*u)},pinch:function(a){var b=this,c=b.chart,d=b.pinchDown,e=c.tooltip&&c.tooltip.options.followTouchMove,f=a.touches,
153 g=f.length,h=b.lastValidTouch,i=b.zoomHor||b.pinchHor,j=b.zoomVert||b.pinchVert,k=i||j,l=b.selectionMarker,m={},p=g===1&&(b.inClass(a.target,"highcharts-tracker")&&c.runTrackerClick||c.runChartClick),q={};(k||e)&&!p&&a.preventDefault();Na(f,function(a){return b.normalize(a)});if(a.type==="touchstart")n(f,function(a,b){d[b]={chartX:a.chartX,chartY:a.chartY}}),h.x=[d[0].chartX,d[1]&&d[1].chartX],h.y=[d[0].chartY,d[1]&&d[1].chartY],n(c.axes,function(a){if(a.zoomEnabled){var b=c.bounds[a.horiz?"h":"v"],
154 d=a.minPixelPadding,e=a.toPixels(a.dataMin),f=a.toPixels(a.dataMax),g=I(e,f),e=s(e,f);b.min=I(a.pos,g-d);b.max=s(a.pos+a.len,e+d)}});else if(d.length){if(!l)b.selectionMarker=l=r({destroy:pa},c.plotBox);i&&b.pinchTranslateDirection(!0,d,f,m,l,q,h);j&&b.pinchTranslateDirection(!1,d,f,m,l,q,h);b.hasPinched=k;b.scaleGroups(m,q);!k&&e&&g===1&&this.runPointActions(b.normalize(a))}},dragStart:function(a){var b=this.chart;b.mouseIsDown=a.type;b.cancelClick=!1;b.mouseDownX=this.mouseDownX=a.chartX;b.mouseDownY=
155 this.mouseDownY=a.chartY},drag:function(a){var b=this.chart,c=b.options.chart,d=a.chartX,e=a.chartY,f=this.zoomHor,g=this.zoomVert,h=b.plotLeft,i=b.plotTop,j=b.plotWidth,k=b.plotHeight,l,m=this.mouseDownX,p=this.mouseDownY;d<h?d=h:d>h+j&&(d=h+j);e<i?e=i:e>i+k&&(e=i+k);this.hasDragged=Math.sqrt(Math.pow(m-d,2)+Math.pow(p-e,2));if(this.hasDragged>10){l=b.isInsidePlot(m-h,p-i);if(b.hasCartesianSeries&&(this.zoomX||this.zoomY)&&l&&!this.selectionMarker)this.selectionMarker=b.renderer.rect(h,i,f?1:j,g?
156 1:k,0).attr({fill:c.selectionMarkerFill||"rgba(69,114,167,0.25)",zIndex:7}).add();this.selectionMarker&&f&&(d-=m,this.selectionMarker.attr({width:N(d),x:(d>0?0:d)+m}));this.selectionMarker&&g&&(d=e-p,this.selectionMarker.attr({height:N(d),y:(d>0?0:d)+p}));l&&!this.selectionMarker&&c.panning&&b.pan(a,c.panning)}},drop:function(a){var b=this.chart,c=this.hasPinched;if(this.selectionMarker){var d={xAxis:[],yAxis:[],originalEvent:a.originalEvent||a},e=this.selectionMarker,f=e.x,g=e.y,h;if(this.hasDragged||
157 c)n(b.axes,function(a){if(a.zoomEnabled){var b=a.horiz,c=a.toValue(b?f:g),b=a.toValue(b?f+e.width:g+e.height);!isNaN(c)&&!isNaN(b)&&(d[a.xOrY+"Axis"].push({axis:a,min:I(c,b),max:s(c,b)}),h=!0)}}),h&&z(b,"selection",d,function(a){b.zoom(r(a,c?{animation:!1}:null))});this.selectionMarker=this.selectionMarker.destroy();c&&this.scaleGroups()}if(b)K(b.container,{cursor:b._cursor}),b.cancelClick=this.hasDragged>10,b.mouseIsDown=this.hasDragged=this.hasPinched=!1,this.pinchDown=[]},onContainerMouseDown:function(a){a=
158 this.normalize(a);a.preventDefault&&a.preventDefault();this.dragStart(a)},onDocumentMouseUp:function(a){this.drop(a)},onDocumentMouseMove:function(a){var b=this.chart,c=this.chartPosition,d=b.hoverSeries,a=this.normalize(a,c);c&&d&&!this.inClass(a.target,"highcharts-tracker")&&!b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop)&&this.reset()},onContainerMouseLeave:function(){this.reset();this.chartPosition=null},onContainerMouseMove:function(a){var b=this.chart,a=this.normalize(a);a.returnValue=
159 !1;b.mouseIsDown==="mousedown"&&this.drag(a);(this.inClass(a.target,"highcharts-tracker")||b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop))&&!b.openMenu&&this.runPointActions(a)},inClass:function(a,b){for(var c;a;){if(c=v(a,"class"))if(c.indexOf(b)!==-1)return!0;else if(c.indexOf("highcharts-container")!==-1)return!1;a=a.parentNode}},onTrackerMouseOut:function(a){var b=this.chart.hoverSeries;if(b&&!b.options.stickyTracking&&!this.inClass(a.toElement||a.relatedTarget,"highcharts-tooltip"))b.onMouseOut()},
160 onContainerClick:function(a){var b=this.chart,c=b.hoverPoint,d=b.plotLeft,e=b.plotTop,f=b.inverted,g,h,i,a=this.normalize(a);a.cancelBubble=!0;if(!b.cancelClick)c&&this.inClass(a.target,"highcharts-tracker")?(g=this.chartPosition,h=c.plotX,i=c.plotY,r(c,{pageX:g.left+d+(f?b.plotWidth-i:h),pageY:g.top+e+(f?b.plotHeight-h:i)}),z(c.series,"click",r(a,{point:c})),b.hoverPoint&&c.firePointEvent("click",a)):(r(a,this.getCoordinates(a)),b.isInsidePlot(a.chartX-d,a.chartY-e)&&z(b,"click",a))},onContainerTouchStart:function(a){var b=
161 this.chart;a.touches.length===1?(a=this.normalize(a),b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop)?(this.runPointActions(a),this.pinch(a)):this.reset()):a.touches.length===2&&this.pinch(a)},onContainerTouchMove:function(a){(a.touches.length===1||a.touches.length===2)&&this.pinch(a)},onDocumentTouchEnd:function(a){this.drop(a)},setDOMEvents:function(){var a=this,b=a.chart.container,c;this._events=c=[[b,"onmousedown","onContainerMouseDown"],[b,"onmousemove","onContainerMouseMove"],[b,"onclick",
162 "onContainerClick"],[b,"mouseleave","onContainerMouseLeave"],[y,"mousemove","onDocumentMouseMove"],[y,"mouseup","onDocumentMouseUp"]];ib&&c.push([b,"ontouchstart","onContainerTouchStart"],[b,"ontouchmove","onContainerTouchMove"],[y,"touchend","onDocumentTouchEnd"]);n(c,function(b){a["_"+b[2]]=function(c){a[b[2]](c)};b[1].indexOf("on")===0?b[0][b[1]]=a["_"+b[2]]:J(b[0],b[1],a["_"+b[2]])})},destroy:function(){var a=this;n(a._events,function(b){b[1].indexOf("on")===0?b[0][b[1]]=null:aa(b[0],b[1],a["_"+
163 b[2]])});delete a._events;clearInterval(a.tooltipTimeout)}};eb.prototype={init:function(a,b){var c=this,d=b.itemStyle,e=o(b.padding,8),f=b.itemMarginTop||0;this.options=b;if(b.enabled)c.baseline=C(d.fontSize)+3+f,c.itemStyle=d,c.itemHiddenStyle=x(d,b.itemHiddenStyle),c.itemMarginTop=f,c.padding=e,c.initialItemX=e,c.initialItemY=e-5,c.maxItemWidth=0,c.chart=a,c.itemHeight=0,c.lastLineHeight=0,c.render(),J(c.chart,"endResize",function(){c.positionCheckboxes()})},colorizeItem:function(a,b){var c=this.options,
164 d=a.legendItem,e=a.legendLine,f=a.legendSymbol,g=this.itemHiddenStyle.color,c=b?c.itemStyle.color:g,h=b?a.color:g,g=a.options&&a.options.marker,i={stroke:h,fill:h},j;d&&d.css({fill:c,color:c});e&&e.attr({stroke:h});if(f){if(g&&f.isMarker)for(j in g=a.convertAttribs(g),g)d=g[j],d!==w&&(i[j]=d);f.attr(i)}},positionItem:function(a){var b=this.options,c=b.symbolPadding,b=!b.rtl,d=a._legendItemPos,e=d[0],d=d[1],f=a.checkbox;a.legendGroup&&a.legendGroup.translate(b?e:this.legendWidth-e-2*c-4,d);if(f)f.x=
165 e,f.y=d},destroyItem:function(a){var b=a.checkbox;n(["legendItem","legendLine","legendSymbol","legendGroup"],function(b){a[b]&&(a[b]=a[b].destroy())});b&&Ta(a.checkbox)},destroy:function(){var a=this.group,b=this.box;if(b)this.box=b.destroy();if(a)this.group=a.destroy()},positionCheckboxes:function(a){var b=this.group.alignAttr,c,d=this.clipHeight||this.legendHeight;if(b)c=b.translateY,n(this.allItems,function(e){var f=e.checkbox,g;f&&(g=c+f.y+(a||0)+3,K(f,{left:b.translateX+e.legendItemWidth+f.x-
166 20+"px",top:g+"px",display:g>c-6&&g<c+d-6?"":S}))})},renderTitle:function(){var a=this.padding,b=this.options.title,c=0;if(b.text){if(!this.title)this.title=this.chart.renderer.label(b.text,a-3,a-4,null,null,null,null,null,"legend-title").attr({zIndex:1}).css(b.style).add(this.group);a=this.title.getBBox();c=a.height;this.offsetWidth=a.width;this.contentGroup.attr({translateY:c})}this.titleHeight=c},renderItem:function(a){var B;var b=this,c=b.chart,d=c.renderer,e=b.options,f=e.layout==="horizontal",
167 g=e.symbolWidth,h=e.symbolPadding,i=b.itemStyle,j=b.itemHiddenStyle,k=b.padding,l=f?o(e.itemDistance,8):0,m=!e.rtl,p=e.width,q=e.itemMarginBottom||0,n=b.itemMarginTop,A=b.initialItemX,t=a.legendItem,u=a.series||a,r=u.options,w=r.showCheckbox,v=e.useHTML;if(!t&&(a.legendGroup=d.g("legend-item").attr({zIndex:1}).add(b.scrollGroup),u.drawLegendSymbol(b,a),a.legendItem=t=d.text(e.labelFormat?Ca(e.labelFormat,a):e.labelFormatter.call(a),m?g+h:-h,b.baseline,v).css(x(a.visible?i:j)).attr({align:m?"left":
168 "right",zIndex:2}).add(a.legendGroup),(v?t:a.legendGroup).on("mouseover",function(){a.setState("hover");t.css(b.options.itemHoverStyle)}).on("mouseout",function(){t.css(a.visible?i:j);a.setState()}).on("click",function(b){var c=function(){a.setVisible()},b={browserEvent:b};a.firePointEvent?a.firePointEvent("legendItemClick",b,c):z(a,"legendItemClick",b,c)}),b.colorizeItem(a,a.visible),r&&w))a.checkbox=U("input",{type:"checkbox",checked:a.selected,defaultChecked:a.selected},e.itemCheckboxStyle,c.container),
169 J(a.checkbox,"click",function(b){z(a,"checkboxClick",{checked:b.target.checked},function(){a.select()})});d=t.getBBox();B=a.legendItemWidth=e.itemWidth||g+h+d.width+l+(w?20:0),e=B;b.itemHeight=g=d.height;if(f&&b.itemX-A+e>(p||c.chartWidth-2*k-A))b.itemX=A,b.itemY+=n+b.lastLineHeight+q,b.lastLineHeight=0;b.maxItemWidth=s(b.maxItemWidth,e);b.lastItemY=n+b.itemY+q;b.lastLineHeight=s(g,b.lastLineHeight);a._legendItemPos=[b.itemX,b.itemY];f?b.itemX+=e:(b.itemY+=n+g+q,b.lastLineHeight=g);b.offsetWidth=
170 p||s((f?b.itemX-A-l:e)+k,b.offsetWidth)},render:function(){var a=this,b=a.chart,c=b.renderer,d=a.group,e,f,g,h,i=a.box,j=a.options,k=a.padding,l=j.borderWidth,m=j.backgroundColor;a.itemX=a.initialItemX;a.itemY=a.initialItemY;a.offsetWidth=0;a.lastItemY=0;if(!d)a.group=d=c.g("legend").attr({zIndex:7}).add(),a.contentGroup=c.g().attr({zIndex:1}).add(d),a.scrollGroup=c.g().add(a.contentGroup);a.renderTitle();e=[];n(b.series,function(a){var b=a.options;b.showInLegend&&!u(b.linkedTo)&&(e=e.concat(a.legendItems||
171 (b.legendType==="point"?a.data:a)))});Kb(e,function(a,b){return(a.options&&a.options.legendIndex||0)-(b.options&&b.options.legendIndex||0)});j.reversed&&e.reverse();a.allItems=e;a.display=f=!!e.length;n(e,function(b){a.renderItem(b)});g=j.width||a.offsetWidth;h=a.lastItemY+a.lastLineHeight+a.titleHeight;h=a.handleOverflow(h);if(l||m){g+=k;h+=k;if(i){if(g>0&&h>0)i[i.isNew?"attr":"animate"](i.crisp(null,null,null,g,h)),i.isNew=!1}else a.box=i=c.rect(0,0,g,h,j.borderRadius,l||0).attr({stroke:j.borderColor,
172 "stroke-width":l||0,fill:m||S}).add(d).shadow(j.shadow),i.isNew=!0;i[f?"show":"hide"]()}a.legendWidth=g;a.legendHeight=h;n(e,function(b){a.positionItem(b)});f&&d.align(r({width:g,height:h},j),!0,"spacingBox");b.isResizing||this.positionCheckboxes()},handleOverflow:function(a){var b=this,c=this.chart,d=c.renderer,e=this.options,f=e.y,f=c.spacingBox.height+(e.verticalAlign==="top"?-f:f)-this.padding,g=e.maxHeight,h=this.clipRect,i=e.navigation,j=o(i.animation,!0),k=i.arrowSize||12,l=this.nav;e.layout===
173 "horizontal"&&(f/=2);g&&(f=I(f,g));if(a>f&&!e.useHTML){this.clipHeight=c=f-20-this.titleHeight;this.pageCount=xa(a/c);this.currentPage=o(this.currentPage,1);this.fullHeight=a;if(!h)h=b.clipRect=d.clipRect(0,0,9999,0),b.contentGroup.clip(h);h.attr({height:c});if(!l)this.nav=l=d.g().attr({zIndex:1}).add(this.group),this.up=d.symbol("triangle",0,0,k,k).on("click",function(){b.scroll(-1,j)}).add(l),this.pager=d.text("",15,10).css(i.style).add(l),this.down=d.symbol("triangle-down",0,0,k,k).on("click",
174 function(){b.scroll(1,j)}).add(l);b.scroll(0);a=f}else if(l)h.attr({height:c.chartHeight}),l.hide(),this.scrollGroup.attr({translateY:1}),this.clipHeight=0;return a},scroll:function(a,b){var c=this.pageCount,d=this.currentPage+a,e=this.clipHeight,f=this.options.navigation,g=f.activeColor,h=f.inactiveColor,f=this.pager,i=this.padding;d>c&&(d=c);if(d>0)b!==w&&La(b,this.chart),this.nav.attr({translateX:i,translateY:e+7+this.titleHeight,visibility:"visible"}),this.up.attr({fill:d===1?h:g}).css({cursor:d===
175 1?"default":"pointer"}),f.attr({text:d+"/"+this.pageCount}),this.down.attr({x:18+this.pager.getBBox().width,fill:d===c?h:g}).css({cursor:d===c?"default":"pointer"}),e=-I(e*(d-1),this.fullHeight-e+i)+1,this.scrollGroup.animate({translateY:e}),f.attr({text:d+"/"+c}),this.currentPage=d,this.positionCheckboxes(e)}};/Trident.*?11\.0/.test(oa)&&mb(eb.prototype,"positionItem",function(a,b){var c=this;setTimeout(function(){a.call(c,b)})});yb.prototype={init:function(a,b){var c,d=a.series;a.series=null;c=
176 x(M,a);c.series=a.series=d;d=c.chart;this.margin=this.splashArray("margin",d);this.spacing=this.splashArray("spacing",d);var e=d.events;this.bounds={h:{},v:{}};this.callback=b;this.isResizing=0;this.options=c;this.axes=[];this.series=[];this.hasCartesianSeries=d.showAxes;var f=this,g;f.index=Ga.length;Ga.push(f);d.reflow!==!1&&J(f,"load",function(){f.initReflow()});if(e)for(g in e)J(f,g,e[g]);f.xAxis=[];f.yAxis=[];f.animation=$?!1:o(d.animation,!0);f.pointCount=0;f.counters=new Jb;f.firstRender()},
177 initSeries:function(a){var b=this.options.chart;(b=W[a.type||b.type||b.defaultSeriesType])||ka(17,!0);b=new b;b.init(this,a);return b},addSeries:function(a,b,c){var d,e=this;a&&(b=o(b,!0),z(e,"addSeries",{options:a},function(){d=e.initSeries(a);e.isDirtyLegend=!0;e.linkSeries();b&&e.redraw(c)}));return d},addAxis:function(a,b,c,d){var e=b?"xAxis":"yAxis",f=this.options;new db(this,x(a,{index:this[e].length,isX:b}));f[e]=ja(f[e]||{});f[e].push(a);o(c,!0)&&this.redraw(d)},isInsidePlot:function(a,b,
178 c){var d=c?b:a,a=c?a:b;return d>=0&&d<=this.plotWidth&&a>=0&&a<=this.plotHeight},adjustTickAmounts:function(){this.options.chart.alignTicks!==!1&&n(this.axes,function(a){a.adjustTickAmount()});this.maxTicks=null},redraw:function(a){var b=this.axes,c=this.series,d=this.pointer,e=this.legend,f=this.isDirtyLegend,g,h,i=this.isDirtyBox,j=c.length,k=j,l=this.renderer,m=l.isHidden(),p=[];La(a,this);m&&this.cloneRenderTo();for(this.layOutTitles();k--;)if(a=c[k],a.options.stacking&&(g=!0,a.isDirty)){h=!0;
179 break}if(h)for(k=j;k--;)if(a=c[k],a.options.stacking)a.isDirty=!0;n(c,function(a){a.isDirty&&a.options.legendType==="point"&&(f=!0)});if(f&&e.options.enabled)e.render(),this.isDirtyLegend=!1;g&&this.getStacks();if(this.hasCartesianSeries){if(!this.isResizing)this.maxTicks=null,n(b,function(a){a.setScale()});this.adjustTickAmounts();this.getMargins();n(b,function(a){a.isDirty&&(i=!0)});n(b,function(a){if(a.isDirtyExtremes)a.isDirtyExtremes=!1,p.push(function(){z(a,"afterSetExtremes",r(a.eventArgs,
180 a.getExtremes()));delete a.eventArgs});(i||g)&&a.redraw()})}i&&this.drawChartBox();n(c,function(a){a.isDirty&&a.visible&&(!a.isCartesian||a.xAxis)&&a.redraw()});d&&d.reset&&d.reset(!0);l.draw();z(this,"redraw");m&&this.cloneRenderTo(!0);n(p,function(a){a.call()})},showLoading:function(a){var b=this.options,c=this.loadingDiv,d=b.loading;if(!c)this.loadingDiv=c=U(Ea,{className:"highcharts-loading"},r(d.style,{zIndex:10,display:S}),this.container),this.loadingSpan=U("span",null,d.labelStyle,c);this.loadingSpan.innerHTML=
181 a||b.lang.loading;if(!this.loadingShown)K(c,{opacity:0,display:"",left:this.plotLeft+"px",top:this.plotTop+"px",width:this.plotWidth+"px",height:this.plotHeight+"px"}),Bb(c,{opacity:d.style.opacity},{duration:d.showDuration||0}),this.loadingShown=!0},hideLoading:function(){var a=this.options,b=this.loadingDiv;b&&Bb(b,{opacity:0},{duration:a.loading.hideDuration||100,complete:function(){K(b,{display:S})}});this.loadingShown=!1},get:function(a){var b=this.axes,c=this.series,d,e;for(d=0;d<b.length;d++)if(b[d].options.id===
182 a)return b[d];for(d=0;d<c.length;d++)if(c[d].options.id===a)return c[d];for(d=0;d<c.length;d++){e=c[d].points||[];for(b=0;b<e.length;b++)if(e[b].id===a)return e[b]}return null},getAxes:function(){var a=this,b=this.options,c=b.xAxis=ja(b.xAxis||{}),b=b.yAxis=ja(b.yAxis||{});n(c,function(a,b){a.index=b;a.isX=!0});n(b,function(a,b){a.index=b});c=c.concat(b);n(c,function(b){new db(a,b)});a.adjustTickAmounts()},getSelectedPoints:function(){var a=[];n(this.series,function(b){a=a.concat(ub(b.points||[],
183 function(a){return a.selected}))});return a},getSelectedSeries:function(){return ub(this.series,function(a){return a.selected})},getStacks:function(){var a=this;n(a.yAxis,function(a){if(a.stacks&&a.hasVisibleSeries)a.oldStacks=a.stacks});n(a.series,function(b){if(b.options.stacking&&(b.visible===!0||a.options.chart.ignoreHiddenSeries===!1))b.stackKey=b.type+o(b.options.stack,"")})},showResetZoom:function(){var a=this,b=M.lang,c=a.options.chart.resetZoomButton,d=c.theme,e=d.states,f=c.relativeTo===
184 "chart"?null:"plotBox";this.resetZoomButton=a.renderer.button(b.resetZoom,null,null,function(){a.zoomOut()},d,e&&e.hover).attr({align:c.position.align,title:b.resetZoomTitle}).add().align(c.position,!1,f)},zoomOut:function(){var a=this;z(a,"selection",{resetSelection:!0},function(){a.zoom()})},zoom:function(a){var b,c=this.pointer,d=!1,e;!a||a.resetSelection?n(this.axes,function(a){b=a.zoom()}):n(a.xAxis.concat(a.yAxis),function(a){var e=a.axis,h=e.isXAxis;if(c[h?"zoomX":"zoomY"]||c[h?"pinchX":"pinchY"])b=
185 e.zoom(a.min,a.max),e.displayBtn&&(d=!0)});e=this.resetZoomButton;if(d&&!e)this.showResetZoom();else if(!d&&T(e))this.resetZoomButton=e.destroy();b&&this.redraw(o(this.options.chart.animation,a&&a.animation,this.pointCount<100))},pan:function(a,b){var c=this,d=c.hoverPoints,e;d&&n(d,function(a){a.setState()});n(b==="xy"?[1,0]:[1],function(b){var d=a[b?"chartX":"chartY"],h=c[b?"xAxis":"yAxis"][0],i=c[b?"mouseDownX":"mouseDownY"],j=(h.pointRange||0)/2,k=h.getExtremes(),l=h.toValue(i-d,!0)+j,i=h.toValue(i+
186 c[b?"plotWidth":"plotHeight"]-d,!0)-j;h.series.length&&l>I(k.dataMin,k.min)&&i<s(k.dataMax,k.max)&&(h.setExtremes(l,i,!1,!1,{trigger:"pan"}),e=!0);c[b?"mouseDownX":"mouseDownY"]=d});e&&c.redraw(!1);K(c.container,{cursor:"move"})},setTitle:function(a,b){var f;var c=this,d=c.options,e;e=d.title=x(d.title,a);f=d.subtitle=x(d.subtitle,b),d=f;n([["title",a,e],["subtitle",b,d]],function(a){var b=a[0],d=c[b],e=a[1],a=a[2];d&&e&&(c[b]=d=d.destroy());a&&a.text&&!d&&(c[b]=c.renderer.text(a.text,0,0,a.useHTML).attr({align:a.align,
187 "class":"highcharts-"+b,zIndex:a.zIndex||4}).css(a.style).add())});c.layOutTitles()},layOutTitles:function(){var a=0,b=this.title,c=this.subtitle,d=this.options,e=d.title,d=d.subtitle,f=this.spacingBox.width-44;if(b&&(b.css({width:(e.width||f)+"px"}).align(r({y:15},e),!1,"spacingBox"),!e.floating&&!e.verticalAlign))a=b.getBBox().height,a>=18&&a<=25&&(a=15);c&&(c.css({width:(d.width||f)+"px"}).align(r({y:a+e.margin},d),!1,"spacingBox"),!d.floating&&!d.verticalAlign&&(a=xa(a+c.getBBox().height)));this.titleOffset=
188 a},getChartSize:function(){var a=this.options.chart,b=this.renderToClone||this.renderTo;this.containerWidth=jb(b,"width");this.containerHeight=jb(b,"height");this.chartWidth=s(0,a.width||this.containerWidth||600);this.chartHeight=s(0,o(a.height,this.containerHeight>19?this.containerHeight:400))},cloneRenderTo:function(a){var b=this.renderToClone,c=this.container;a?b&&(this.renderTo.appendChild(c),Ta(b),delete this.renderToClone):(c&&c.parentNode===this.renderTo&&this.renderTo.removeChild(c),this.renderToClone=
189 b=this.renderTo.cloneNode(0),K(b,{position:"absolute",top:"-9999px",display:"block"}),y.body.appendChild(b),c&&b.appendChild(c))},getContainer:function(){var a,b=this.options.chart,c,d,e;this.renderTo=a=b.renderTo;e="highcharts-"+zb++;if(ea(a))this.renderTo=a=y.getElementById(a);a||ka(13,!0);c=C(v(a,"data-highcharts-chart"));!isNaN(c)&&Ga[c]&&Ga[c].destroy();v(a,"data-highcharts-chart",this.index);a.innerHTML="";a.offsetWidth||this.cloneRenderTo();this.getChartSize();c=this.chartWidth;d=this.chartHeight;
190 this.container=a=U(Ea,{className:"highcharts-container"+(b.className?" "+b.className:""),id:e},r({position:"relative",overflow:"hidden",width:c+"px",height:d+"px",textAlign:"left",lineHeight:"normal",zIndex:0,"-webkit-tap-highlight-color":"rgba(0,0,0,0)"},b.style),this.renderToClone||a);this._cursor=a.style.cursor;this.renderer=b.forExport?new Ha(a,c,d,!0):new Va(a,c,d);$&&this.renderer.create(this,a,c,d)},getMargins:function(){var a=this.spacing,b,c=this.legend,d=this.margin,e=this.options.legend,
191 f=o(e.margin,10),g=e.x,h=e.y,i=e.align,j=e.verticalAlign,k=this.titleOffset;this.resetMargins();b=this.axisOffset;if(k&&!u(d[0]))this.plotTop=s(this.plotTop,k+this.options.title.margin+a[0]);if(c.display&&!e.floating)if(i==="right"){if(!u(d[1]))this.marginRight=s(this.marginRight,c.legendWidth-g+f+a[1])}else if(i==="left"){if(!u(d[3]))this.plotLeft=s(this.plotLeft,c.legendWidth+g+f+a[3])}else if(j==="top"){if(!u(d[0]))this.plotTop=s(this.plotTop,c.legendHeight+h+f+a[0])}else if(j==="bottom"&&!u(d[2]))this.marginBottom=
192 s(this.marginBottom,c.legendHeight-h+f+a[2]);this.extraBottomMargin&&(this.marginBottom+=this.extraBottomMargin);this.extraTopMargin&&(this.plotTop+=this.extraTopMargin);this.hasCartesianSeries&&n(this.axes,function(a){a.getOffset()});u(d[3])||(this.plotLeft+=b[3]);u(d[0])||(this.plotTop+=b[0]);u(d[2])||(this.marginBottom+=b[2]);u(d[1])||(this.marginRight+=b[1]);this.setChartSize()},initReflow:function(){function a(a){var g=c.width||jb(d,"width"),h=c.height||jb(d,"height"),a=a?a.target:O;if(!b.hasUserSize&&
193 g&&h&&(a===O||a===y)){if(g!==b.containerWidth||h!==b.containerHeight)clearTimeout(e),b.reflowTimeout=e=setTimeout(function(){if(b.container)b.setSize(g,h,!1),b.hasUserSize=null},100);b.containerWidth=g;b.containerHeight=h}}var b=this,c=b.options.chart,d=b.renderTo,e;b.reflow=a;J(O,"resize",a);J(b,"destroy",function(){aa(O,"resize",a)})},setSize:function(a,b,c){var d=this,e,f,g;d.isResizing+=1;g=function(){d&&z(d,"endResize",null,function(){d.isResizing-=1})};La(c,d);d.oldChartHeight=d.chartHeight;
194 d.oldChartWidth=d.chartWidth;if(u(a))d.chartWidth=e=s(0,t(a)),d.hasUserSize=!!e;if(u(b))d.chartHeight=f=s(0,t(b));K(d.container,{width:e+"px",height:f+"px"});d.setChartSize(!0);d.renderer.setSize(e,f,c);d.maxTicks=null;n(d.axes,function(a){a.isDirty=!0;a.setScale()});n(d.series,function(a){a.isDirty=!0});d.isDirtyLegend=!0;d.isDirtyBox=!0;d.getMargins();d.redraw(c);d.oldChartHeight=null;z(d,"resize");Fa===!1?g():setTimeout(g,Fa&&Fa.duration||500)},setChartSize:function(a){var b=this.inverted,c=this.renderer,
195 d=this.chartWidth,e=this.chartHeight,f=this.options.chart,g=this.spacing,h=this.clipOffset,i,j,k,l;this.plotLeft=i=t(this.plotLeft);this.plotTop=j=t(this.plotTop);this.plotWidth=k=s(0,t(d-i-this.marginRight));this.plotHeight=l=s(0,t(e-j-this.marginBottom));this.plotSizeX=b?l:k;this.plotSizeY=b?k:l;this.plotBorderWidth=f.plotBorderWidth||0;this.spacingBox=c.spacingBox={x:g[3],y:g[0],width:d-g[3]-g[1],height:e-g[0]-g[2]};this.plotBox=c.plotBox={x:i,y:j,width:k,height:l};d=2*P(this.plotBorderWidth/2);
196 b=xa(s(d,h[3])/2);c=xa(s(d,h[0])/2);this.clipBox={x:b,y:c,width:P(this.plotSizeX-s(d,h[1])/2-b),height:P(this.plotSizeY-s(d,h[2])/2-c)};a||n(this.axes,function(a){a.setAxisSize();a.setAxisTranslation()})},resetMargins:function(){var a=this.spacing,b=this.margin;this.plotTop=o(b[0],a[0]);this.marginRight=o(b[1],a[1]);this.marginBottom=o(b[2],a[2]);this.plotLeft=o(b[3],a[3]);this.axisOffset=[0,0,0,0];this.clipOffset=[0,0,0,0]},drawChartBox:function(){var a=this.options.chart,b=this.renderer,c=this.chartWidth,
197 d=this.chartHeight,e=this.chartBackground,f=this.plotBackground,g=this.plotBorder,h=this.plotBGImage,i=a.borderWidth||0,j=a.backgroundColor,k=a.plotBackgroundColor,l=a.plotBackgroundImage,m=a.plotBorderWidth||0,p,q=this.plotLeft,o=this.plotTop,n=this.plotWidth,s=this.plotHeight,t=this.plotBox,u=this.clipRect,r=this.clipBox;p=i+(a.shadow?8:0);if(i||j)if(e)e.animate(e.crisp(null,null,null,c-p,d-p));else{e={fill:j||S};if(i)e.stroke=a.borderColor,e["stroke-width"]=i;this.chartBackground=b.rect(p/2,p/
198 2,c-p,d-p,a.borderRadius,i).attr(e).add().shadow(a.shadow)}if(k)f?f.animate(t):this.plotBackground=b.rect(q,o,n,s,0).attr({fill:k}).add().shadow(a.plotShadow);if(l)h?h.animate(t):this.plotBGImage=b.image(l,q,o,n,s).add();u?u.animate({width:r.width,height:r.height}):this.clipRect=b.clipRect(r);if(m)g?g.animate(g.crisp(null,q,o,n,s)):this.plotBorder=b.rect(q,o,n,s,0,-m).attr({stroke:a.plotBorderColor,"stroke-width":m,zIndex:1}).add();this.isDirtyBox=!1},propFromSeries:function(){var a=this,b=a.options.chart,
199 c,d=a.options.series,e,f;n(["inverted","angular","polar"],function(g){c=W[b.type||b.defaultSeriesType];f=a[g]||b[g]||c&&c.prototype[g];for(e=d&&d.length;!f&&e--;)(c=W[d[e].type])&&c.prototype[g]&&(f=!0);a[g]=f})},linkSeries:function(){var a=this,b=a.series;n(b,function(a){a.linkedSeries.length=0});n(b,function(b){var d=b.options.linkedTo;if(ea(d)&&(d=d===":previous"?a.series[b.index-1]:a.get(d)))d.linkedSeries.push(b),b.linkedParent=d})},render:function(){var a=this,b=a.axes,c=a.renderer,d=a.options,
200 e=d.labels,f=d.credits,g;a.setTitle();a.legend=new eb(a,d.legend);a.getStacks();n(b,function(a){a.setScale()});a.getMargins();a.maxTicks=null;n(b,function(a){a.setTickPositions(!0);a.setMaxTicks()});a.adjustTickAmounts();a.getMargins();a.drawChartBox();a.hasCartesianSeries&&n(b,function(a){a.render()});if(!a.seriesGroup)a.seriesGroup=c.g("series-group").attr({zIndex:3}).add();n(a.series,function(a){a.translate();a.setTooltipPoints();a.render()});e.items&&n(e.items,function(b){var d=r(e.style,b.style),
201 f=C(d.left)+a.plotLeft,g=C(d.top)+a.plotTop+12;delete d.left;delete d.top;c.text(b.html,f,g).attr({zIndex:2}).css(d).add()});if(f.enabled&&!a.credits)g=f.href,a.credits=c.text(f.text,0,0).on("click",function(){if(g)location.href=g}).attr({align:f.position.align,zIndex:8}).css(f.style).add().align(f.position);a.hasRendered=!0},destroy:function(){var a=this,b=a.axes,c=a.series,d=a.container,e,f=d&&d.parentNode;z(a,"destroy");Ga[a.index]=w;a.renderTo.removeAttribute("data-highcharts-chart");aa(a);for(e=
202 b.length;e--;)b[e]=b[e].destroy();for(e=c.length;e--;)c[e]=c[e].destroy();n("title,subtitle,chartBackground,plotBackground,plotBGImage,plotBorder,seriesGroup,clipRect,credits,pointer,scroller,rangeSelector,legend,resetZoomButton,tooltip,renderer".split(","),function(b){var c=a[b];c&&c.destroy&&(a[b]=c.destroy())});if(d)d.innerHTML="",aa(d),f&&Ta(d);for(e in a)delete a[e]},isReadyToRender:function(){var a=this;return!Z&&O==O.top&&y.readyState!=="complete"||$&&!O.canvg?($?Tb.push(function(){a.firstRender()},
203 a.options.global.canvasToolsURL):y.attachEvent("onreadystatechange",function(){y.detachEvent("onreadystatechange",a.firstRender);y.readyState==="complete"&&a.firstRender()}),!1):!0},firstRender:function(){var a=this,b=a.options,c=a.callback;if(a.isReadyToRender())a.getContainer(),z(a,"init"),a.resetMargins(),a.setChartSize(),a.propFromSeries(),a.getAxes(),n(b.series||[],function(b){a.initSeries(b)}),a.linkSeries(),z(a,"beforeRender"),a.pointer=new xb(a,b),a.render(),a.renderer.draw(),c&&c.apply(a,
204 [a]),n(a.callbacks,function(b){b.apply(a,[a])}),a.cloneRenderTo(!0),z(a,"load")},splashArray:function(a,b){var c=b[a],c=T(c)?c:[c,c,c,c];return[o(b[a+"Top"],c[0]),o(b[a+"Right"],c[1]),o(b[a+"Bottom"],c[2]),o(b[a+"Left"],c[3])]}};yb.prototype.callbacks=[];var Pa=function(){};Pa.prototype={init:function(a,b,c){this.series=a;this.applyOptions(b,c);this.pointAttr={};if(a.options.colorByPoint&&(b=a.options.colors||a.chart.options.colors,this.color=this.color||b[a.colorCounter++],a.colorCounter===b.length))a.colorCounter=
205 0;a.chart.pointCount++;return this},applyOptions:function(a,b){var c=this.series,d=c.pointValKey,a=Pa.prototype.optionsToObject.call(this,a);r(this,a);this.options=this.options?r(this.options,a):a;if(d)this.y=this[d];if(this.x===w&&c)this.x=b===w?c.autoIncrement():b;return this},optionsToObject:function(a){var b,c=this.series,d=c.pointArrayMap||["y"],e=d.length,f=0,g=0;if(typeof a==="number"||a===null)b={y:a};else if(Ia(a)){b={};if(a.length>e){c=typeof a[0];if(c==="string")b.name=a[0];else if(c===
206 "number")b.x=a[0];f++}for(;g<e;)b[d[g++]]=a[f++]}else if(typeof a==="object"){b=a;if(a.dataLabels)c._hasPointLabels=!0;if(a.marker)c._hasPointMarkers=!0}return b},destroy:function(){var a=this.series.chart,b=a.hoverPoints,c;a.pointCount--;if(b&&(this.setState(),ga(b,this),!b.length))a.hoverPoints=null;if(this===a.hoverPoint)this.onMouseOut();if(this.graphic||this.dataLabel)aa(this),this.destroyElements();this.legendItem&&a.legend.destroyItem(this);for(c in this)this[c]=null},destroyElements:function(){for(var a=
207 "graphic,dataLabel,dataLabelUpper,group,connector,shadowGroup".split(","),b,c=6;c--;)b=a[c],this[b]&&(this[b]=this[b].destroy())},getLabelConfig:function(){return{x:this.category,y:this.y,key:this.name||this.category,series:this.series,point:this,percentage:this.percentage,total:this.total||this.stackTotal}},select:function(a,b){var c=this,d=c.series,e=d.chart,a=o(a,!c.selected);c.firePointEvent(a?"select":"unselect",{accumulate:b},function(){c.selected=c.options.selected=a;d.options.data[qa(c,d.data)]=
208 c.options;c.setState(a&&"select");b||n(e.getSelectedPoints(),function(a){if(a.selected&&a!==c)a.selected=a.options.selected=!1,d.options.data[qa(a,d.data)]=a.options,a.setState(""),a.firePointEvent("unselect")})})},onMouseOver:function(a){var b=this.series,c=b.chart,d=c.tooltip,e=c.hoverPoint;if(e&&e!==this)e.onMouseOut();this.firePointEvent("mouseOver");d&&(!d.shared||b.noSharedTooltip)&&d.refresh(this,a);this.setState("hover");c.hoverPoint=this},onMouseOut:function(){var a=this.series.chart,b=a.hoverPoints;
209 if(!b||qa(this,b)===-1)this.firePointEvent("mouseOut"),this.setState(),a.hoverPoint=null},tooltipFormatter:function(a){var b=this.series,c=b.tooltipOptions,d=o(c.valueDecimals,""),e=c.valuePrefix||"",f=c.valueSuffix||"";n(b.pointArrayMap||["y"],function(b){b="{point."+b;if(e||f)a=a.replace(b+"}",e+b+"}"+f);a=a.replace(b+"}",b+":,."+d+"f}")});return Ca(a,{point:this,series:this.series})},update:function(a,b,c){var d=this,e=d.series,f=d.graphic,g,h=e.data,i=e.chart,j=e.options,b=o(b,!0);d.firePointEvent("update",
210 {options:a},function(){d.applyOptions(a);if(T(a)&&(e.getAttribs(),f))a.marker&&a.marker.symbol?d.graphic=f.destroy():f.attr(d.pointAttr[d.state||""]);g=qa(d,h);e.xData[g]=d.x;e.yData[g]=e.toYData?e.toYData(d):d.y;e.zData[g]=d.z;j.data[g]=d.options;e.isDirty=e.isDirtyData=!0;if(!e.fixedBox&&e.hasCartesianSeries)i.isDirtyBox=!0;j.legendType==="point"&&i.legend.destroyItem(d);b&&i.redraw(c)})},remove:function(a,b){var c=this,d=c.series,e=d.points,f=d.chart,g,h=d.data;La(b,f);a=o(a,!0);c.firePointEvent("remove",
211 null,function(){g=qa(c,h);h.length===e.length&&e.splice(g,1);h.splice(g,1);d.options.data.splice(g,1);d.xData.splice(g,1);d.yData.splice(g,1);d.zData.splice(g,1);c.destroy();d.isDirty=!0;d.isDirtyData=!0;a&&f.redraw()})},firePointEvent:function(a,b,c){var d=this,e=this.series.options;(e.point.events[a]||d.options&&d.options.events&&d.options.events[a])&&this.importEvents();a==="click"&&e.allowPointSelect&&(c=function(a){d.select(null,a.ctrlKey||a.metaKey||a.shiftKey)});z(this,a,b,c)},importEvents:function(){if(!this.hasImportedEvents){var a=
212 x(this.series.options.point,this.options).events,b;this.events=a;for(b in a)J(this,b,a[b]);this.hasImportedEvents=!0}},setState:function(a){var b=this.plotX,c=this.plotY,d=this.series,e=d.options.states,f=Y[d.type].marker&&d.options.marker,g=f&&!f.enabled,h=f&&f.states[a],i=h&&h.enabled===!1,j=d.stateMarkerGraphic,k=this.marker||{},l=d.chart,m=this.pointAttr,a=a||"";if(!(a===this.state||this.selected&&a!=="select"||e[a]&&e[a].enabled===!1||a&&(i||g&&!h.enabled))){if(this.graphic)e=f&&this.graphic.symbolName&&
213 m[a].r,this.graphic.attr(x(m[a],e?{x:b-e,y:c-e,width:2*e,height:2*e}:{}));else{if(a&&h)e=h.radius,k=k.symbol||d.symbol,j&&j.currentSymbol!==k&&(j=j.destroy()),j?j.attr({x:b-e,y:c-e}):(d.stateMarkerGraphic=j=l.renderer.symbol(k,b-e,c-e,2*e,2*e).attr(m[a]).add(d.markerGroup),j.currentSymbol=k);if(j)j[a&&l.isInsidePlot(b,c)?"show":"hide"]()}this.state=a}}};var Q=function(){};Q.prototype={isCartesian:!0,type:"line",pointClass:Pa,sorted:!0,requireSorting:!0,pointAttrToOptions:{stroke:"lineColor","stroke-width":"lineWidth",
214 fill:"fillColor",r:"radius"},colorCounter:0,init:function(a,b){var c,d,e=a.series;this.chart=a;this.options=b=this.setOptions(b);this.linkedSeries=[];this.bindAxes();r(this,{name:b.name,state:"",pointAttr:{},visible:b.visible!==!1,selected:b.selected===!0});if($)b.animation=!1;d=b.events;for(c in d)J(this,c,d[c]);if(d&&d.click||b.point&&b.point.events&&b.point.events.click||b.allowPointSelect)a.runTrackerClick=!0;this.getColor();this.getSymbol();this.setData(b.data,!1);if(this.isCartesian)a.hasCartesianSeries=
215 !0;e.push(this);this._i=e.length-1;Kb(e,function(a,b){return o(a.options.index,a._i)-o(b.options.index,a._i)});n(e,function(a,b){a.index=b;a.name=a.name||"Series "+(b+1)})},bindAxes:function(){var a=this,b=a.options,c=a.chart,d;a.isCartesian&&n(["xAxis","yAxis"],function(e){n(c[e],function(c){d=c.options;if(b[e]===d.index||b[e]!==w&&b[e]===d.id||b[e]===w&&d.index===0)c.series.push(a),a[e]=c,c.isDirty=!0});a[e]||ka(18,!0)})},autoIncrement:function(){var a=this.options,b=this.xIncrement,b=o(b,a.pointStart,
216 0);this.pointInterval=o(this.pointInterval,a.pointInterval,1);this.xIncrement=b+this.pointInterval;return b},getSegments:function(){var a=-1,b=[],c,d=this.points,e=d.length;if(e)if(this.options.connectNulls){for(c=e;c--;)d[c].y===null&&d.splice(c,1);d.length&&(b=[d])}else n(d,function(c,g){c.y===null?(g>a+1&&b.push(d.slice(a+1,g)),a=g):g===e-1&&b.push(d.slice(a+1,g+1))});this.segments=b},setOptions:function(a){var b=this.chart.options,c=b.plotOptions,d=c[this.type];this.userOptions=a;a=x(d,c.series,
217 a);this.tooltipOptions=x(b.tooltip,a.tooltip);d.marker===null&&delete a.marker;return a},getColor:function(){var a=this.options,b=this.userOptions,c=this.chart.options.colors,d=this.chart.counters,e;e=a.color||Y[this.type].color;if(!e&&!a.colorByPoint)u(b._colorIndex)?a=b._colorIndex:(b._colorIndex=d.color,a=d.color++),e=c[a];this.color=e;d.wrapColor(c.length)},getSymbol:function(){var a=this.userOptions,b=this.options.marker,c=this.chart,d=c.options.symbols,c=c.counters;this.symbol=b.symbol;if(!this.symbol)u(a._symbolIndex)?
218 a=a._symbolIndex:(a._symbolIndex=c.symbol,a=c.symbol++),this.symbol=d[a];if(/^url/.test(this.symbol))b.radius=0;c.wrapSymbol(d.length)},drawLegendSymbol:function(a){var b=this.options,c=b.marker,d=a.options,e;e=d.symbolWidth;var f=this.chart.renderer,g=this.legendGroup,a=a.baseline-t(f.fontMetrics(d.itemStyle.fontSize).b*0.3);if(b.lineWidth){d={"stroke-width":b.lineWidth};if(b.dashStyle)d.dashstyle=b.dashStyle;this.legendLine=f.path(["M",0,a,"L",e,a]).attr(d).add(g)}if(c&&c.enabled)b=c.radius,this.legendSymbol=
219 e=f.symbol(this.symbol,e/2-b,a-b,2*b,2*b).add(g),e.isMarker=!0},addPoint:function(a,b,c,d){var e=this.options,f=this.data,g=this.graph,h=this.area,i=this.chart,j=this.xData,k=this.yData,l=this.zData,m=this.names,p=g&&g.shift||0,q=e.data,s;La(d,i);c&&n([g,h,this.graphNeg,this.areaNeg],function(a){if(a)a.shift=p+1});if(h)h.isArea=!0;b=o(b,!0);d={series:this};this.pointClass.prototype.applyOptions.apply(d,[a]);g=d.x;h=j.length;if(this.requireSorting&&g<j[h-1])for(s=!0;h&&j[h-1]>g;)h--;j.splice(h,0,g);
220 k.splice(h,0,this.toYData?this.toYData(d):d.y);l.splice(h,0,d.z);if(m)m[g]=d.name;q.splice(h,0,a);s&&(this.data.splice(h,0,null),this.processData());e.legendType==="point"&&this.generatePoints();c&&(f[0]&&f[0].remove?f[0].remove(!1):(f.shift(),j.shift(),k.shift(),l.shift(),q.shift()));this.isDirtyData=this.isDirty=!0;b&&(this.getAttribs(),i.redraw())},setData:function(a,b){var c=this.points,d=this.options,e=this.chart,f=null,g=this.xAxis,h=g&&g.categories&&!g.categories.length?[]:null,i;this.xIncrement=
221 null;this.pointRange=g&&g.categories?1:d.pointRange;this.colorCounter=0;var j=[],k=[],l=[],m=a?a.length:[];i=o(d.turboThreshold,1E3);var p=this.pointArrayMap,p=p&&p.length,q=!!this.toYData;if(i&&m>i){for(i=0;f===null&&i<m;)f=a[i],i++;if(sa(f)){f=o(d.pointStart,0);d=o(d.pointInterval,1);for(i=0;i<m;i++)j[i]=f,k[i]=a[i],f+=d;this.xIncrement=f}else if(Ia(f))if(p)for(i=0;i<m;i++)d=a[i],j[i]=d[0],k[i]=d.slice(1,p+1);else for(i=0;i<m;i++)d=a[i],j[i]=d[0],k[i]=d[1];else ka(12)}else for(i=0;i<m;i++)if(a[i]!==
222 w&&(d={series:this},this.pointClass.prototype.applyOptions.apply(d,[a[i]]),j[i]=d.x,k[i]=q?this.toYData(d):d.y,l[i]=d.z,h&&d.name))h[d.x]=d.name;ea(k[0])&&ka(14,!0);this.data=[];this.options.data=a;this.xData=j;this.yData=k;this.zData=l;this.names=h;for(i=c&&c.length||0;i--;)c[i]&&c[i].destroy&&c[i].destroy();if(g)g.minRange=g.userMinRange;this.isDirty=this.isDirtyData=e.isDirtyBox=!0;o(b,!0)&&e.redraw(!1)},remove:function(a,b){var c=this,d=c.chart,a=o(a,!0);if(!c.isRemoving)c.isRemoving=!0,z(c,"remove",
223 null,function(){c.destroy();d.isDirtyLegend=d.isDirtyBox=!0;d.linkSeries();a&&d.redraw(b)});c.isRemoving=!1},processData:function(a){var b=this.xData,c=this.yData,d=b.length,e;e=0;var f,g,h=this.xAxis,i=this.options,j=i.cropThreshold,k=this.isCartesian;if(k&&!this.isDirty&&!h.isDirty&&!this.yAxis.isDirty&&!a)return!1;if(k&&this.sorted&&(!j||d>j||this.forceCrop))if(a=h.min,h=h.max,b[d-1]<a||b[0]>h)b=[],c=[];else if(b[0]<a||b[d-1]>h)e=this.cropData(this.xData,this.yData,a,h),b=e.xData,c=e.yData,e=e.start,
224 f=!0;for(h=b.length-1;h>=0;h--)d=b[h]-b[h-1],d>0&&(g===w||d<g)?g=d:d<0&&this.requireSorting&&ka(15);this.cropped=f;this.cropStart=e;this.processedXData=b;this.processedYData=c;if(i.pointRange===null)this.pointRange=g||1;this.closestPointRange=g},cropData:function(a,b,c,d){var e=a.length,f=0,g=e,h=o(this.cropShoulder,1),i;for(i=0;i<e;i++)if(a[i]>=c){f=s(0,i-h);break}for(;i<e;i++)if(a[i]>d){g=i+h;break}return{xData:a.slice(f,g),yData:b.slice(f,g),start:f,end:g}},generatePoints:function(){var a=this.options.data,
225 b=this.data,c,d=this.processedXData,e=this.processedYData,f=this.pointClass,g=d.length,h=this.cropStart||0,i,j=this.hasGroupedData,k,l=[],m;if(!b&&!j)b=[],b.length=a.length,b=this.data=b;for(m=0;m<g;m++)i=h+m,j?l[m]=(new f).init(this,[d[m]].concat(ja(e[m]))):(b[i]?k=b[i]:a[i]!==w&&(b[i]=k=(new f).init(this,a[i],d[m])),l[m]=k);if(b&&(g!==(c=b.length)||j))for(m=0;m<c;m++)if(m===h&&!j&&(m+=g),b[m])b[m].destroyElements(),b[m].plotX=w;this.data=b;this.points=l},setStackedPoints:function(){if(this.options.stacking&&
226 !(this.visible!==!0&&this.chart.options.chart.ignoreHiddenSeries!==!1)){var a=this.processedXData,b=this.processedYData,c=[],d=b.length,e=this.options,f=e.threshold,g=e.stack,e=e.stacking,h=this.stackKey,i="-"+h,j=this.negStacks,k=this.yAxis,l=k.stacks,m=k.oldStacks,p,q,o,n,t;for(o=0;o<d;o++){n=a[o];t=b[o];q=(p=j&&t<f)?i:h;l[q]||(l[q]={});if(!l[q][n])m[q]&&m[q][n]?(l[q][n]=m[q][n],l[q][n].total=null):l[q][n]=new Mb(k,k.options.stackLabels,p,n,g,e);q=l[q][n];q.points[this.index]=[q.cum||0];e==="percent"?
227 (p=p?h:i,j&&l[p]&&l[p][n]?(p=l[p][n],q.total=p.total=s(p.total,q.total)+N(t)||0):q.total+=N(t)||0):q.total+=t||0;q.cum=(q.cum||0)+(t||0);q.points[this.index].push(q.cum);c[o]=q.cum}if(e==="percent")k.usePercentage=!0;this.stackedYData=c;k.oldStacks={}}},setPercentStacks:function(){var a=this,b=a.stackKey,c=a.yAxis.stacks;n([b,"-"+b],function(b){var d;for(var e=a.xData.length,f,g;e--;)if(f=a.xData[e],d=(g=c[b]&&c[b][f])&&g.points[a.index],f=d)g=g.total?100/g.total:0,f[0]=ia(f[0]*g),f[1]=ia(f[1]*g),
228 a.stackedYData[e]=f[1]})},getExtremes:function(){var a=this.yAxis,b=this.processedXData,c=this.stackedYData||this.processedYData,d=c.length,e=[],f=0,g=this.xAxis.getExtremes(),h=g.min,g=g.max,i,j,k,l;for(l=0;l<d;l++)if(j=b[l],k=c[l],i=k!==null&&k!==w&&(!a.isLog||k.length||k>0),j=this.getExtremesFromAll||this.cropped||(b[l+1]||j)>=h&&(b[l-1]||j)<=g,i&&j)if(i=k.length)for(;i--;)k[i]!==null&&(e[f++]=k[i]);else e[f++]=k;this.dataMin=o(void 0,Ja(e));this.dataMax=o(void 0,va(e))},translate:function(){this.processedXData||
229 this.processData();this.generatePoints();for(var a=this.options,b=a.stacking,c=this.xAxis,d=c.categories,e=this.yAxis,f=this.points,g=f.length,h=!!this.modifyValue,i=a.pointPlacement,j=i==="between"||sa(i),k=a.threshold,a=0;a<g;a++){var l=f[a],m=l.x,p=l.y,q=l.low,n=e.stacks[(this.negStacks&&p<k?"-":"")+this.stackKey];if(e.isLog&&p<=0)l.y=p=null;l.plotX=c.translate(m,0,0,0,1,i,this.type==="flags");if(b&&this.visible&&n&&n[m])n=n[m],p=n.points[this.index],q=p[0],p=p[1],q===0&&(q=o(k,e.min)),e.isLog&&
230 q<=0&&(q=null),l.percentage=b==="percent"&&p,l.total=l.stackTotal=n.total,l.stackY=p,n.setOffset(this.pointXOffset||0,this.barW||0);l.yBottom=u(q)?e.translate(q,0,1,0,1):null;h&&(p=this.modifyValue(p,l));l.plotY=typeof p==="number"&&p!==Infinity?e.translate(p,0,1,0,1):w;l.clientX=j?c.translate(m,0,0,0,1):l.plotX;l.negative=l.y<(k||0);l.category=d&&d[l.x]!==w?d[l.x]:l.x}this.getSegments()},setTooltipPoints:function(a){var b=[],c,d,e=this.xAxis,f=e&&e.getExtremes(),g=e?e.tooltipLen||e.len:this.chart.plotSizeX,
231 h,i,j=[];if(this.options.enableMouseTracking!==!1){if(a)this.tooltipPoints=null;n(this.segments||this.points,function(a){b=b.concat(a)});e&&e.reversed&&(b=b.reverse());this.orderTooltipPoints&&this.orderTooltipPoints(b);a=b.length;for(i=0;i<a;i++)if(e=b[i],c=e.x,c>=f.min&&c<=f.max){h=b[i+1];c=d===w?0:d+1;for(d=b[i+1]?I(s(0,P((e.clientX+(h?h.wrappedClientX||h.clientX:g))/2)),g):g;c>=0&&c<=d;)j[c++]=e}this.tooltipPoints=j}},tooltipHeaderFormatter:function(a){var b=this.tooltipOptions,c=b.xDateFormat,
232 d=b.dateTimeLabelFormats,e=this.xAxis,f=e&&e.options.type==="datetime",b=b.headerFormat,e=e&&e.closestPointRange,g;if(f&&!c)if(e)for(g in D){if(D[g]>=e){c=d[g];break}}else c=d.day;f&&c&&sa(a.key)&&(b=b.replace("{point.key}","{point.key:"+c+"}"));return Ca(b,{point:a,series:this})},onMouseOver:function(){var a=this.chart,b=a.hoverSeries;if(b&&b!==this)b.onMouseOut();this.options.events.mouseOver&&z(this,"mouseOver");this.setState("hover");a.hoverSeries=this},onMouseOut:function(){var a=this.options,
233 b=this.chart,c=b.tooltip,d=b.hoverPoint;if(d)d.onMouseOut();this&&a.events.mouseOut&&z(this,"mouseOut");c&&!a.stickyTracking&&(!c.shared||this.noSharedTooltip)&&c.hide();this.setState();b.hoverSeries=null},animate:function(a){var b=this,c=b.chart,d=c.renderer,e;e=b.options.animation;var f=c.clipBox,g=c.inverted,h;if(e&&!T(e))e=Y[b.type].animation;h="_sharedClip"+e.duration+e.easing;if(a)a=c[h],e=c[h+"m"],a||(c[h]=a=d.clipRect(r(f,{width:0})),c[h+"m"]=e=d.clipRect(-99,g?-c.plotLeft:-c.plotTop,99,g?
234 c.chartWidth:c.chartHeight)),b.group.clip(a),b.markerGroup.clip(e),b.sharedClipKey=h;else{if(a=c[h])a.animate({width:c.plotSizeX},e),c[h+"m"].animate({width:c.plotSizeX+99},e);b.animate=null;b.animationTimeout=setTimeout(function(){b.afterAnimate()},e.duration)}},afterAnimate:function(){var a=this.chart,b=this.sharedClipKey,c=this.group;c&&this.options.clip!==!1&&(c.clip(a.clipRect),this.markerGroup.clip());setTimeout(function(){b&&a[b]&&(a[b]=a[b].destroy(),a[b+"m"]=a[b+"m"].destroy())},100)},drawPoints:function(){var a,
235 b=this.points,c=this.chart,d,e,f,g,h,i,j,k,l=this.options.marker,m,p=this.markerGroup;if(l.enabled||this._hasPointMarkers)for(f=b.length;f--;)if(g=b[f],d=P(g.plotX),e=g.plotY,k=g.graphic,i=g.marker||{},a=l.enabled&&i.enabled===w||i.enabled,m=c.isInsidePlot(t(d),e,c.inverted),a&&e!==w&&!isNaN(e)&&g.y!==null)if(a=g.pointAttr[g.selected?"select":""],h=a.r,i=o(i.symbol,this.symbol),j=i.indexOf("url")===0,k)k.attr({visibility:m?Z?"inherit":"visible":"hidden"}).animate(r({x:d-h,y:e-h},k.symbolName?{width:2*
236 h,height:2*h}:{}));else{if(m&&(h>0||j))g.graphic=c.renderer.symbol(i,d-h,e-h,2*h,2*h).attr(a).add(p)}else if(k)g.graphic=k.destroy()},convertAttribs:function(a,b,c,d){var e=this.pointAttrToOptions,f,g,h={},a=a||{},b=b||{},c=c||{},d=d||{};for(f in e)g=e[f],h[f]=o(a[g],b[f],c[f],d[f]);return h},getAttribs:function(){var a=this,b=a.options,c=Y[a.type].marker?b.marker:b,d=c.states,e=d.hover,f,g=a.color,h={stroke:g,fill:g},i=a.points||[],j=[],k,l=a.pointAttrToOptions,m=b.negativeColor,p=c.lineColor,q;
237 b.marker?(e.radius=e.radius||c.radius+2,e.lineWidth=e.lineWidth||c.lineWidth+1):e.color=e.color||ra(e.color||g).brighten(e.brightness).get();j[""]=a.convertAttribs(c,h);n(["hover","select"],function(b){j[b]=a.convertAttribs(d[b],j[""])});a.pointAttr=j;for(g=i.length;g--;){h=i[g];if((c=h.options&&h.options.marker||h.options)&&c.enabled===!1)c.radius=0;if(h.negative&&m)h.color=h.fillColor=m;f=b.colorByPoint||h.color;if(h.options)for(q in l)u(c[l[q]])&&(f=!0);if(f){c=c||{};k=[];d=c.states||{};f=d.hover=
238 d.hover||{};if(!b.marker)f.color=ra(f.color||h.color).brighten(f.brightness||e.brightness).get();k[""]=a.convertAttribs(r({color:h.color,fillColor:h.color,lineColor:p===null?h.color:w},c),j[""]);k.hover=a.convertAttribs(d.hover,j.hover,k[""]);k.select=a.convertAttribs(d.select,j.select,k[""])}else k=j;h.pointAttr=k}},update:function(a,b){var c=this.chart,d=this.type,e=W[d].prototype,f,a=x(this.userOptions,{animation:!1,index:this.index,pointStart:this.xData[0]},{data:this.options.data},a);this.remove(!1);
239 for(f in e)e.hasOwnProperty(f)&&(this[f]=w);r(this,W[a.type||d].prototype);this.init(c,a);o(b,!0)&&c.redraw(!1)},destroy:function(){var a=this,b=a.chart,c=/AppleWebKit\/533/.test(oa),d,e,f=a.data||[],g,h,i;z(a,"destroy");aa(a);n(["xAxis","yAxis"],function(b){if(i=a[b])ga(i.series,a),i.isDirty=i.forceRedraw=!0,i.stacks={}});a.legendItem&&a.chart.legend.destroyItem(a);for(e=f.length;e--;)(g=f[e])&&g.destroy&&g.destroy();a.points=null;clearTimeout(a.animationTimeout);n("area,graph,dataLabelsGroup,group,markerGroup,tracker,graphNeg,areaNeg,posClip,negClip".split(","),
240 function(b){a[b]&&(d=c&&b==="group"?"hide":"destroy",a[b][d]())});if(b.hoverSeries===a)b.hoverSeries=null;ga(b.series,a);for(h in a)delete a[h]},drawDataLabels:function(){var a=this,b=a.options.dataLabels,c=a.points,d,e,f,g;if(b.enabled||a._hasPointLabels)a.dlProcessOptions&&a.dlProcessOptions(b),g=a.plotGroup("dataLabelsGroup","data-labels",a.visible?"visible":"hidden",b.zIndex||6),e=b,n(c,function(c){var i,j=c.dataLabel,k,l,m=c.connector,p=!0;d=c.options&&c.options.dataLabels;i=o(d&&d.enabled,e.enabled);
241 if(j&&!i)c.dataLabel=j.destroy();else if(i){b=x(e,d);i=b.rotation;k=c.getLabelConfig();f=b.format?Ca(b.format,k):b.formatter.call(k,b);b.style.color=o(b.color,b.style.color,a.color,"black");if(j)if(u(f))j.attr({text:f}),p=!1;else{if(c.dataLabel=j=j.destroy(),m)c.connector=m.destroy()}else if(u(f)){j={fill:b.backgroundColor,stroke:b.borderColor,"stroke-width":b.borderWidth,r:b.borderRadius||0,rotation:i,padding:b.padding,zIndex:1};for(l in j)j[l]===w&&delete j[l];j=c.dataLabel=a.chart.renderer[i?"text":
242 "label"](f,0,-999,null,null,null,b.useHTML).attr(j).css(b.style).add(g).shadow(b.shadow)}j&&a.alignDataLabel(c,j,b,null,p)}})},alignDataLabel:function(a,b,c,d,e){var f=this.chart,g=f.inverted,h=o(a.plotX,-999),i=o(a.plotY,-999),j=b.getBBox();if(a=this.visible&&f.isInsidePlot(a.plotX,a.plotY,g))d=r({x:g?f.plotWidth-i:h,y:t(g?f.plotHeight-h:i),width:0,height:0},d),r(c,{width:j.width,height:j.height}),c.rotation?(g={align:c.align,x:d.x+c.x+d.width/2,y:d.y+c.y+d.height/2},b[e?"attr":"animate"](g)):(b.align(c,
243 null,d),g=b.alignAttr,o(c.overflow,"justify")==="justify"?this.justifyDataLabel(b,c,g,j,d,e):o(c.crop,!0)&&(a=f.isInsidePlot(g.x,g.y)&&f.isInsidePlot(g.x+j.width,g.y+j.height)));a||b.attr({y:-999})},justifyDataLabel:function(a,b,c,d,e,f){var g=this.chart,h=b.align,i=b.verticalAlign,j,k;j=c.x;if(j<0)h==="right"?b.align="left":b.x=-j,k=!0;j=c.x+d.width;if(j>g.plotWidth)h==="left"?b.align="right":b.x=g.plotWidth-j,k=!0;j=c.y;if(j<0)i==="bottom"?b.verticalAlign="top":b.y=-j,k=!0;j=c.y+d.height;if(j>g.plotHeight)i===
244 "top"?b.verticalAlign="bottom":b.y=g.plotHeight-j,k=!0;if(k)a.placed=!f,a.align(b,null,e)},getSegmentPath:function(a){var b=this,c=[],d=b.options.step;n(a,function(e,f){var g=e.plotX,h=e.plotY,i;b.getPointSpline?c.push.apply(c,b.getPointSpline(a,e,f)):(c.push(f?"L":"M"),d&&f&&(i=a[f-1],d==="right"?c.push(i.plotX,h):d==="center"?c.push((i.plotX+g)/2,i.plotY,(i.plotX+g)/2,h):c.push(g,i.plotY)),c.push(e.plotX,e.plotY))});return c},getGraphPath:function(){var a=this,b=[],c,d=[];n(a.segments,function(e){c=
245 a.getSegmentPath(e);e.length>1?b=b.concat(c):d.push(e[0])});a.singlePoints=d;return a.graphPath=b},drawGraph:function(){var a=this,b=this.options,c=[["graph",b.lineColor||this.color]],d=b.lineWidth,e=b.dashStyle,f=this.getGraphPath(),g=b.negativeColor;g&&c.push(["graphNeg",g]);n(c,function(c,g){var j=c[0],k=a[j];if(k)Wa(k),k.animate({d:f});else if(d&&f.length)k={stroke:c[1],"stroke-width":d,zIndex:1},e?k.dashstyle=e:k["stroke-linecap"]=k["stroke-linejoin"]="round",a[j]=a.chart.renderer.path(f).attr(k).add(a.group).shadow(!g&&
246 b.shadow)})},clipNeg:function(){var a=this.options,b=this.chart,c=b.renderer,d=a.negativeColor||a.negativeFillColor,e,f=this.graph,g=this.area,h=this.posClip,i=this.negClip;e=b.chartWidth;var j=b.chartHeight,k=s(e,j),l=this.yAxis;if(d&&(f||g)){d=t(l.toPixels(a.threshold||0,!0));a={x:0,y:0,width:k,height:d};k={x:0,y:d,width:k,height:k};if(b.inverted)a.height=k.y=b.plotWidth-d,c.isVML&&(a={x:b.plotWidth-d-b.plotLeft,y:0,width:e,height:j},k={x:d+b.plotLeft-e,y:0,width:b.plotLeft+d,height:e});l.reversed?
247 (b=k,e=a):(b=a,e=k);h?(h.animate(b),i.animate(e)):(this.posClip=h=c.clipRect(b),this.negClip=i=c.clipRect(e),f&&this.graphNeg&&(f.clip(h),this.graphNeg.clip(i)),g&&(g.clip(h),this.areaNeg.clip(i)))}},invertGroups:function(){function a(){var a={width:b.yAxis.len,height:b.xAxis.len};n(["group","markerGroup"],function(c){b[c]&&b[c].attr(a).invert()})}var b=this,c=b.chart;if(b.xAxis)J(c,"resize",a),J(b,"destroy",function(){aa(c,"resize",a)}),a(),b.invertGroups=a},plotGroup:function(a,b,c,d,e){var f=this[a],
248 g=!f;g&&(this[a]=f=this.chart.renderer.g(b).attr({visibility:c,zIndex:d||0.1}).add(e));f[g?"attr":"animate"](this.getPlotBox());return f},getPlotBox:function(){return{translateX:this.xAxis?this.xAxis.left:this.chart.plotLeft,translateY:this.yAxis?this.yAxis.top:this.chart.plotTop,scaleX:1,scaleY:1}},render:function(){var a=this.chart,b,c=this.options,d=c.animation&&!!this.animate&&a.renderer.isSVG,e=this.visible?"visible":"hidden",f=c.zIndex,g=this.hasRendered,h=a.seriesGroup;b=this.plotGroup("group",
249 "series",e,f,h);this.markerGroup=this.plotGroup("markerGroup","markers",e,f,h);d&&this.animate(!0);this.getAttribs();b.inverted=this.isCartesian?a.inverted:!1;this.drawGraph&&(this.drawGraph(),this.clipNeg());this.drawDataLabels();this.drawPoints();this.options.enableMouseTracking!==!1&&this.drawTracker();a.inverted&&this.invertGroups();c.clip!==!1&&!this.sharedClipKey&&!g&&b.clip(a.clipRect);d?this.animate():g||this.afterAnimate();this.isDirty=this.isDirtyData=!1;this.hasRendered=!0},redraw:function(){var a=
250 this.chart,b=this.isDirtyData,c=this.group,d=this.xAxis,e=this.yAxis;c&&(a.inverted&&c.attr({width:a.plotWidth,height:a.plotHeight}),c.animate({translateX:o(d&&d.left,a.plotLeft),translateY:o(e&&e.top,a.plotTop)}));this.translate();this.setTooltipPoints(!0);this.render();b&&z(this,"updatedData")},setState:function(a){var b=this.options,c=this.graph,d=this.graphNeg,e=b.states,b=b.lineWidth,a=a||"";if(this.state!==a)this.state=a,e[a]&&e[a].enabled===!1||(a&&(b=e[a].lineWidth||b+1),c&&!c.dashstyle&&
251 (a={"stroke-width":b},c.attr(a),d&&d.attr(a)))},setVisible:function(a,b){var c=this,d=c.chart,e=c.legendItem,f,g=d.options.chart.ignoreHiddenSeries,h=c.visible;f=(c.visible=a=c.userOptions.visible=a===w?!h:a)?"show":"hide";n(["group","dataLabelsGroup","markerGroup","tracker"],function(a){if(c[a])c[a][f]()});if(d.hoverSeries===c)c.onMouseOut();e&&d.legend.colorizeItem(c,a);c.isDirty=!0;c.options.stacking&&n(d.series,function(a){if(a.options.stacking&&a.visible)a.isDirty=!0});n(c.linkedSeries,function(b){b.setVisible(a,
252 !1)});if(g)d.isDirtyBox=!0;b!==!1&&d.redraw();z(c,f)},show:function(){this.setVisible(!0)},hide:function(){this.setVisible(!1)},select:function(a){this.selected=a=a===w?!this.selected:a;if(this.checkbox)this.checkbox.checked=a;z(this,a?"select":"unselect")},drawTracker:function(){var a=this,b=a.options,c=b.trackByArea,d=[].concat(c?a.areaPath:a.graphPath),e=d.length,f=a.chart,g=f.pointer,h=f.renderer,i=f.options.tooltip.snap,j=a.tracker,k=b.cursor,l=k&&{cursor:k},k=a.singlePoints,m,p=function(){if(f.hoverSeries!==
253 a)a.onMouseOver()};if(e&&!c)for(m=e+1;m--;)d[m]==="M"&&d.splice(m+1,0,d[m+1]-i,d[m+2],"L"),(m&&d[m]==="M"||m===e)&&d.splice(m,0,"L",d[m-2]+i,d[m-1]);for(m=0;m<k.length;m++)e=k[m],d.push("M",e.plotX-i,e.plotY,"L",e.plotX+i,e.plotY);j?j.attr({d:d}):(a.tracker=h.path(d).attr({"stroke-linejoin":"round",visibility:a.visible?"visible":"hidden",stroke:Qb,fill:c?Qb:S,"stroke-width":b.lineWidth+(c?0:2*i),zIndex:2}).add(a.group),n([a.tracker,a.markerGroup],function(a){a.addClass("highcharts-tracker").on("mouseover",
254 p).on("mouseout",function(a){g.onTrackerMouseOut(a)}).css(l);if(ib)a.on("touchstart",p)}))}};G=ha(Q);W.line=G;Y.area=x(X,{threshold:0});G=ha(Q,{type:"area",getSegments:function(){var a=[],b=[],c=[],d=this.xAxis,e=this.yAxis,f=e.stacks[this.stackKey],g={},h,i,j=this.points,k=this.options.connectNulls,l,m,p;if(this.options.stacking&&!this.cropped){for(m=0;m<j.length;m++)g[j[m].x]=j[m];for(p in f)c.push(+p);c.sort(function(a,b){return a-b});n(c,function(a){if(!k||g[a]&&g[a].y!==null)g[a]?b.push(g[a]):
255 (h=d.translate(a),l=f[a].percent?f[a].total?f[a].cum*100/f[a].total:0:f[a].cum,i=e.toPixels(l,!0),b.push({y:null,plotX:h,clientX:h,plotY:i,yBottom:i,onMouseOver:pa}))});b.length&&a.push(b)}else Q.prototype.getSegments.call(this),a=this.segments;this.segments=a},getSegmentPath:function(a){var b=Q.prototype.getSegmentPath.call(this,a),c=[].concat(b),d,e=this.options;d=b.length;var f=this.yAxis.getThreshold(e.threshold),g;d===3&&c.push("L",b[1],b[2]);if(e.stacking&&!this.closedStacks)for(d=a.length-
256 1;d>=0;d--)g=o(a[d].yBottom,f),d<a.length-1&&e.step&&c.push(a[d+1].plotX,g),c.push(a[d].plotX,g);else this.closeSegment(c,a,f);this.areaPath=this.areaPath.concat(c);return b},closeSegment:function(a,b,c){a.push("L",b[b.length-1].plotX,c,"L",b[0].plotX,c)},drawGraph:function(){this.areaPath=[];Q.prototype.drawGraph.apply(this);var a=this,b=this.areaPath,c=this.options,d=c.negativeColor,e=c.negativeFillColor,f=[["area",this.color,c.fillColor]];(d||e)&&f.push(["areaNeg",d,e]);n(f,function(d){var e=d[0],
257 f=a[e];f?f.animate({d:b}):a[e]=a.chart.renderer.path(b).attr({fill:o(d[2],ra(d[1]).setOpacity(o(c.fillOpacity,0.75)).get()),zIndex:0}).add(a.group)})},drawLegendSymbol:function(a,b){b.legendSymbol=this.chart.renderer.rect(0,a.baseline-11,a.options.symbolWidth,12,2).attr({zIndex:3}).add(b.legendGroup)}});W.area=G;Y.spline=x(X);F=ha(Q,{type:"spline",getPointSpline:function(a,b,c){var d=b.plotX,e=b.plotY,f=a[c-1],g=a[c+1],h,i,j,k;if(f&&g){a=f.plotY;j=g.plotX;var g=g.plotY,l;h=(1.5*d+f.plotX)/2.5;i=(1.5*
258 e+a)/2.5;j=(1.5*d+j)/2.5;k=(1.5*e+g)/2.5;l=(k-i)*(j-d)/(j-h)+e-k;i+=l;k+=l;i>a&&i>e?(i=s(a,e),k=2*e-i):i<a&&i<e&&(i=I(a,e),k=2*e-i);k>g&&k>e?(k=s(g,e),i=2*e-k):k<g&&k<e&&(k=I(g,e),i=2*e-k);b.rightContX=j;b.rightContY=k}c?(b=["C",f.rightContX||f.plotX,f.rightContY||f.plotY,h||d,i||e,d,e],f.rightContX=f.rightContY=null):b=["M",d,e];return b}});W.spline=F;Y.areaspline=x(Y.area);ma=G.prototype;F=ha(F,{type:"areaspline",closedStacks:!0,getSegmentPath:ma.getSegmentPath,closeSegment:ma.closeSegment,drawGraph:ma.drawGraph,
259 drawLegendSymbol:ma.drawLegendSymbol});W.areaspline=F;Y.column=x(X,{borderColor:"#FFFFFF",borderWidth:1,borderRadius:0,groupPadding:0.2,marker:null,pointPadding:0.1,minPointLength:0,cropThreshold:50,pointRange:null,states:{hover:{brightness:0.1,shadow:!1},select:{color:"#C0C0C0",borderColor:"#000000",shadow:!1}},dataLabels:{align:null,verticalAlign:null,y:null},stickyTracking:!1,threshold:0});F=ha(Q,{type:"column",pointAttrToOptions:{stroke:"borderColor","stroke-width":"borderWidth",fill:"color",
260 r:"borderRadius"},cropShoulder:0,trackerGroups:["group","dataLabelsGroup"],negStacks:!0,init:function(){Q.prototype.init.apply(this,arguments);var a=this,b=a.chart;b.hasRendered&&n(b.series,function(b){if(b.type===a.type)b.isDirty=!0})},getColumnMetrics:function(){var a=this,b=a.options,c=a.xAxis,d=a.yAxis,e=c.reversed,f,g={},h,i=0;b.grouping===!1?i=1:n(a.chart.series,function(b){var c=b.options,e=b.yAxis;if(b.type===a.type&&b.visible&&d.len===e.len&&d.pos===e.pos)c.stacking?(f=b.stackKey,g[f]===
261 w&&(g[f]=i++),h=g[f]):c.grouping!==!1&&(h=i++),b.columnIndex=h});var c=I(N(c.transA)*(c.ordinalSlope||b.pointRange||c.closestPointRange||1),c.len),j=c*b.groupPadding,k=(c-2*j)/i,l=b.pointWidth,b=u(l)?(k-l)/2:k*b.pointPadding,l=o(l,k-2*b);return a.columnMetrics={width:l,offset:b+(j+((e?i-(a.columnIndex||0):a.columnIndex)||0)*k-c/2)*(e?-1:1)}},translate:function(){var a=this.chart,b=this.options,c=b.borderWidth,d=this.yAxis,e=this.translatedThreshold=d.getThreshold(b.threshold),f=o(b.minPointLength,
262 5),b=this.getColumnMetrics(),g=b.width,h=this.barW=xa(s(g,1+2*c)),i=this.pointXOffset=b.offset,j=-(c%2?0.5:0),k=c%2?0.5:1;a.renderer.isVML&&a.inverted&&(k+=1);Q.prototype.translate.apply(this);n(this.points,function(a){var b=o(a.yBottom,e),c=I(s(-999-b,a.plotY),d.len+999+b),n=a.plotX+i,u=h,r=I(c,b),w,c=s(c,b)-r;N(c)<f&&f&&(c=f,r=t(N(r-e)>f?b-f:e-(d.translate(a.y,0,1,0,1)<=e?f:0)));a.barX=n;a.pointWidth=g;b=N(n)<0.5;u=t(n+u)+j;n=t(n)+j;u-=n;w=N(r)<0.5;c=t(r+c)+k;r=t(r)+k;c-=r;b&&(n+=1,u-=1);w&&(r-=
263 1,c+=1);a.shapeType="rect";a.shapeArgs={x:n,y:r,width:u,height:c}})},getSymbol:pa,drawLegendSymbol:G.prototype.drawLegendSymbol,drawGraph:pa,drawPoints:function(){var a=this,b=a.options,c=a.chart.renderer,d;n(a.points,function(e){var f=e.plotY,g=e.graphic;if(f!==w&&!isNaN(f)&&e.y!==null)d=e.shapeArgs,g?(Wa(g),g.animate(x(d))):e.graphic=c[e.shapeType](d).attr(e.pointAttr[e.selected?"select":""]).add(a.group).shadow(b.shadow,null,b.stacking&&!b.borderRadius);else if(g)e.graphic=g.destroy()})},drawTracker:function(){var a=
264 this,b=a.chart,c=b.pointer,d=a.options.cursor,e=d&&{cursor:d},f=function(c){var d=c.target,e;if(b.hoverSeries!==a)a.onMouseOver();for(;d&&!e;)e=d.point,d=d.parentNode;if(e!==w&&e!==b.hoverPoint)e.onMouseOver(c)};n(a.points,function(a){if(a.graphic)a.graphic.element.point=a;if(a.dataLabel)a.dataLabel.element.point=a});if(!a._hasTracking)n(a.trackerGroups,function(b){if(a[b]&&(a[b].addClass("highcharts-tracker").on("mouseover",f).on("mouseout",function(a){c.onTrackerMouseOut(a)}).css(e),ib))a[b].on("touchstart",
265 f)}),a._hasTracking=!0},alignDataLabel:function(a,b,c,d,e){var f=this.chart,g=f.inverted,h=a.dlBox||a.shapeArgs,i=a.below||a.plotY>o(this.translatedThreshold,f.plotSizeY),j=o(c.inside,!!this.options.stacking);if(h&&(d=x(h),g&&(d={x:f.plotWidth-d.y-d.height,y:f.plotHeight-d.x-d.width,width:d.height,height:d.width}),!j))g?(d.x+=i?0:d.width,d.width=0):(d.y+=i?d.height:0,d.height=0);c.align=o(c.align,!g||j?"center":i?"right":"left");c.verticalAlign=o(c.verticalAlign,g||j?"middle":i?"top":"bottom");Q.prototype.alignDataLabel.call(this,
266 a,b,c,d,e)},animate:function(a){var b=this.yAxis,c=this.options,d=this.chart.inverted,e={};if(Z)a?(e.scaleY=0.001,a=I(b.pos+b.len,s(b.pos,b.toPixels(c.threshold))),d?e.translateX=a-b.len:e.translateY=a,this.group.attr(e)):(e.scaleY=1,e[d?"translateX":"translateY"]=b.pos,this.group.animate(e,this.options.animation),this.animate=null)},remove:function(){var a=this,b=a.chart;b.hasRendered&&n(b.series,function(b){if(b.type===a.type)b.isDirty=!0});Q.prototype.remove.apply(a,arguments)}});W.column=F;Y.bar=
267 x(Y.column);ma=ha(F,{type:"bar",inverted:!0});W.bar=ma;Y.scatter=x(X,{lineWidth:0,tooltip:{headerFormat:'<span style="font-size: 10px; color:{series.color}">{series.name}</span><br/>',pointFormat:"x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>",followPointer:!0},stickyTracking:!1});ma=ha(Q,{type:"scatter",sorted:!1,requireSorting:!1,noSharedTooltip:!0,trackerGroups:["markerGroup"],drawTracker:F.prototype.drawTracker,setTooltipPoints:pa});W.scatter=ma;Y.pie=x(X,{borderColor:"#FFFFFF",borderWidth:1,
268 center:[null,null],clip:!1,colorByPoint:!0,dataLabels:{distance:30,enabled:!0,formatter:function(){return this.point.name}},ignoreHiddenPoint:!0,legendType:"point",marker:null,size:null,showInLegend:!1,slicedOffset:10,states:{hover:{brightness:0.1,shadow:!1}},stickyTracking:!1,tooltip:{followPointer:!0}});X={type:"pie",isCartesian:!1,pointClass:ha(Pa,{init:function(){Pa.prototype.init.apply(this,arguments);var a=this,b;if(a.y<0)a.y=null;r(a,{visible:a.visible!==!1,name:o(a.name,"Slice")});b=function(b){a.slice(b.type===
269 "select")};J(a,"select",b);J(a,"unselect",b);return a},setVisible:function(a){var b=this,c=b.series,d=c.chart,e;b.visible=b.options.visible=a=a===w?!b.visible:a;c.options.data[qa(b,c.data)]=b.options;e=a?"show":"hide";n(["graphic","dataLabel","connector","shadowGroup"],function(a){if(b[a])b[a][e]()});b.legendItem&&d.legend.colorizeItem(b,a);if(!c.isDirty&&c.options.ignoreHiddenPoint)c.isDirty=!0,d.redraw()},slice:function(a,b,c){var d=this.series;La(c,d.chart);o(b,!0);this.sliced=this.options.sliced=
270 a=u(a)?a:!this.sliced;d.options.data[qa(this,d.data)]=this.options;a=a?this.slicedTranslation:{translateX:0,translateY:0};this.graphic.animate(a);this.shadowGroup&&this.shadowGroup.animate(a)}}),requireSorting:!1,noSharedTooltip:!0,trackerGroups:["group","dataLabelsGroup"],pointAttrToOptions:{stroke:"borderColor","stroke-width":"borderWidth",fill:"color"},getColor:pa,animate:function(a){var b=this,c=b.points,d=b.startAngleRad;if(!a)n(c,function(a){var c=a.graphic,a=a.shapeArgs;c&&(c.attr({r:b.center[3]/
271 2,start:d,end:d}),c.animate({r:a.r,start:a.start,end:a.end},b.options.animation))}),b.animate=null},setData:function(a,b){Q.prototype.setData.call(this,a,!1);this.processData();this.generatePoints();o(b,!0)&&this.chart.redraw()},generatePoints:function(){var a,b=0,c,d,e,f=this.options.ignoreHiddenPoint;Q.prototype.generatePoints.call(this);c=this.points;d=c.length;for(a=0;a<d;a++)e=c[a],b+=f&&!e.visible?0:e.y;this.total=b;for(a=0;a<d;a++)e=c[a],e.percentage=b>0?e.y/b*100:0,e.total=b},getCenter:function(){var a=
272 this.options,b=this.chart,c=2*(a.slicedOffset||0),d,e=b.plotWidth-2*c,f=b.plotHeight-2*c,b=a.center,a=[o(b[0],"50%"),o(b[1],"50%"),a.size||"100%",a.innerSize||0],g=I(e,f),h;return Na(a,function(a,b){h=/%$/.test(a);d=b<2||b===2&&h;return(h?[e,f,g,g][b]*C(a)/100:a)+(d?c:0)})},translate:function(a){this.generatePoints();var b=0,c=this.options,d=c.slicedOffset,e=d+c.borderWidth,f,g,h,i=c.startAngle||0,j=this.startAngleRad=ya/180*(i-90),i=(this.endAngleRad=ya/180*((c.endAngle||i+360)-90))-j,k=this.points,
273 l=c.dataLabels.distance,c=c.ignoreHiddenPoint,m,n=k.length,o;if(!a)this.center=a=this.getCenter();this.getX=function(b,c){h=R.asin((b-a[1])/(a[2]/2+l));return a[0]+(c?-1:1)*V(h)*(a[2]/2+l)};for(m=0;m<n;m++){o=k[m];f=j+b*i;if(!c||o.visible)b+=o.percentage/100;g=j+b*i;o.shapeType="arc";o.shapeArgs={x:a[0],y:a[1],r:a[2]/2,innerR:a[3]/2,start:t(f*1E3)/1E3,end:t(g*1E3)/1E3};h=(g+f)/2;h>0.75*i&&(h-=2*ya);o.slicedTranslation={translateX:t(V(h)*d),translateY:t(ca(h)*d)};f=V(h)*a[2]/2;g=ca(h)*a[2]/2;o.tooltipPos=
274 [a[0]+f*0.7,a[1]+g*0.7];o.half=h<-ya/2||h>ya/2?1:0;o.angle=h;e=I(e,l/2);o.labelPos=[a[0]+f+V(h)*l,a[1]+g+ca(h)*l,a[0]+f+V(h)*e,a[1]+g+ca(h)*e,a[0]+f,a[1]+g,l<0?"center":o.half?"right":"left",h]}},setTooltipPoints:pa,drawGraph:null,drawPoints:function(){var a=this,b=a.chart.renderer,c,d,e=a.options.shadow,f,g;if(e&&!a.shadowGroup)a.shadowGroup=b.g("shadow").add(a.group);n(a.points,function(h){d=h.graphic;g=h.shapeArgs;f=h.shadowGroup;if(e&&!f)f=h.shadowGroup=b.g("shadow").add(a.shadowGroup);c=h.sliced?
275 h.slicedTranslation:{translateX:0,translateY:0};f&&f.attr(c);d?d.animate(r(g,c)):h.graphic=d=b.arc(g).setRadialReference(a.center).attr(h.pointAttr[h.selected?"select":""]).attr({"stroke-linejoin":"round"}).attr(c).add(a.group).shadow(e,f);h.visible===!1&&h.setVisible(!1)})},sortByAngle:function(a,b){a.sort(function(a,d){return a.angle!==void 0&&(d.angle-a.angle)*b})},drawDataLabels:function(){var a=this,b=a.data,c,d=a.chart,e=a.options.dataLabels,f=o(e.connectorPadding,10),g=o(e.connectorWidth,1),
276 h=d.plotWidth,d=d.plotHeight,i,j,k=o(e.softConnector,!0),l=e.distance,m=a.center,p=m[2]/2,q=m[1],u=l>0,r,w,v,x,C=[[],[]],y,z,E,H,B,D=[0,0,0,0],I=function(a,b){return b.y-a.y};if(a.visible&&(e.enabled||a._hasPointLabels)){Q.prototype.drawDataLabels.apply(a);n(b,function(a){a.dataLabel&&C[a.half].push(a)});for(H=0;!x&&b[H];)x=b[H]&&b[H].dataLabel&&(b[H].dataLabel.getBBox().height||21),H++;for(H=2;H--;){var b=[],K=[],G=C[H],J=G.length,F;a.sortByAngle(G,H-0.5);if(l>0){for(B=q-p-l;B<=q+p+l;B+=x)b.push(B);
277 w=b.length;if(J>w){c=[].concat(G);c.sort(I);for(B=J;B--;)c[B].rank=B;for(B=J;B--;)G[B].rank>=w&&G.splice(B,1);J=G.length}for(B=0;B<J;B++){c=G[B];v=c.labelPos;c=9999;var O,M;for(M=0;M<w;M++)O=N(b[M]-v[1]),O<c&&(c=O,F=M);if(F<B&&b[B]!==null)F=B;else for(w<J-B+F&&b[B]!==null&&(F=w-J+B);b[F]===null;)F++;K.push({i:F,y:b[F]});b[F]=null}K.sort(I)}for(B=0;B<J;B++){c=G[B];v=c.labelPos;r=c.dataLabel;E=c.visible===!1?"hidden":"visible";c=v[1];if(l>0){if(w=K.pop(),F=w.i,z=w.y,c>z&&b[F+1]!==null||c<z&&b[F-1]!==
278 null)z=c}else z=c;y=e.justify?m[0]+(H?-1:1)*(p+l):a.getX(F===0||F===b.length-1?c:z,H);r._attr={visibility:E,align:v[6]};r._pos={x:y+e.x+({left:f,right:-f}[v[6]]||0),y:z+e.y-10};r.connX=y;r.connY=z;if(this.options.size===null)w=r.width,y-w<f?D[3]=s(t(w-y+f),D[3]):y+w>h-f&&(D[1]=s(t(y+w-h+f),D[1])),z-x/2<0?D[0]=s(t(-z+x/2),D[0]):z+x/2>d&&(D[2]=s(t(z+x/2-d),D[2]))}}if(va(D)===0||this.verifyDataLabelOverflow(D))this.placeDataLabels(),u&&g&&n(this.points,function(b){i=b.connector;v=b.labelPos;if((r=b.dataLabel)&&
279 r._pos)E=r._attr.visibility,y=r.connX,z=r.connY,j=k?["M",y+(v[6]==="left"?5:-5),z,"C",y,z,2*v[2]-v[4],2*v[3]-v[5],v[2],v[3],"L",v[4],v[5]]:["M",y+(v[6]==="left"?5:-5),z,"L",v[2],v[3],"L",v[4],v[5]],i?(i.animate({d:j}),i.attr("visibility",E)):b.connector=i=a.chart.renderer.path(j).attr({"stroke-width":g,stroke:e.connectorColor||b.color||"#606060",visibility:E}).add(a.group);else if(i)b.connector=i.destroy()})}},verifyDataLabelOverflow:function(a){var b=this.center,c=this.options,d=c.center,e=c=c.minSize||
280 80,f;d[0]!==null?e=s(b[2]-s(a[1],a[3]),c):(e=s(b[2]-a[1]-a[3],c),b[0]+=(a[3]-a[1])/2);d[1]!==null?e=s(I(e,b[2]-s(a[0],a[2])),c):(e=s(I(e,b[2]-a[0]-a[2]),c),b[1]+=(a[0]-a[2])/2);e<b[2]?(b[2]=e,this.translate(b),n(this.points,function(a){if(a.dataLabel)a.dataLabel._pos=null}),this.drawDataLabels()):f=!0;return f},placeDataLabels:function(){n(this.points,function(a){var a=a.dataLabel,b;if(a)(b=a._pos)?(a.attr(a._attr),a[a.moved?"animate":"attr"](b),a.moved=!0):a&&a.attr({y:-999})})},alignDataLabel:pa,
281 drawTracker:F.prototype.drawTracker,drawLegendSymbol:G.prototype.drawLegendSymbol,getSymbol:pa};X=ha(Q,X);W.pie=X;r(Highcharts,{Axis:db,Chart:yb,Color:ra,Legend:eb,Pointer:xb,Point:Pa,Tick:Ma,Tooltip:wb,Renderer:Va,Series:Q,SVGElement:wa,SVGRenderer:Ha,arrayMin:Ja,arrayMax:va,charts:Ga,dateFormat:Xa,format:Ca,pathAnim:Ab,getOptions:function(){return M},hasBidiBug:Ub,isTouchDevice:Ob,numberFormat:Aa,seriesTypes:W,setOptions:function(a){M=x(M,a);Lb();return M},addEvent:J,removeEvent:aa,createElement:U,
282 discardElement:Ta,css:K,each:n,extend:r,map:Na,merge:x,pick:o,splat:ja,extendClass:ha,pInt:C,wrap:mb,svg:Z,canvas:$,vml:!Z&&!$,product:"Highcharts",version:"3.0.6"})})();
0 /*
1 HTML5 Shiv v3.6.2pre | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
2 */
3 (function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag();
4 a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/\w+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x<style>article,aside,figcaption,figure,footer,header,hgroup,nav,section{display:block}mark{background:#FF0;color:#000}</style>";
5 c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="<xyz></xyz>";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode||
6 "undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",version:"3.6.2pre",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f);if(g)return a.createDocumentFragment();
7 for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;d<h;d++)c.createElement(e[d]);return c}};l.html5=e;q(f)})(this,document);
0 /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */
1 /*! NOTE: If you're already including a window.matchMedia polyfill via Modernizr or otherwise, you don't need this part */
2 window.matchMedia=window.matchMedia||function(a){"use strict";var c,d=a.documentElement,e=d.firstElementChild||d.firstChild,f=a.createElement("body"),g=a.createElement("div");return g.id="mq-test-1",g.style.cssText="position:absolute;top:-100em",f.style.background="none",f.appendChild(g),function(a){return g.innerHTML='&shy;<style media="'+a+'"> #mq-test-1 { width: 42px; }</style>',d.insertBefore(f,e),c=42===g.offsetWidth,d.removeChild(f),{matches:c,media:a}}}(document);
3
4 /*! Respond.js v1.1.0: min/max-width media query polyfill. (c) Scott Jehl. MIT/GPLv2 Lic. j.mp/respondjs */
5 (function(a){"use strict";function x(){u(!0)}var b={};a.respond=b,b.update=function(){},b.mediaQueriesSupported=a.matchMedia&&a.matchMedia("only all").matches,b.mediaQueriesSupported;var q,r,t,c=a.document,d=c.documentElement,e=[],f=[],g=[],h={},i=30,j=c.getElementsByTagName("head")[0]||d,k=c.getElementsByTagName("base")[0],l=j.getElementsByTagName("link"),m=[],n=function(){for(var b=0;l.length>b;b++){var c=l[b],d=c.href,e=c.media,f=c.rel&&"stylesheet"===c.rel.toLowerCase();d&&f&&!h[d]&&(c.styleSheet&&c.styleSheet.rawCssText?(p(c.styleSheet.rawCssText,d,e),h[d]=!0):(!/^([a-zA-Z:]*\/\/)/.test(d)&&!k||d.replace(RegExp.$1,"").split("/")[0]===a.location.host)&&m.push({href:d,media:e}))}o()},o=function(){if(m.length){var a=m.shift();v(a.href,function(b){p(b,a.href,a.media),h[a.href]=!0,setTimeout(function(){o()},0)})}},p=function(a,b,c){var d=a.match(/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi),g=d&&d.length||0;b=b.substring(0,b.lastIndexOf("/"));var h=function(a){return a.replace(/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,"$1"+b+"$2$3")},i=!g&&c;b.length&&(b+="/"),i&&(g=1);for(var j=0;g>j;j++){var k,l,m,n;i?(k=c,f.push(h(a))):(k=d[j].match(/@media *([^\{]+)\{([\S\s]+?)$/)&&RegExp.$1,f.push(RegExp.$2&&h(RegExp.$2))),m=k.split(","),n=m.length;for(var o=0;n>o;o++)l=m[o],e.push({media:l.split("(")[0].match(/(only\s+)?([a-zA-Z]+)\s?/)&&RegExp.$2||"all",rules:f.length-1,hasquery:l.indexOf("(")>-1,minw:l.match(/\(min\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:l.match(/\(max\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},s=function(){var a,b=c.createElement("div"),e=c.body,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",e||(e=f=c.createElement("body"),e.style.background="none"),e.appendChild(b),d.insertBefore(e,d.firstChild),a=b.offsetWidth,f?d.removeChild(e):e.removeChild(b),a=t=parseFloat(a)},u=function(a){var b="clientWidth",h=d[b],k="CSS1Compat"===c.compatMode&&h||c.body[b]||h,m={},n=l[l.length-1],o=(new Date).getTime();if(a&&q&&i>o-q)return clearTimeout(r),r=setTimeout(u,i),void 0;q=o;for(var p in e)if(e.hasOwnProperty(p)){var v=e[p],w=v.minw,x=v.maxw,y=null===w,z=null===x,A="em";w&&(w=parseFloat(w)*(w.indexOf(A)>-1?t||s():1)),x&&(x=parseFloat(x)*(x.indexOf(A)>-1?t||s():1)),v.hasquery&&(y&&z||!(y||k>=w)||!(z||x>=k))||(m[v.media]||(m[v.media]=[]),m[v.media].push(f[v.rules]))}for(var B in g)g.hasOwnProperty(B)&&g[B]&&g[B].parentNode===j&&j.removeChild(g[B]);for(var C in m)if(m.hasOwnProperty(C)){var D=c.createElement("style"),E=m[C].join("\n");D.type="text/css",D.media=C,j.insertBefore(D,n.nextSibling),D.styleSheet?D.styleSheet.cssText=E:D.appendChild(c.createTextNode(E)),g.push(D)}},v=function(a,b){var c=w();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))},w=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}();n(),b.update=n,a.addEventListener?a.addEventListener("resize",x,!1):a.attachEvent&&a.attachEvent("onresize",x)})(this);
0 var Serf = (function() {
1
2 function initialize (){
3 Serf.Util.runIfClassNamePresent('page-home', initHome);
4 }
5
6 function initHome() {
7 if(!Serf.Util.isMobile){
8 Serf.Nodes.init();
9 }else{
10 Serf.Home.mobileHero();
11 }
12
13 }
14
15 //api
16 return {
17 initialize: initialize
18 }
19
20 })();
21
22 var Serf = Serf || {};
23
24 (function () {
25
26 //check for mobile user agents
27 var isMobile = (function(){
28 if( navigator.userAgent.match(/Android/i)
29 || navigator.userAgent.match(/webOS/i)
30 || navigator.userAgent.match(/iPhone/i)
31 //|| navigator.userAgent.match(/iPad/i)
32 || navigator.userAgent.match(/iPod/i)
33 || navigator.userAgent.match(/BlackBerry/i)
34 || navigator.userAgent.match(/Windows Phone/i)
35 ){
36 return true;
37 }
38 else {
39 return false;
40 }
41 })()
42
43 // calls the given function if the given classname is found
44 function runIfClassNamePresent(selector, initFunction) {
45 var elms = document.getElementsByClassName(selector);
46 if (elms.length > 0) {
47 initFunction();
48 }
49 }
50
51 Serf.Util = {};
52 Serf.Util.isMobile = isMobile;
53 Serf.Util.runIfClassNamePresent = runIfClassNamePresent;
54
55 })();
56
57 var Serf = Serf || {};
58
59 (function () {
60
61 // calls the given function if the given classname is found
62 function mobileHero() {
63 var jumbo = document.getElementById('jumbotron');
64 jumbo.className = jumbo.className + ' mobile-hero';
65 }
66
67 Serf.Home = {};
68 Serf.Home.mobileHero = mobileHero;
69
70 })();
71
72 var Serf = Serf || {};
73
74 (function () {
75
76 var width = 1400,
77 height = 490,
78 border = 50,
79 numberNodes = 128,
80 linkGroup = 0;
81 //nodeLinks = [];
82
83 var nodes = [];
84 for (i=0; i<numberNodes; i++) {
85 nodes.push({
86 x: Math.random() * (width - border) + (border / 2),
87 y: Math.random() * (height - border) + (border / 2),
88 });
89 }
90
91 var fill = d3.scale.category20();
92
93 var force = d3.layout.force()
94 .size([width, height])
95 .nodes(nodes)
96 .linkDistance(60)
97 .charge(-1)
98 .gravity(0.0004)
99 .on("tick", tick);
100
101 var svg = d3.select("#jumbotron").append("svg")
102 .attr('id', 'node-canvas')
103 .attr("width", width)
104 .attr("height", height)
105
106 //set left value after adding to dom
107 resize();
108
109 svg.append("rect")
110 .attr("width", width)
111 .attr("height", height);
112
113 var nodes = force.nodes(),
114 links = force.links(),
115 node = svg.selectAll(".node"),
116 link = svg.selectAll(".link");
117
118 var cursor = svg.append("circle")
119 .attr("r", 30)
120 .attr("transform", "translate(-100,-100)")
121 .attr("class", "cursor");
122
123
124 function createLink(index) {
125 var node = nodes[index];
126 var nodeSelected = svg.select("#id_" + node.index).classed("active linkgroup_"+ linkGroup, true);
127
128 var distMap = {};
129 var distances = [];
130
131 for (var i=0; i<nodes.length; i++) {
132 if (i == index) {
133 continue
134 }
135
136 var target = nodes[i];
137 var selected = svg.select("#id_" + i);
138 var dx = selected.attr('cx') - nodeSelected.attr('cx');
139 var dy = selected.attr('cy') - nodeSelected.attr('cy');
140 var dist = Math.sqrt(dx * dx + dy * dy)
141
142 if (dist in distMap) {
143 distMap[dist].push(target)
144 } else {
145 distMap[dist] = [target]
146 }
147 distances.push(dist)
148 }
149
150 distances.sort(d3.ascending);
151 for (i = 0; i < 3; i++) {
152 var dist = distances[i]
153 var target = distMap[dist].pop()
154 var link = {
155 source: node,
156 target: target
157 }
158 links.push(link);
159 }
160
161 restart();
162 }
163
164
165 function tick() {
166 link.attr("x1", function(d) { return d.source.x; })
167 .attr("y1", function(d) { return d.source.y; })
168 .attr("x2", function(d) { return d.target.x; })
169 .attr("y2", function(d) { return d.target.y; });
170
171 node.attr("cx", function(d) { return d.x; })
172 .attr("cy", function(d) { return d.y; });
173 }
174
175
176 function restart() {
177
178 node = node.data(nodes);
179
180 node.enter().insert("circle", ".cursor")
181 .attr("class", "node")
182 .attr("r", 12)
183 .attr("id", function (d, i) {
184 return ("id_" + i)
185 })
186 .call(force.drag);
187
188 link = link.data(links);
189
190 link.enter().insert("line", ".node")
191 .attr("class", "link active linkgroup_"+ linkGroup);
192
193 force.start();
194
195 resetLink(linkGroup);
196 linkGroup++;
197 }
198
199 function resetLink(num){
200 setTimeout(resetColors, 700, num)
201 }
202
203 function resetColors(num){
204 svg.selectAll(".linkgroup_"+ num).classed('active', false)
205 }
206
207 window.onresize = function(){
208 resize();
209 }
210
211 function resize() {
212 var nodeC = document.getElementById('node-canvas');
213 wW = window.innerWidth;
214
215 nodeC.style.left = ((wW - width) / 2 ) + 'px';
216 }
217
218 //kick things off
219 function init() {
220 restart();
221 for (i=0;i<numberNodes;i++) {
222 setTimeout(createLink, 700*i+1000, i);
223 }
224 }
225
226 Serf.Nodes = {};
227 Serf.Nodes.init = init;
228
229 })();
0 function graph_settings() {
1 return {
2 chart: {
3 type: 'spline'
4 },
5 title: {
6 text: null
7 },
8 xAxis: {
9 title: {
10 text: "Time (sec)",
11 }
12 },
13 yAxis: {
14 title: {
15 text: 'Convergence %'
16 },
17 min: 0.0,
18 max: 100.0
19 },
20 tooltip: {
21 formatter: function() {
22 return '<b>'+ (Math.round(this.y*1000.0)/1000.0) +'%</b><br/>'
23 }
24 },
25 legend: {
26 enabled: false
27 },
28 series: [{
29 name: 'Convergence Rate',
30 data: []
31 }]
32 }
33 }
34
35 function create_graph() {
36 $('#graph').highcharts(graph_settings())
37 return $('#graph').highcharts()
38 }
39
40 var Simulator = Class.$extend({
41 __init__ : function(graph, bytes, maxConverge) {
42 this.graph = graph
43 this.bytes = bytes
44 this.maxConverge = maxConverge
45 this.interval = 0.2
46 this.fanout = 3
47 this.nodes = 30
48 this.packetLoss = 0
49 this.nodeFail = 0
50 },
51
52 convergenceAtRound: function(x) {
53 var contact = (this.fanout / this.nodes) * (1 - this.packetLoss) * (1 - this.nodeFail) * 0.5
54 var numInf = this.nodes / (1 + (this.nodes+1) * Math.pow(Math.E, -1*contact*this.nodes*x))
55 return numInf / this.nodes
56 },
57
58 roundLength: function() {
59 return this.interval
60 },
61
62 seriesData: function() {
63 var data = []
64 var lastVal = 0
65 var round = 0
66 var roundLength = this.roundLength()
67 while (lastVal < this.maxConverge && round < 100) {
68 lastVal = this.convergenceAtRound(round)
69 data.push([round * roundLength, lastVal*100.0])
70 round++
71 }
72 return data
73 },
74
75 bytesUsed: function() {
76 var roundLength = this.roundLength()
77 var roundsPerSec = 1 / roundLength
78 var packetSize = 1400
79 var send = packetSize * this.fanout * roundsPerSec
80 return send * 2
81 },
82
83 draw: function() {
84 var data = this.seriesData()
85 this.graph.series[0].setData(data, false)
86 this.graph.redraw()
87
88 var kilobits = this.bytesUsed() * 8
89 var used = Math.round((kilobits / 1024) * 10) / 10
90 this.bytes.html(""+used)
91 }
92 })
93
94 function update_interval(elem, simulator) {
95 var val = elem.value
96 var interval = Number(val)
97 if (isNaN(interval)) {
98 alert("Gossip interval must be a number!")
99 return
100 }
101 if (interval <= 0) {
102 alert("Gossip interval must be a positive value!")
103 return
104 }
105 simulator.interval = interval
106 simulator.draw()
107 console.log("Redraw with interval set to: " + interval)
108 }
109
110
111 function update_fanout(elem, simulator) {
112 var val = elem.value
113 var fanout = Number(val)
114 if (isNaN(fanout)) {
115 alert("Gossip fanout must be a number!")
116 return
117 }
118 if (fanout <= 0) {
119 alert("Gossip fanout must be a positive value!")
120 return
121 }
122 simulator.fanout = fanout
123 simulator.draw()
124 console.log("Redraw with fanout set to: " + fanout)
125 }
126
127 function update_nodes(elem, simulator) {
128 var val = elem.value
129 var nodes = Number(val)
130 if (isNaN(nodes)) {
131 alert("Node count must be a number!")
132 return
133 }
134 if (nodes <= 1) {
135 alert("Must have at least one node")
136 return
137 }
138 simulator.nodes = nodes
139 simulator.draw()
140 console.log("Redraw with nodes set to: " + nodes)
141 }
142
143 function update_packetloss(elem, simulator) {
144 var val = elem.value
145 var pkt = Number(val)
146 if (isNaN(pkt)) {
147 alert("Packet loss must be a number!")
148 return
149 }
150 if (pkt < 0 || pkt >= 100) {
151 alert("Packet loss must be greater or equal to 0 and less than 100")
152 return
153 }
154 simulator.packetLoss = (pkt / 100.0)
155 simulator.draw()
156 console.log("Redraw with packet loss set to: " + pkt)
157 }
158
159 function update_failed(elem, simulator) {
160 var val = elem.value
161 var failed = Number(val)
162 if (isNaN(failed)) {
163 alert("Failure rate must be a number!")
164 return
165 }
166 if (failed < 0 || failed >= 100) {
167 alert("Failure rate must be greater or equal to 0 and less than 100")
168 return
169 }
170 simulator.nodeFail = (failed / 100.0)
171 simulator.draw()
172 console.log("Redraw with failure rate set to: " + failed)
173 }
174
175 // wait for dom ready
176 $(function(){
177 var bytes = $("#bytes")
178 var graph = create_graph()
179 var simulator = new Simulator(graph, bytes, 0.9999)
180 simulator.draw()
181
182 var interval = $("#interval")
183 interval.change(function() { update_interval(interval[0], simulator) })
184
185 var fanout = $("#fanout")
186 fanout.change(function() { update_fanout(fanout[0], simulator) })
187
188 var nodes = $("#nodes")
189 nodes.change(function() { update_nodes(nodes[0], simulator) })
190
191 var loss = $("#packetloss")
192 loss.change(function() { update_packetloss(loss[0], simulator) })
193
194 var failed = $("#failed")
195 failed.change(function() { update_failed(failed[0], simulator) })
196 })
0 .people {
1 margin-top: 30px;
2
3 .person {
4 margin-bottom: 40px;
5
6 h3 {
7 text-transform: none;
8 }
9
10 .bio {
11 padding-left: 150px;
12 }
13 }
14 }
0 //
1 // Docs
2 // --------------------------------------------------
3
4 .docs-sidebar{
5 margin-bottom: 30px;
6 margin-top: 50px;
7 margin-right: 4%;
8 background-color: $tan;
9 border-radius: $el-border-radius;
10
11 a{
12 color: $black;
13 }
14
15 .docs-sidenav{
16 padding-top: 15px;
17 padding-bottom: 15px;
18
19 :last-child{
20 border-bottom: none;
21 }
22
23 //all li > a
24 li{
25 position: relative;
26
27 > a{
28 @include transition( color 0.5s ease );
29 }
30
31 > a:hover,
32 > a:focus {
33 background-color: transparent !important;
34 color: $red-dark;
35 @include transition( color 0.5s ease );
36 }
37 }
38
39
40 > li {
41 padding: 10px 0;
42 margin: 0 30px;
43 border-bottom: 1px solid #fff;
44
45 &.active {
46 &:before{
47 content: '';
48 position: absolute;
49 width: 8px;
50 height: 8px;
51 background-color: $red;
52 border-radius: 4px;
53 top: 26px;
54 left: -10px;
55 }
56 > a{
57 font-weight: $font-weight-rales-xb;
58 -webkit-font-smoothing: antialiased;
59 }
60
61 > a:hover,
62 > a:focus {
63 color: $black;
64 }
65
66 .nav {
67 display: block;
68
69 li.active a {
70 color: $red;
71 }
72 }
73 }
74
75 > a {
76 text-transform: uppercase;
77 font-family: $font-family-rales;
78 font-weight: $font-weight-rales-sb;
79 -webkit-font-smoothing: antialiased;
80 }
81 }
82
83 .nav {
84 display: none;
85 margin-bottom: 15px;
86
87 > li{
88 margin-left: 20px;
89
90 > a{
91 -webkit-font-smoothing: antialiased;
92 font-family: $font-family-open-sans;
93 padding: 6px 15px;
94 }
95 }
96 }
97 }
98 }
99
100
101 .bs-docs-section{
102 padding-top: 10px;
103 padding-left: 3%;
104 padding-bottom: 80px;
105
106 .lead{
107 margin-bottom: 48px
108 }
109
110 .doc-sectional{
111 margin-bottom: 48px;
112 }
113
114 p, li, .alert {
115 font-size: 20px;
116 font-family: $font-family-open-sans;
117 font-weight: $font-weight-open;
118 line-height: 1.5em;
119 margin: 0 0 18px;
120 -webkit-font-smoothing: antialiased;
121 }
122
123 pre{
124 margin: 0 0 18px;
125 }
126
127 a{
128 color: $red-darker;
129 &:hover{
130 text-decoration: underline;
131 }
132 }
133
134 h1{
135 padding-bottom: 24px;
136 margin-top: 40px;
137 margin-bottom: 24px;
138 border-bottom: 1px solid #eeeeee;
139 }
140
141 h2, h3, h4{
142 margin-bottom: 16px;
143 }
144
145 #graph {
146 margin-top: 30px;
147 }
148 }
0 .downloads {
1 margin-top: 20px;
2
3 .description {
4 margin-bottom: 20px;
5 }
6
7 .download {
8 border-bottom: 1px solid #b2b2b2;
9 padding-bottom: 15px;
10 margin-top: 10px;
11 margin-bottom: 10px;
12
13 .details {
14 padding-left: 95px;
15
16 h2 {
17 margin-top: 0px;
18 }
19
20 ul {
21 padding-left: 0px;
22 }
23
24 li {
25 display: inline-block;
26
27 &:after {
28 content: " | ";
29 }
30
31 &:last-child:after {
32 content: "";
33 }
34 }
35 }
36
37 .icon {
38 img {
39 width: 75px;
40 }
41 }
42
43 .os-name {
44 font-size: 40px;
45 margin-bottom: -3px;
46 }
47 }
48
49 .poweredby {
50 margin-top: 20px;
51
52 img {
53 display: block;
54 margin: 0 auto;
55 width: 122px;
56 }
57 }
58 }
0 //
1 // Typography
2 // --------------------------------------------------
3
4 //light
5 .rls-l{
6 font-family: $font-family-rales;
7 font-weight: $font-weight-rales-xl;
8 }
9
10 //semibold
11 .rls-sb{
12 font-family: $font-family-rales;
13 font-weight: $font-weight-rales-sb;
14 }
15
16 //extrabold
17 .rls-xb{
18 font-family: $font-family-rales;
19 font-weight: $font-weight-rales-xb;
20 }
21
22 .os{
23 font-family: $font-family-open-sans;
24 font-weight: $font-weight-open;
25 }
0 //
1 // Global Site
2 // --------------------------------------------------
3
4 /*html{
5 text-rendering: optimizeLegibility;
6 -webkit-font-smoothing: antialiased;
7 }*/
8
9 body {
10 font-size: 15px;
11 }
12
13 h1{
14 font-size: 42px;
15 line-height: 42px;
16 font-family: $font-family-rales;
17 font-weight: $font-weight-rales-sb;
18 margin-bottom: 24px;
19 }
20
21 h3{
22 font-size: 28px;
23 line-height: 28px;
24 font-family: $font-family-rales;
25 font-weight: $font-weight-rales-sb;
26 }
27
28 //an alternative color for buttons in the doc body
29 .btn-serf{
30 color: $white !important;
31 background-color: $btn-color;
32 border-radius: $btn-border-radius;
33 //.box-shadow( $shadow );
34 }
35
36 .highlight{
37 margin-bottom: 18px;
38 }
39
40 pre {
41 background-color: $black;
42 color: $white;
43 font-size: 14px;
44 font-weight: normal;
45 font-family: "Courier New", Monaco, Menlo, Consolas, monospace;
46 border: none;
47 padding: 20px;
48 margin-bottom: 0;
49 }
50
51
52 //fixed grid below 992 to prevent smaller responsive sizes
53 @media (max-width: 992px) {
54 .container{
55 max-width: 970px;
56 }
57 }
58
59 //all below styles are overriding corrections for below (min-width: 992px)
60 //below (min-width: 992px) these styles change
61 .navbar-nav {
62 margin: 0;
63 }
64
65 .navbar-right {
66 float: right !important;
67 }
68
69 .navbar-nav > li {
70 float: left;
71 }
72
73 .navbar-nav > li > a {
74 padding-top: 15px;
75 padding-bottom: 15px;
76 }
0 //
1 // Header
2 // --------------------------------------------------
3
4 #footer,
5 #header {
6 color: $white;
7 background-color: $black;
8 text-rendering: optimizeLegibility;
9
10 a{
11 color: $white;
12 }
13
14 .navbar-brand {
15 &.logo{
16 padding: 13px 15px;
17 line-height: 0;
18
19 span{
20 display: inline-block;
21 width: 52px;
22 height: 62px;
23 background: image-url("logo-type-medium.png") 0 0 no-repeat;
24 @include img-retina("logo-type-medium.png", "logo-type-medium@2x.png", 52px, 64px);
25 }
26 }
27
28 &.text{
29 font-size: 36px;
30 line-height: 62px;
31 letter-spacing: 14px;
32 text-shadow: $text-shadow;
33 }
34 }
35
36 .navbar-nav{
37 -webkit-font-smoothing: antialiased;
38 li > a {
39 font-size: 14px;
40 text-transform: uppercase;
41 letter-spacing: 3px;
42 }
43 }
44
45 .nav > li > a:hover, .nav > li > a:focus {
46 background-color: transparent;
47 }
48
49 .main-links.navbar-nav{
50 li + li a::before {
51 content: " | ";
52 padding-right: 15px;
53 }
54
55 li > a {
56 line-height: 62px;
57 text-shadow: $text-shadow;
58 }
59
60 }
61
62 .buttons.navbar-nav{
63 margin-top: 30px;
64 margin-left: 30px;
65 background-color: $red-dark;
66 border-radius: $btn-border-radius;
67 @include box-shadow( $shadow );
68
69 li.first{
70 border-right: 2px solid $red-darker;
71 }
72
73 li > a {
74 padding-top: 6px;
75 padding-bottom: 6px;
76 }
77 }
78 }
79
80 #footer{
81 text-align: center;
82 .main-links.navbar-nav{
83 float: none;
84 display: inline-block;
85 }
86
87
88 .buttons.navbar-nav{
89 float: none;
90 display: inline-block;
91 margin-bottom: 30px;
92 margin-top: 0px;
93 }
94 }
0 //
1 // Home
2 // --------------------------------------------------
3
4 #features{
5 @include anti-alias();
6 padding: 90px 0 0 0;
7
8 .row{
9 padding: 0 0 90px 0;
10 }
11
12 h2{
13 font-size: 32px;
14 letter-spacing: 2px;
15 color: $gray;
16 text-transform: uppercase;
17 font-family: $font-family-rales;
18 font-weight: $font-weight-rales-sb;
19 }
20 p{
21 font-size: 18px;
22 letter-spacing: 1px;
23 line-height: 1.5em;
24 color: $gray-darker;
25 font-family: $font-family-open-sans;
26 font-weight: $font-weight-open;
27 }
28
29 .icn{
30 display: block;
31 width: 286px;
32 height: 286px;
33 margin: 0 auto;
34 background-size: 286px 286px;
35 background-position: 0 0;
36 background-repeat: no-repeat;
37 }
38
39 .icn1{
40 background-image: image-url("gossip-proto-icon@2x.png");
41 }
42 .icn2{
43 background-image: image-url("failure-detect-icon@2x.png");
44 }
45 .icn3{
46 background-image: image-url("custom-event-icon@2x.png");
47 }
48 }
49
50 #trusted{
51 background-color: $black;
52 padding: 140px 0;
53
54 h3{
55 margin-bottom: 60px;
56 color: $white;
57 text-transform: uppercase;
58 font-size: 18px;
59 text-align: center;
60 }
61
62 .trusted-items{
63 width: 800px;
64 margin: 0 auto;
65
66 ul {
67 padding: 0;
68 > li{
69 display: inline-block;
70 float: left;
71 width: 25%;
72 height: 80px;
73 margin: 12px 0;
74 background: image-url("trusted-sprite.png") 0 0 no-repeat;
75
76 &#i0{
77 background-position: 0 0;
78 }
79 &#i1{
80 background-position: 0 -80px;
81 }
82 &#i2{
83 background-position: 0 -160px;
84 }
85 &#i3{
86 background-position: 0 -240px;
87 }
88 &#i4{
89 background-position: 0 -320px;
90 }
91 &#i5{
92 background-position: 0 -400px;
93 }
94 &#i6{
95 background-position: 0 -480px;
96 }
97 &#i7{
98 background-position: 0 -560px;
99 }
100 &#i8{
101 background-position: 0 -640px;
102 }
103 &#i9{
104 background-position: 0 -720px;
105 }
106 &#i10{
107 background-position: 0 -800px;
108 }
109 &#i11{
110 background-position: 0 -880px;
111 }
112 }
113 }
114 }
115 }
116
117
118
119 #footer{
120 padding: 50px 0;
121 background: $black image-url("footer-pattern.jpg");
122
123 .footer-links{
124 margin-bottom: 20px;
125 }
126
127 .footer-logo{
128 span{
129 display: inline-block;
130 width: 206px;
131 height: 206px;
132 margin-bottom: 30px;
133 border-radius: 103px;
134 background: $black image-url("logo-type-medium.png") center center no-repeat;
135 @include img-retina("logo-type-medium.png", "logo-type-medium@2x.png", 72px, 94px);
136 @include box-shadow( 0px 0px 5px rgba(0, 0, 0, 0.04) );
137 }
138 }
139
140 .footer-hashi{
141 letter-spacing: 2px;
142 margin-bottom: 30px;
143 span{
144 margin-right: 20px;
145 }
146 img{
147 display: inline-block;
148 width: 37px;
149 height: 40px;
150 }
151 }
152 }
0 //
1 // Jumbotron
2 // --------------------------------------------------
3
4
5 #jumbotron {
6 position: relative;
7 height: $jumbotron-height;
8 color: $jumbotron-color;
9 background-color: $gray-light;
10 padding-top: 0;
11 padding-bottom: 0;
12 overflow: hidden;
13
14 &.mobile-hero{
15 background: transparent image-url("node-hero-pattern.jpg") center center;
16 background-size: 320px 320px;
17 background: -webkit-image-set(image-url("node-hero-pattern.jpg") 1x, image-url("node-hero-pattern@2x.jpg") 2x);
18 }
19
20 .container{
21 position: relative;
22 height: 100%;
23
24 .jumbo-logo{
25 position: absolute;
26 bottom: 0;
27 right: -150px;
28 width: 726px;
29 height: 454px;
30 background: transparent image-url("logo-circle-logo@2x.png") 0 0 no-repeat;
31 background-size: 726px 454px;
32 z-index: 20;
33 pointer-events: none;
34 }
35
36 .col-lg-5{
37 z-index: 20;
38 pointer-events: none;
39 }
40
41 h2{
42 margin-top: 140px;
43 font-size: 28px;
44 line-height: 38px;
45 letter-spacing: 2px;
46 }
47 }
48 }
49
50 @media (min-width: 992px) {
51 .jumbotron .container .jumbo-logo{
52 right: -150px;
53 }
54 }
55
56 @media (min-width: 1200px) {
57 .jumbotron .container .jumbo-logo{
58 right: -120px;
59 }
60 }
0 //
1 // Mixins
2 // --------------------------------------------------
3
4
5 // Utilities
6 // -------------------------
7
8 // Clearfix
9 // Source: http://nicolasgallagher.com/micro-clearfix-hack/
10 //
11 // For modern browsers
12 // 1. The space content is one way to avoid an Opera bug when the
13 // contenteditable attribute is included anywhere else in the document.
14 // Otherwise it causes space to appear at the top and bottom of elements
15 // that are clearfixed.
16 // 2. The use of `table` rather than `block` is only necessary if using
17 // `:before` to contain the top-margins of child elements.
18 @mixin clearfix() {
19 &:before,
20 &:after {
21 content: " "; /* 1 */
22 display: table; /* 2 */
23 }
24 &:after {
25 clear: both;
26 }
27 }
28
29 // Webkit-style focus
30 @mixin tab-focus() {
31 // Default
32 outline: thin dotted #333;
33 // Webkit
34 outline: 5px auto -webkit-focus-ring-color;
35 outline-offset: -2px;
36 }
37
38 // Center-align a block level element
39 @mixin center-block() {
40 display: block;
41 margin-left: auto;
42 margin-right: auto;
43 }
44
45 // Sizing shortcuts
46 @mixin size($width, $height) {
47 width: $width;
48 height: $height;
49 }
50 @mixin square($size) {
51 @include size($size, $size);
52 }
53
54 // Placeholder text
55 @mixin placeholder($color: $input-color-placeholder) {
56 &:-moz-placeholder { color: $color; } // Firefox 4-18
57 &::-moz-placeholder { color: $color; } // Firefox 19+
58 &:-ms-input-placeholder { color: $color; } // Internet Explorer 10+
59 &::-webkit-input-placeholder { color: $color; } // Safari and Chrome
60 }
61
62 // Text overflow
63 // Requires inline-block or block for proper styling
64 @mixin text-overflow() {
65 overflow: hidden;
66 text-overflow: ellipsis;
67 white-space: nowrap;
68 }
69
70 // CSS image replacement
71 // Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757
72 @mixin hide-text() {
73 font: #{"0/0"} a;
74 color: transparent;
75 text-shadow: none;
76 background-color: transparent;
77 border: 0;
78 }
79
80
81
82 // CSS3 PROPERTIES
83 // --------------------------------------------------
84
85 // Single side border-radius
86 @mixin border-top-radius($radius) {
87 border-top-right-radius: $radius;
88 border-top-left-radius: $radius;
89 }
90 @mixin border-right-radius($radius) {
91 border-bottom-right-radius: $radius;
92 border-top-right-radius: $radius;
93 }
94 @mixin border-bottom-radius($radius) {
95 border-bottom-right-radius: $radius;
96 border-bottom-left-radius: $radius;
97 }
98 @mixin border-left-radius($radius) {
99 border-bottom-left-radius: $radius;
100 border-top-left-radius: $radius;
101 }
102
103 // Drop shadows
104 @mixin box-shadow($shadow) {
105 -webkit-box-shadow: $shadow; // iOS <4.3 & Android <4.1
106 box-shadow: $shadow;
107 }
108
109 // Transitions
110 @mixin transition($transition) {
111 -webkit-transition: $transition;
112 transition: $transition;
113 }
114 @mixin transition-delay($transition-delay) {
115 -webkit-transition-delay: $transition-delay;
116 transition-delay: $transition-delay;
117 }
118 @mixin transition-duration($transition-duration) {
119 -webkit-transition-duration: $transition-duration;
120 transition-duration: $transition-duration;
121 }
122 @mixin transition-transform($transition) {
123 -webkit-transition: -webkit-transform $transition;
124 -moz-transition: -moz-transform $transition;
125 -o-transition: -o-transform $transition;
126 transition: transform $transition;
127 }
128
129 // Transformations
130 @mixin rotate($degrees) {
131 -webkit-transform: rotate($degrees);
132 -ms-transform: rotate($degrees); // IE9+
133 transform: rotate($degrees);
134 }
135 @mixin scale($ratio) {
136 -webkit-transform: scale($ratio);
137 -ms-transform: scale($ratio); // IE9+
138 transform: scale($ratio);
139 }
140 @mixin translate($x, $y) {
141 -webkit-transform: translate($x, $y);
142 -ms-transform: translate($x, $y); // IE9+
143 transform: translate($x, $y);
144 }
145 @mixin skew($x, $y) {
146 -webkit-transform: skew($x, $y);
147 -ms-transform: skewX($x) skewY($y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+
148 transform: skew($x, $y);
149 }
150 @mixin translate3d($x, $y, $z) {
151 -webkit-transform: translate3d($x, $y, $z);
152 transform: translate3d($x, $y, $z);
153 }
154
155 // Backface visibility
156 // Prevent browsers from flickering when using CSS 3D transforms.
157 // Default value is `visible`, but can be changed to `hidden`
158 // See git pull https://github.com/dannykeane/bootstrap.git backface-visibility for examples
159 @mixin backface-visibility($visibility) {
160 -webkit-backface-visibility: $visibility;
161 -moz-backface-visibility: $visibility;
162 backface-visibility: $visibility;
163 }
164
165 // Box sizing
166 @mixin box-sizing($boxmodel) {
167 -webkit-box-sizing: $boxmodel;
168 -moz-box-sizing: $boxmodel;
169 box-sizing: $boxmodel;
170 }
171
172 // User select
173 // For selecting text on the page
174 @mixin user-select($select) {
175 -webkit-user-select: $select;
176 -moz-user-select: $select;
177 -ms-user-select: $select; // IE10+
178 -o-user-select: $select;
179 user-select: $select;
180 }
181
182 // Resize anything
183 @mixin resizable($direction) {
184 resize: $direction; // Options: horizontal, vertical, both
185 overflow: auto; // Safari fix
186 }
187
188 // CSS3 Content Columns
189 @mixin content-columns($column-count, $column-gap: $grid-gutter-width) {
190 -webkit-column-count: $column-count;
191 -moz-column-count: $column-count;
192 column-count: $column-count;
193 -webkit-column-gap: $column-gap;
194 -moz-column-gap: $column-gap;
195 column-gap: $column-gap;
196 }
197
198 // Optional hyphenation
199 @mixin hyphens($mode: auto) {
200 word-wrap: break-word;
201 -webkit-hyphens: $mode;
202 -moz-hyphens: $mode;
203 -ms-hyphens: $mode; // IE10+
204 -o-hyphens: $mode;
205 hyphens: $mode;
206 }
207
208 // Opacity
209 @mixin opacity($opacity) {
210 opacity: $opacity;
211 // IE8 filter
212 $opacity-ie: ($opacity * 100);
213 filter: #{"alpha(opacity=${opacity-ie})"};
214 }
215
216
217
218 // GRADIENTS
219 // --------------------------------------------------
220
221 #gradient {
222
223 // Horizontal gradient, from left to right
224 //
225 // Creates two color stops, start and end, by specifying a color and position for each color stop.
226 // Color stops are not available in IE9 and below.
227 @mixin horizontal($start-color: #555, $end-color: #333, $start-percent: 0%, $end-percent: 100%) {
228 background-image: -webkit-gradient(linear, $start-percent top, $end-percent top, from($start-color), to($end-color)); // Safari 4+, Chrome 2+
229 background-image: -webkit-linear-gradient(left, color-stop($start-color $start-percent), color-stop($end-color $end-percent)); // Safari 5.1+, Chrome 10+
230 background-image: -moz-linear-gradient(left, $start-color $start-percent, $end-color $end-percent); // FF 3.6+
231 background-image: linear-gradient(to right, $start-color $start-percent, $end-color $end-percent); // Standard, IE10
232 background-repeat: repeat-x;
233 }
234
235 // Vertical gradient, from top to bottom
236 //
237 // Creates two color stops, start and end, by specifying a color and position for each color stop.
238 // Color stops are not available in IE9 and below.
239 @mixin vertical($start-color: #555, $end-color: #333, $start-percent: 0%, $end-percent: 100%) {
240 background-image: -webkit-gradient(linear, left $start-percent, left $end-percent, from($start-color), to($end-color)); // Safari 4+, Chrome 2+
241 background-image: -webkit-linear-gradient(top, $start-color, $start-percent, $end-color, $end-percent); // Safari 5.1+, Chrome 10+
242 background-image: -moz-linear-gradient(top, $start-color $start-percent, $end-color $end-percent); // FF 3.6+
243 background-image: linear-gradient(to bottom, $start-color $start-percent, $end-color $end-percent); // Standard, IE10
244 background-repeat: repeat-x;
245 }
246
247 @mixin directional($start-color: #555, $end-color: #333, $deg: 45deg) {
248 background-repeat: repeat-x;
249 background-image: -webkit-linear-gradient($deg, $start-color, $end-color); // Safari 5.1+, Chrome 10+
250 background-image: -moz-linear-gradient($deg, $start-color, $end-color); // FF 3.6+
251 background-image: linear-gradient($deg, $start-color, $end-color); // Standard, IE10
252 }
253 @mixin horizontal-three-colors($start-color: #00b3ee, $mid-color: #7a43b6, $color-stop: 50%, $end-color: #c3325f) {
254 background-image: -webkit-gradient(left, linear, 0 0, 0 100%, from($start-color), color-stop($color-stop, $mid-color), to($end-color));
255 background-image: -webkit-linear-gradient(left, $start-color, $mid-color $color-stop, $end-color);
256 background-image: -moz-linear-gradient(left, $start-color, $mid-color $color-stop, $end-color);
257 background-image: linear-gradient(to right, $start-color, $mid-color $color-stop, $end-color);
258 background-repeat: no-repeat;
259 }
260 @mixin vertical-three-colors($start-color: #00b3ee, $mid-color: #7a43b6, $color-stop: 50%, $end-color: #c3325f) {
261 background-image: -webkit-gradient(linear, 0 0, 0 100%, from($start-color), color-stop($color-stop, $mid-color), to($end-color));
262 background-image: -webkit-linear-gradient($start-color, $mid-color $color-stop, $end-color);
263 background-image: -moz-linear-gradient(top, $start-color, $mid-color $color-stop, $end-color);
264 background-image: linear-gradient($start-color, $mid-color $color-stop, $end-color);
265 background-repeat: no-repeat;
266 }
267 @mixin radial($inner-color: #555, $outer-color: #333) {
268 background-image: -webkit-gradient(radial, center center, 0, center center, 460, from($inner-color), to($outer-color));
269 background-image: -webkit-radial-gradient(circle, $inner-color, $outer-color);
270 background-image: -moz-radial-gradient(circle, $inner-color, $outer-color);
271 background-image: radial-gradient(circle, $inner-color, $outer-color);
272 background-repeat: no-repeat;
273 }
274 @mixin striped($color: #555, $angle: 45deg) {
275 background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, rgba(255,255,255,.15)), color-stop(.25, transparent), color-stop(.5, transparent), color-stop(.5, rgba(255,255,255,.15)), color-stop(.75, rgba(255,255,255,.15)), color-stop(.75, transparent), to(transparent));
276 background-image: -webkit-linear-gradient($angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent);
277 background-image: -moz-linear-gradient($angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent);
278 background-image: linear-gradient($angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent);
279 }
280 }
281
282 // Retina images
283 //
284 // Short retina mixin for setting background-image and -size
285
286 @mixin img-retina($file-1x, $file-2x, $width-1x, $height-1x) {
287 background-image: image-url("#{$file-1x}");
288 background-size: $width-1x $height-1x;
289
290 @media
291 only screen and (-webkit-min-device-pixel-ratio: 2),
292 only screen and ( min--moz-device-pixel-ratio: 2),
293 only screen and ( -o-min-device-pixel-ratio: 2/1),
294 only screen and ( min-device-pixel-ratio: 2),
295 only screen and ( min-resolution: 192dpi),
296 only screen and ( min-resolution: 2dppx) {
297 background-image: imageurl("#{$file-2x}");
298 background-size: $width-1x $height-1x;
299 }
300 }
301
302
303 // Responsive image
304 //
305 // Keep images from scaling beyond the width of their parents.
306
307 @mixin img-responsive($display: block) {
308 display: $display;
309 max-width: 100%; // Part 1: Set a maximum relative to the parent
310 height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching
311 }
312
313
314 // COMPONENT MIXINS
315 // --------------------------------------------------
316
317 // Horizontal dividers
318 // -------------------------
319 // Dividers (basically an hr) within dropdowns and nav lists
320 @mixin nav-divider($color: #e5e5e5) {
321 height: 1px;
322 margin: (($line-height-computed / 2) - 1) 0;
323 overflow: hidden;
324 background-color: $color;
325 }
326
327 // Panels
328 // -------------------------
329 @mixin panel-variant($border, $heading-text-color, $heading-bg-color, $heading-border) {
330 border-color: $border;
331 & > .panel-heading {
332 color: $heading-text-color;
333 background-color: $heading-bg-color;
334 border-color: $heading-border;
335 + .panel-collapse .panel-body {
336 border-top-color: $border;
337 }
338 }
339 & > .panel-footer {
340 + .panel-collapse .panel-body {
341 border-bottom-color: $border;
342 }
343 }
344 }
345
346 // Alerts
347 // -------------------------
348 @mixin alert-variant($background, $border, $text-color) {
349 background-color: $background;
350 border-color: $border;
351 color: $text-color;
352 hr {
353 border-top-color: darken($border, 5%);
354 }
355 .alert-link {
356 color: darken($text-color, 10%);
357 }
358 }
359
360 // Tables
361 // -------------------------
362 @mixin table-row-variant($state, $background, $border) {
363 // Exact selectors below required to override `.table-striped` and prevent
364 // inheritance to nested tables.
365 .table > thead > tr,
366 .table > tbody > tr,
367 .table > tfoot > tr {
368 > td.#{state},
369 > th.#{state},
370 &.#{state} > td,
371 &.#{state} > th {
372 background-color: $background;
373 border-color: $border;
374 }
375 }
376
377 // Hover states for `.table-hover`
378 // Note: this is not available for cells or rows within `thead` or `tfoot`.
379 .table-hover > tbody > tr {
380 > td.#{state}:hover,
381 > th.#{state}:hover,
382 &.#{state}:hover > td {
383 background-color: darken($background, 5%);
384 border-color: darken($border, 5%);
385 }
386 }
387 }
388
389 // Button variants
390 // -------------------------
391 // Easily pump out default styles, as well as :hover, :focus, :active,
392 // and disabled options for all buttons
393 @mixin button-variant($color, $background, $border) {
394 color: $color;
395 background-color: $background;
396 border-color: $border;
397
398 &:hover,
399 &:focus,
400 &:active,
401 &.active,
402 .open .dropdown-toggle& {
403 color: $color;
404 background-color: darken($background, 8%);
405 border-color: darken($border, 12%);
406 }
407 &:active,
408 &.active,
409 .open .dropdown-toggle& {
410 background-image: none;
411 }
412 &.disabled,
413 &[disabled],
414 fieldset[disabled] & {
415 &,
416 &:hover,
417 &:focus,
418 &:active,
419 &.active {
420 background-color: $background;
421 border-color: $border
422 }
423 }
424 }
425
426 // Button sizes
427 // -------------------------
428 @mixin button-size($padding-vertical, $padding-horizontal, $font-size, $line-height, $border-radius) {
429 padding: $padding-vertical $padding-horizontal;
430 font-size: $font-size;
431 line-height: $line-height;
432 border-radius: $border-radius;
433 }
434
435 // Pagination
436 // -------------------------
437 @mixin pagination-size($padding-vertical, $padding-horizontal, $font-size, $border-radius) {
438 > li {
439 > a,
440 > span {
441 padding: $padding-vertical $padding-horizontal;
442 font-size: $font-size;
443 }
444 &:first-child {
445 > a,
446 > span {
447 @include border-left-radius($border-radius);
448 }
449 }
450 &:last-child {
451 > a,
452 > span {
453 @include border-right-radius($border-radius);
454 }
455 }
456 }
457 }
458
459 // Labels
460 // -------------------------
461 @mixin label-variant($color) {
462 background-color: $color;
463 &[href] {
464 &:hover,
465 &:focus {
466 background-color: darken($color, 10%);
467 }
468 }
469 }
470
471 // Navbar vertical align
472 // -------------------------
473 // Vertically center elements in the navbar.
474 // Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin.
475 @mixin navbar-vertical-align($element-height) {
476 margin-top: (($navbar-height - $element-height) / 2);
477 margin-bottom: (($navbar-height - $element-height) / 2);
478 }
479
480 // Progress bars
481 // -------------------------
482 @mixin progress-bar-variant($color) {
483 background-color: $color;
484 .progress-striped & {
485 #gradient > * {
486 @include striped($color);
487 }
488 }
489 }
490
491 // Responsive utilities
492 // -------------------------
493 // More easily include all the states for responsive-utilities.less.
494 @mixin responsive-visibility() {
495 display: block !important;
496 tr& { display: table-row !important; }
497 th&,
498 td& { display: table-cell !important; }
499 }
500
501 @mixin responsive-invisibility() {
502 display: none !important;
503 tr& { display: none !important; }
504 th&,
505 td& { display: none !important; }
506 }
507
508 // Grid System
509 // -----------
510
511 // Centered container element
512 @mixin container-fixed() {
513 margin-right: auto;
514 margin-left: auto;
515 padding-left: ($grid-gutter-width / 2);
516 padding-right: ($grid-gutter-width / 2);
517 @include clearfix();
518 }
519
520 // Creates a wrapper for a series of columns
521 @mixin make-row($gutter: $grid-gutter-width) {
522 margin-left: ($gutter / -2);
523 margin-right: ($gutter / -2);
524 @include clearfix();
525 }
526
527 // Generate the extra small columns
528 @mixin make-xs-column($columns, $gutter: $grid-gutter-width) {
529 position: relative;
530 float: left;
531 width: percentage(($columns / $grid-columns));
532 // Prevent columns from collapsing when empty
533 min-height: 1px;
534 // Inner gutter via padding
535 padding-left: ($gutter / 2);
536 padding-right: ($gutter / 2);
537 }
538
539 // Generate the small columns
540 @mixin make-sm-column($columns, $gutter: $grid-gutter-width) {
541 position: relative;
542 // Prevent columns from collapsing when empty
543 min-height: 1px;
544 // Inner gutter via padding
545 padding-left: ($gutter / 2);
546 padding-right: ($gutter / 2);
547
548 // Calculate width based on number of columns available
549 @media (min-width: $screen-sm) {
550 float: left;
551 width: percentage(($columns / $grid-columns));
552 }
553 }
554
555 // Generate the small column offsets
556 @mixin make-sm-column-offset($columns) {
557 @media (min-width: $screen-sm) {
558 margin-left: percentage(($columns / $grid-columns));
559 }
560 }
561 @mixin make-sm-column-push($columns) {
562 @media (min-width: $screen-sm) {
563 left: percentage(($columns / $grid-columns));
564 }
565 }
566 @mixin make-sm-column-pull($columns) {
567 @media (min-width: $screen-sm) {
568 right: percentage(($columns / $grid-columns));
569 }
570 }
571
572 // Generate the medium columns
573 @mixin make-md-column($columns, $gutter: $grid-gutter-width) {
574 position: relative;
575 // Prevent columns from collapsing when empty
576 min-height: 1px;
577 // Inner gutter via padding
578 padding-left: ($gutter / 2);
579 padding-right: ($gutter / 2);
580
581 // Calculate width based on number of columns available
582 @media (min-width: $screen-md) {
583 float: left;
584 width: percentage(($columns / $grid-columns));
585 }
586 }
587
588 // Generate the large column offsets
589 @mixin make-md-column-offset($columns) {
590 @media (min-width: $screen-md) {
591 margin-left: percentage(($columns / $grid-columns));
592 }
593 }
594 @mixin make-md-column-push($columns) {
595 @media (min-width: $screen-md) {
596 left: percentage(($columns / $grid-columns));
597 }
598 }
599 @mixin make-md-column-pull($columns) {
600 @media (min-width: $screen-md) {
601 right: percentage(($columns / $grid-columns));
602 }
603 }
604
605 // Generate the large columns
606 @mixin make-lg-column($columns, $gutter: $grid-gutter-width) {
607 position: relative;
608 // Prevent columns from collapsing when empty
609 min-height: 1px;
610 // Inner gutter via padding
611 padding-left: ($gutter / 2);
612 padding-right: ($gutter / 2);
613
614 // Calculate width based on number of columns available
615 @media (min-width: $screen-lg) {
616 float: left;
617 width: percentage(($columns / $grid-columns));
618 }
619 }
620
621 // Generate the large column offsets
622 @mixin make-lg-column-offset($columns) {
623 @media (min-width: $screen-lg) {
624 margin-left: percentage(($columns / $grid-columns));
625 }
626 }
627 @mixin make-lg-column-push($columns) {
628 @media (min-width: $screen-lg) {
629 left: percentage(($columns / $grid-columns));
630 }
631 }
632 @mixin make-lg-column-pull($columns) {
633 @media (min-width: $screen-lg) {
634 right: percentage(($columns / $grid-columns));
635 }
636 }
637
638
639 // Form validation states
640 //
641 // Used in forms.less to generate the form validation CSS for warnings, errors,
642 // and successes.
643
644 @mixin form-control-validation($text-color: #555, $border-color: #ccc, $background-color: #f5f5f5) {
645 // Color the label and help text
646 .help-block,
647 .control-label {
648 color: $text-color;
649 }
650 // Set the border and box shadow on specific inputs to match
651 .form-control {
652 border-color: $border-color;
653 @include box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work
654 &:focus {
655 border-color: darken($border-color, 10%);
656 $shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten($border-color, 20%);
657 @include box-shadow($shadow);
658 }
659 }
660 // Set validation states also for addons
661 .input-group-addon {
662 color: $text-color;
663 border-color: $border-color;
664 background-color: $background-color;
665 }
666 }
667
668 // Form control focus state
669 //
670 // Generate a customized focus state and for any input with the specified color,
671 // which defaults to the `$input-focus-border` variable.
672 //
673 // We highly encourage you to not customize the default value, but instead use
674 // this to tweak colors on an as-needed basis. This aesthetic change is based on
675 // WebKit's default styles, but applicable to a wider range of browsers. Its
676 // usability and accessibility should be taken into account with any change.
677 //
678 // Example usage: change the default blue border and shadow to white for better
679 // contrast against a dark gray background.
680
681 @mixin form-control-focus($color: $input-border-focus) {
682 $color-rgba: rgba(red($color), green($color), blue($color), .6);
683 &:focus {
684 border-color: $color;
685 outline: 0;
686 @include box-shadow(#{"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px ${color-rgba}"});
687 }
688 }
689
690 // Form control sizing
691 //
692 // Relative text size, padding, and border-radii changes for form controls. For
693 // horizontal sizing, wrap controls in the predefined grid classes. `<select>`
694 // element gets special love because it's special, and that's a fact!
695
696 @mixin input-size($input-height, $padding-vertical, $padding-horizontal, $font-size, $line-height, $border-radius) {
697 height: $input-height;
698 padding: $padding-vertical $padding-horizontal;
699 font-size: $font-size;
700 line-height: $line-height;
701 border-radius: $border-radius;
702
703 select& {
704 height: $input-height;
705 line-height: $input-height;
706 }
707
708 textarea& {
709 height: auto;
710 }
711 }
0 //
1 // Node animation
2 // --------------------------------------------------
3
4
5 #node-canvas{
6 position: absolute;
7 top: 0;
8 width: 1400px;
9 background-color: transparent;
10
11 rect {
12 fill: none;
13 pointer-events: all;
14 }
15
16 .node {
17 /*fill: #a7a7a7;*/
18 fill: #939393;
19 stroke: #bbb;
20 z-index: 100;
21 transition-property: stroke fill;
22 transition-duration: 1s;
23 }
24
25 .node.active {
26 /*fill: #bbb; */
27 stroke: #dd4e58;
28 transition-property: stroke fill;
29 transition-duration: 1s;
30 }
31
32 .cursor {
33 fill: none;
34 stroke: brown;
35 pointer-events: none;
36 }
37
38 .link {
39 stroke: #bbb;
40 stroke-width: 1.5;
41 z-index: 0;
42 transition-property: stroke fill;
43 transition-duration: 1s;
44 }
45
46 .link.active{
47 stroke: #dd4e58;
48 transition-property: stroke;
49 transition-duration: 1s;
50 }
51 }
52
0 //
1 // Utility classes
2 // --------------------------------------------------
3
4
5 //
6 // -------------------------
7
8 @mixin anti-alias() {
9 text-rendering: optimizeLegibility;
10 -webkit-font-smoothing: antialiased;
11 }
0 //
1 // Variables
2 // --------------------------------------------------
3
4
5 // Global values
6 // --------------------------------------------------
7
8 $jumbotron-height: 490px;
9 $jumbotron-color: #fff;
10 $btn-border-radius: 4px;
11 $el-border-radius: 6px;
12 // colors
13 // -------------------------
14
15 $white: #fff;
16 $black: #242424;
17 $gray-darker: #555;
18 $gray: #777;
19 $gray-light: #939393;
20 $gray-lighter: #979797;
21 $red: #dd4e58;
22 $red-dark: #c5454e;
23 $red-darker: #b03c44;
24 $tan: #f0f0e5;
25
26 $btn-color: #4592C5;
27
28
29 // Scaffolding
30 // -------------------------
31 $body-bg: #fff;
32 $text-color: $gray;
33
34 // Links
35 // -------------------------
36 $link-color: $red-dark;
37 $link-hover-color: darken($link-color, 15%);
38
39 // Typography
40 // -------------------------
41 $font-family-rales: 'Raleway', "Helvetica Neue", Helvetica, Arial, sans-serif;
42 $font-family-open-sans: 'Source Sans Pro', "Helvetica Neue", Helvetica, Arial, sans-serif;
43 $font-weight-rales-xl: 200;
44 $font-weight-rales-reg: 400;
45 $font-weight-rales-sb: 600;
46 $font-weight-rales-xb: 800;
47 $font-weight-open: $font-weight-rales-reg;
48
49
50 $text-shadow: 1px 1px 1px #000;
51 $shadow: $text-shadow;
52
53
0 @import "bootstrap-sprockets";
1 @import "bootstrap";
2
3 // Core variables and mixins
4 @import "_variables";
5 @import "_mixins";
6
7 // Utility classes
8 @import "_utilities";
9
10 // Core CSS
11 @import "_fonts";
12
13 //Global Site
14 @import "_global";
15
16 // Components
17 @import "_header";
18 @import "_jumbotron";
19 @import "_nodes";
20
21 // Pages
22 @import "_home";
23 @import "_community";
24 @import "_docs";
25 @import "_downloads";
0 ---
1 page_title: "Community"
2 description: |-
3 Serf is a new project with a growing community. Despite this, there are active, dedicated users willing to help you through various mediums.
4 ---
5
6 <div class="container">
7 <div class="col-md-8 col-md-offset-2">
8 <div class="bs-docs-section">
9 <h1>Community</h1>
10
11 <p>
12 Serf is a new project with a growing community. Despite this,
13 there are active, dedicated users willing to help you through various
14 mediums.
15 </p>
16 <p>
17 <strong>IRC:</strong> <code>#serfdom</code> on Freenode
18 </p>
19 <p>
20 <strong>Mailing list:</strong>
21 <a href="https://groups.google.com/group/serfdom">Serf Google Group</a>
22 </p>
23 <p>
24 <strong>Bug Tracker:</strong>
25 <a href="https://github.com/hashicorp/serf/issues">Issue tracker
26 on GitHub</a>. Please only use this for reporting bugs. Do not ask
27 for general help here. Use IRC or the mailing list for that.
28
29 <h1>People</h1>
30 <p>
31 The following people are some of the faces behind Serf. They each
32 contribute to Serf in some core way. Over time, faces may appear and
33 disappear from this list as contributors come and go.
34 </p>
35 <div class="people">
36 <div class="person">
37 <img class="pull-left" src="http://www.gravatar.com/avatar/11ba9630c9136eef9a70d26473d355d5.png?s=125">
38 <div class="bio">
39 <h3>Armon Dadgar (<a href="https://github.com/armon">@armon</a>)</h3>
40 <p>
41 Armon Dagar is the creator of Serf. He researched and developed
42 many of the internals of how Serf works, including the gossip
43 layer. Armon is also the creator of
44 <a href="https://github.com/armon/statsite">Statsite</a> and
45 <a href="https://github.com/armon/bloomd">Bloomd</a>.
46 </div>
47 </div>
48
49 <div class="person">
50 <img class="pull-left" src="http://www.gravatar.com/avatar/54079122b67de9677c1f93933ce8b63a.png?s=125">
51 <div class="bio">
52 <h3>Mitchell Hashimoto (<a href="https://github.com/mitchellh">@mitchellh</a>)</h3>
53 <p>
54 Mitchell Hashimoto is the creator of Serf, and works on
55 all layers of Serf from the gossip layer up to the CLI. In addition
56 to Serf, Mitchell is the creator of
57 <a href="http://www.vagrantup.com">Vagrant</a> and
58 <a href="http://www.packer.io">Packer</a>. He is self described
59 as "automation obsessed."
60 </p>
61 </div>
62 </div>
63
64 <div class="person">
65 <img class="pull-left" src="http://www.gravatar.com/avatar/3ad545e7f2da237bd2b379d764d5f92a.png?s=125">
66 <div class="bio">
67 <h3>Ryan Uber (<a href="https://github.com/ryanuber">@ryanuber</a>)</h3>
68 <p>
69 Ryan Uber is a core committer on Serf, and works mostly on
70 the agent, RPC, and CLI layers as well as documentation.
71 Ryan has also contributed to
72 <a href="http://www.vagrantup.com">Vagrant</a> and has a
73 few open source projects of his own.
74 </p>
75 </div>
76 </div>
77 <div class="clearfix"></div>
78 </div>
79 </div>
80 </div>
81 </div>
0 ---
1 layout: "docs"
2 page_title: "Agent"
3 sidebar_current: "docs-agent-running"
4 description: |-
5 The Serf agent is the core process of Serf. The agent maintains membership information, propagates events, invokes event handlers, detects failures, and more. The agent must run on every node that is part of a Serf cluster.
6 ---
7
8 # Serf Agent
9
10 The Serf agent is the core process of Serf. The agent maintains membership
11 information, propagates events, invokes event handlers, detects failures,
12 and more. The agent must run on every node that is part of a Serf cluster.
13
14 ## Running an Agent
15
16 The agent is started with the `serf agent` command. This command blocks,
17 running forever or until told to quit. The agent command takes a variety
18 of configuration options but the defaults are usually good enough. When
19 running `serf agent`, you should see output similar to that below:
20
21 ```
22 $ serf agent
23 ==> Starting Serf agent...
24 ==> Serf agent running!
25 Node name: 'mitchellh.local'
26 Bind addr: '0.0.0.0:7946'
27 RPC addr: '127.0.0.1:7373'
28 Encrypted: false
29 Snapshot: false
30 Profile: lan
31
32 ==> Log data will now stream in as it occurs:
33
34 2013/10/22 10:35:33 [INFO] Serf agent starting
35 2013/10/22 10:35:33 [INFO] serf: EventMemberJoin: mitchellh.local 127.0.0.1
36 2013/10/22 10:35:33 [INFO] Serf agent started
37 2013/10/22 10:35:33 [INFO] agent: Received event: member-join
38 ...
39 ```
40
41 There are six important components that `serf agent` outputs:
42
43 * **Node name**: This is a unique name for the agent. By default this
44 is the hostname of the machine, but you may customize it to whatever
45 you'd like using the `-node` flag.
46
47 * **Bind addr**: This is the address and port used for communication between
48 Serf agents in a cluster. Every Serf agent in a cluster does not have to
49 use the same port.
50
51 * **RPC addr**: This is the address and port used for RPC communications
52 for other `serf` commands. Other Serf commands such as `serf members`
53 connect to a running agent and use RPC to query and control the agent.
54 By default, this binds only to localhost on the default port. If you
55 change this address, you'll have to specify an `-rpc-addr` to commands
56 such as `serf members` so they know how to talk to the agent. This is also
57 the address other applications can use over [RPC to control Serf](/docs/agent/rpc.html).
58
59 * **Encrypted**: This shows if Serf is encrypting all traffic that it
60 sends and expects to receive. It is a good sanity check to avoid sending
61 non-encrypted traffic over any public networks. You can read more about
62 [encryption here](/docs/agent/encryption.html).
63
64 * **Snapshot**: This shows if Serf snapshotting is enabled. The snapshot
65 file enables Serf to automatically re-join a cluster after failure and
66 prevents replay of events that have already been seen. It requires storing
67 state on disk, and [must be configured](/docs/agent/options.html)
68 using a CLI flag or in the configuration directory. If it is not provided,
69 other nodes will still attempt to reconnect on recovery, however the node
70 will take longer to join the cluster and will replay old events.
71
72 * **Profile**: The profile controls various timing values which should
73 be appropriate to the environment Serf is running in. It defaults to
74 optimizing for a LAN environment, but can also be set for WAN or
75 local-only communication. The profile can be set in
76 the [configuration](/docs/agent/options.html).
77
78 ## Stopping an Agent
79
80 An agent can be stopped in two ways: gracefully or forcefully. To gracefully
81 halt an agent, send the process an interrupt signal, which is usually
82 `Ctrl-C` from a terminal. When gracefully exiting, the agent first notifies
83 the cluster it intends to leave the cluster. This way, other cluster members
84 notify the cluster that the node has _left_.
85
86 Alternatively, you can force kill the agent by sending it a kill signal.
87 When force killed, the agent ends immediately. The rest of the cluster will
88 eventually (usually within seconds) detect that the node has died and will
89 notify the cluster that the node has _failed_.
90
91 The difference between a node _failing_ and a node _leaving_ may not be
92 important for your use case. For example, for a web server and load
93 balancer setup, both result in the same action: remove the web node
94 from the load balancer pool. But for other situations, you may handle
95 each scenario differently.
0 ---
1 layout: "docs"
2 page_title: "Encryption"
3 sidebar_current: "docs-agent-encryption"
4 description: |-
5 The Serf agent supports encrypting all of its network traffic. The exact method of this encryption is described on the encryption internals page.
6 ---
7
8 # Encryption
9
10 The Serf agent supports encrypting all of its network traffic. The exact
11 method of this encryption is described on the
12 [encryption internals page](/docs/internals/security.html).
13
14 ## Enabling Encryption
15
16 Enabling encryption only requires that you set an encryption key when
17 starting the Serf agent. The key can be set using the `-encrypt` flag
18 on `serf agent` or by setting the `encrypt_key` in a configuration file.
19 It is advisable to put the key in a configuration file to avoid other users
20 from being able to discover it by inspecting running processes.
21 The key must be 16-bytes that are base64 encoded. The easiest method to
22 obtain a cryptographically suitable key is by using `serf keygen`.
23
24 ```
25 $ serf keygen
26 cg8StVXbQJ0gPvMd9o7yrg==
27 ```
28
29 With that key, you can enable encryption on the agent. You can verify
30 encryption is enabled because the output will include "Encrypted: true".
31
32 ```
33 $ serf agent -encrypt=cg8StVXbQJ0gPvMd9o7yrg==
34 ==> Starting Serf agent...
35 ==> Serf agent running!
36 Node name: 'mitchellh.local'
37 Bind addr: '0.0.0.0:7946'
38 RPC addr: '127.0.0.1:7373'
39 Encrypted: true
40 ...
41 ```
42
43 All nodes within a Serf cluster must share the same encryption key in
44 order to send and receive cluster information.
45
46 ## Changing encryption keys
47
48 Serf supports changing keys used to encrypt network traffic and takes on the
49 burden of distributing the new key to the rest of the members in the cluster. To
50 support this, Serf includes a key management command with 4 actions:
51
52 ```
53 serf keys -install <key>
54 serf keys -use <key>
55 serf keys -remove <key>
56 serf keys -list
57 ```
58
59 Using the above commands, it is possible to effectively change the encryption
60 keys used to protect Serf without any interruption to the cluster. By executing
61 these commands, you are performing cluster-wide operations to distribute and
62 configure new encryption keys.
63
64 All of the above commands are idempotent - meaning, you may run them multiple
65 times with no negative consequences. This can prove useful in case of partial
66 success situations.
67
68 ## Multiple Clusters
69
70 If you're running multiple Serf clusters, for example a Serf cluster to
71 maintain cache nodes and a Serf cluster to maintain web servers, then you
72 can use different encryption keys for each cluster in order to separately
73 encrypt the traffic.
74
75 This has the added benefit that the Serf agents from one cluster cannot
76 even accidentally join the other cluster, since the two clusters will not
77 be able to communicate without the same encryption key.
0 ---
1 layout: "docs"
2 page_title: "Event Handlers"
3 sidebar_current: "docs-agent-events"
4 description: |-
5 Serf's true power and flexibility comes in the form of event handlers: scripts that are executed in response to various events that can occur related to the Serf cluster. Serf invokes events related to membership changes (when a node comes online or goes offline) as well as custom events or queries.
6 ---
7
8 # Event Handlers
9
10 Serf's true power and flexibility comes in the form of event handlers:
11 scripts that are executed in response to various events that can occur
12 related to the Serf cluster. Serf invokes events related to membership
13 changes (when a node comes online or goes offline) as well as
14 [custom events](/docs/commands/event.html) or [queries](/docs/commands/query.html).
15
16 Event handlers can be any executable, including piped executables (such
17 as `awk '{print $2}' | grep foo`), since event handlers are invoked within
18 the context of a shell. The event handler is executed anytime an event
19 occurs and is expected to exit within a reasonable amount of time.
20
21 ## Inputs and Parameters
22
23 Every time an event handler is invoked, Serf sets some environmental
24 variables:
25
26 * `SERF_EVENT` is the event type that is occurring. This will be one of
27 `member-join`, `member-leave`, `member-failed`, `member-update`,
28 `member-reap`, `user`, or `query`.
29
30 * `SERF_SELF_NAME` is the name of the node that is executing the event handler.
31
32 * `SERF_SELF_ROLE` is the role of the node that is executing the event handler.
33
34 * `SERF_TAG_${TAG}` is set for each tag the agent has. The tag name is upper-cased.
35
36 * `SERF_USER_EVENT` is the name of the user event if `SERF_EVENT` is "user".
37
38 * `SERF_USER_LTIME` is the `LamportTime` of the user event if `SERF_EVENT`
39 is "user".
40
41 * `SERF_QUERY_NAME` is the name of the query if `SERF_EVENT` is "query".
42
43 * `SERF_QUERY_LTIME` is the `LamportTime` of the query if `SERF_EVENT`
44 is "query".
45
46 In addition to these environmental variables, the data for an event is passed
47 in via stdin. The format of the data is dependent on the event type.
48
49 If the `SERF_EVENT` is "query", then the handler is also used to generate
50 a query response. If the handler exits with a 0 status code, then any output
51 on stdout and stderr is used as the response. The response should be of a limited
52 size or the response will fail to send due to size restrictions.
53
54 #### Membership Event Data
55
56 For membership related events (`member-join`, `member-leave`, `member-failed`, `member-update`, and `member-reap`),
57 stdin is the list of members that participated in that event. Each member is
58 separated by a newline and each field about the member is separated by a single
59 tab (`\t`). The fields of a membership event are name, address, role, then tags.
60 For example:
61
62 ```
63 mitchellh.local 127.0.0.1 web role=web,datacenter=east
64 ```
65
66 #### User Event Data
67
68 For user events, stdin is the payload (if any) of the user event.
69
70 #### Query Data
71
72 For queries, stdin is the payload (if any) of the query.
73
74 ## Specifying Event Handlers
75
76 Event handlers are specified using the `-event-handler` flag for
77 `serf agent`. This flag can be specified multiple times for multiple
78 event handlers, in which case each event handler will be executed.
79
80 Event handlers can also be filtered by event type. By default, the event
81 handler will be invoked for any event which may occur. But you can restrict
82 the events the event handler is invoked for by using a simple syntax
83 of `type=script`. Below are all the available ways this syntax can be
84 used to filter an event handler:
85
86 * `foo.sh` - The script "foo.sh" will be invoked for any/every event.
87
88 * `member-join=foo.sh` - The script "foo.sh" will only be invoked for the
89 "member-join" event.
90
91 * `member-join,member-leave=foo.sh` - The script "foo.sh" will be invoked
92 for either member-join or member-leave events. Any combination of events
93 may be specified in this way.
94
95 * `user=foo.sh` - The script "foo.sh" will be invoked for all user events.
96
97 * `user:deploy=foo.sh` - The script "foo.sh" will be invoked only for
98 "deploy" user events.
99
100 * `query=foo.sh` - The script "foo.sh" will be invoked for all queries.
101
102 * `query:load=uptime` - The uptime command will be invoked only for "load"
103 queries.
104
105 ## Forking event handlers
106
107 There are some cases where it may be desirable to fork a background process when
108 an event handler fires. This is mainly useful for invoking scripts which take a
109 minute or two to execute, and where the process output is not important. By
110 default, Serf's execution subsystem will block, waiting for output and a return
111 code. It is possible, however, to "detach" a process by forking and replacing
112 the file descriptors for both stdout and stderr.
113
114 In shell, this would look something like:
115
116 ```
117 sleep 5 &>/dev/null &
118 ```
119
120 In the above example, `sleep 5` is the lengthy process. Notice the first
121 ampersand, which copies the file descriptor instead of just redirecting output.
122
123 Similarly, in Python this might look like:
124
125 ```
126 out_log = file('/dev/null', 'a+')
127 os.dup2(out_log.fileno(), sys.stdout.fileno())
128 os.dup2(out_log.fileno(), sys.stderr.fileno())
129 ```
130
131 Or in ruby:
132
133 ```
134 $stdout.reopen('/dev/null', 'w')
135 $stderr.reopen('/dev/null', 'w')
136 ```
137
138 **Note:** This method is really only useful for event handlers, and is mostly
139 useless for [queries](/docs/commands/query.html).
0 ---
1 layout: "docs"
2 page_title: "Logging"
3 sidebar_current: "docs-agent"
4 ---
5
6 # Logging
7
8 TODO
0 ---
1 layout: "docs"
2 page_title: "Configuration"
3 sidebar_current: "docs-agent-config"
4 description: |-
5 The agent has various configuration options that can be specified via the command-line or via configuration files. All of the configuration options are completely optional and their defaults will be specified with their descriptions.
6 ---
7
8 # Configuration
9
10 The agent has various configuration options that can be specified via
11 the command-line or via configuration files. All of the configuration
12 options are completely optional and their defaults will be specified
13 with their descriptions.
14
15 When loading configuration, Serf loads the configuration from files
16 and directories in the order specified. Configuration specified later
17 will be merged into configuration specified earlier. In most cases,
18 "merge" means that the later version will override the earlier. But in
19 some cases, such as event handlers, merging just appends the handlers.
20 The exact merging behavior will be specified.
21
22 Serf also supports reloading of configuration when it receives the
23 SIGHUP signal. Not all changes are respected, but those that are
24 are documented below.
25
26 ## Command-line Options
27
28 The options below are all specified on the command-line.
29
30 * `-bind` - The address that Serf will bind to for communication with
31 other Serf nodes. By default this is "0.0.0.0:7946". Serf nodes may
32 have different ports. If a join is specified without a port, we default
33 to locally configured port. Serf uses both TCP and UDP and use the
34 same port for both, so if you have any firewalls be sure to allow both protocols.
35 If this configuration value is changed and no port is specified, the default of
36 "7946" will be used. An important compatibility note, protocol version 2
37 introduces support for non-consistent ports across the cluster. For more information,
38 see the [compatibility page](/docs/compatibility.html).
39
40 * `-iface` - This flag can be used to provide a binding interface. It can be
41 used instead of `-bind` if the interface is known but not the address. If both
42 are provided, then Serf verifies that the interface has the bind address that is
43 provided. This flag also sets the multicast device used for `-discover`.
44
45 * `-advertise` - The advertise flag is used to change the address that we
46 advertise to other nodes in the cluster. By default, the bind address is
47 advertised. However, in some cases (specifically NAT traversal), there may
48 be a routable address that cannot be bound to. This flag enables gossiping
49 a different address to support this. If this address is not routable, the node
50 will be in a constant flapping state, as other nodes will treat the non-routability
51 as a failure.
52
53 * `-config-file` - A configuration file to load. For more information on
54 the format of this file, read the "Configuration Files" section below.
55 This option can be specified multiple times to load multiple configuration
56 files. If it is specified multiple times, configuration files loaded later
57 will merge with configuration files loaded earlier, with the later values
58 overriding the earlier values.
59
60 * `-config-dir` - A directory of configuration files to load. Serf will
61 load all files in this directory ending in ".json" as configuration files
62 in alphabetical order. For more information on the format of the configuration
63 files, see the "Configuration Files" section below.
64
65 * `-discover` - Discover provides a cluster name, which is used with mDNS to
66 automatically discover Serf peers. When provided, Serf will respond to mDNS
67 queries and periodically poll for new peers. This feature requires a network
68 environment that supports multicasting.
69
70 * `-encrypt` - Specifies the secret key to use for encryption of Serf
71 network traffic. This key must be 16-bytes that are base64 encoded. The
72 easiest way to create an encryption key is to use `serf keygen`. All
73 nodes within a cluster must share the same encryption key to communicate.
74
75 * `-keyring-file` - Specifies a file to load keyring data from. Serf is able to
76 keep encryption keys in sync and perform key rotations. During a key rotation,
77 there may be some period of time in which Serf is required to maintain more
78 than one encryption key until all members have received the new key. The
79 keyring file helps persist changes to the encryption keyring, allowing the
80 agent to start and rejoin the cluster successfully later on, even if key
81 rotations had been initiated by other members in the cluster. More information
82 on the format of the keyring file can be found below in the examples section.
83
84 NOTE: this option is not compatible with the `-encrypt` option.
85
86 * `-event-handler` - Adds an event handler that Serf will invoke for
87 events. This flag can be specified multiple times to define multiple
88 event handlers. By default no event handlers are registered. See the
89 [event handler page](/docs/agent/event-handlers.html) for more details on
90 event handlers as well as a syntax for filtering event handlers by event.
91 Event handlers can be changed by reloading the configuration.
92
93 * `-join` - Address of another agent to join upon starting up. This can be
94 specified multiple times to specify multiple agents to join. If Serf is
95 unable to join with any of the specified addresses, agent startup will
96 fail. By default, the agent won't join any nodes when it starts up.
97
98 * `-replay` - If set, old user events from the past will be replayed for the
99 agent/cluster that is joining based on a `-join` configuration. Otherwise,
100 past events will be ignored. This configures for the initial join
101 only.
102
103 * `-log-level` - The level of logging to show after the Serf agent has
104 started. This defaults to "info". The available log levels are "trace",
105 "debug", "info", "warn", "err". This is the log level that will be shown
106 for the agent output, but note you can always connect via `serf monitor`
107 to an agent at any log level. The log level can be changed during a
108 config reload.
109
110 * `-node` - The name of this node in the cluster. This must be unique within
111 the cluster. By default this is the hostname of the machine.
112
113 * `-profile` - Serf by default is configured to run in a LAN or Local Area
114 Network. However, there are cases in which a user may want to use Serf over
115 the Internet or (WAN), or even just locally. To support setting the correct
116 configuration values for each environment, you can select a timing profile.
117 The current choices are "lan", "wan", and "local". This defaults to "lan".
118 If a "lan" or "local" profile is used over the Internet, or a "local" profile
119 over the LAN, a high rate of false failures is risked, as the timing constrains
120 are too tight.
121
122 * `-protocol` - The Serf protocol version to use. This defaults to the latest
123 version. This should be set only when [upgrading](/docs/upgrading.html).
124 You can view the protocol versions supported by Serf by running `serf -v`.
125
126 * `-retry-join` - Address of another agent to join after starting up. This can
127 be specified multiple times to specify multiple agents to join. If Serf is
128 unable to join with any of the specified addresses, the agent will retry
129 the join every `-retry-interval` up to `-retry-max` attempts. This can be used
130 instead of `-join` to continue attempting to join the cluster.
131
132 * `-retry-interval` - Provides a duration string to control how after the
133 retry join is performed. By default, the join is attempted every 30 seconds
134 until success. This should use the "s" suffix for second, "m" for minute,
135 or "h" for hour.
136
137 * `-retry-max` - Provides a limit on how many attempts to join the cluster
138 can be made by `-retry-join`. If 0, there is no limit, and the agent will
139 retry forever. Defaults to 0.
140
141 * `-role` - **Deprecated** The role of this node, if any. By default this is blank or empty.
142 The role can be used by events in order to differentiate members of a
143 cluster that may have different functional roles. For example, if you're
144 using Serf in a load balancer and web server setup, you only want to add
145 web servers to the load balancers, so the role of web servers may be "web"
146 and the event handlers can filter on that. This has been deprecated as of
147 version 0.4. Instead "-tag role=foo" should be used. The role can be changed
148 during a config reload
149
150 * `-rpc-addr` - The address that Serf will bind to for the agent's RPC server.
151 By default this is "127.0.0.1:7373", allowing only loopback connections.
152 The RPC address is used by other Serf commands, such as `serf members`,
153 in order to query a running Serf agent. It is also used by other applications
154 to control Serf using it's [RPC protocol](/docs/agent/rpc.html).
155
156 * `-snapshot` - The snapshot flag provides a file path that is used to store
157 recovery information, so when Serf restarts it is able to automatically
158 re-join the cluster, and avoid replay of events it has already seen. The path
159 must be read/writable by Serf, and the directory must allow Serf to create
160 other files, so that it can periodically compact the snapshot file.
161
162 * `-rejoin` - When provided with the `-snapshot`, Serf will ignore a previous
163 leave and attempt to rejoin the cluster when starting. By default, Serf treats
164 leave as a permanent intent, and does not attempt to join the cluster again
165 when starting. This flag allows the snapshot state to be used to rejoin
166 the cluster.
167
168 * `-tag` - The tag flag is used to associate a new key/value pair with the
169 agent. The tags are gossiped and can be used to provide additional information
170 such as roles, ports, and configuration values to other nodes. Multiple tags
171 can be specified per agent. There is a byte size limit for the maximum number
172 of tags, but in practice dozens of tags may be used. Tags can be changed during
173 a config reload.
174
175
176 * `-tags-file` - The tags file is used to persist tag data. As an agent's tags
177 are changed, the tags file will be updated. Tags can be reloaded during later
178 agent starts. This option is incompatible with the `-tag` option and requires
179 there be no tags in the agent configuration file, if given.
180
181 * `-syslog` - When provided, the logs will also be sent to the syslog facility.
182 This flag can only be enabled on Linux or OSX systems, as Windows and Plan 9 do
183 not provide the syslog facility.
184
185 ## Configuration Files
186
187 In addition to the command-line options, configuration can be put into
188 files. This may be easier in certain situations, for example when Serf is
189 being configured using a configuration management system.
190
191 The configuration files are JSON formatted, making them easily readable
192 and editable by both humans and computers. The configuration is formatted
193 at a single JSON object with configuration within it.
194
195 #### Example Configuration File
196
197 ```javascript
198 {
199 "tags": {
200 "role": "load-balancer",
201 "datacenter": "east"
202 },
203 "event_handlers": [
204 "handle.sh",
205 "user:deploy=deploy.sh"
206 ]
207 }
208 ```
209
210 #### Configuration Key Reference
211
212 * `node_name` - Equivalent to the `-node` command-line flag.
213
214 * `role` - **Deprecated**. Equivalent to the `-role` command-line flag.
215
216 * `tags` - This is a dictionary of tag values. It is the same as specifying
217 the `tag` command-line flag once per tag.
218
219 * `tags_file` - Equivalent to the `-tags-file` command-line flag.
220
221 * `bind` - Equivalent to the `-bind` command-line flag.
222
223 * `interface` - Equivalent to the `-iface` command-line flag.
224
225 * `advertise` - Equivalent to the `-advertise` command-line flag.
226
227 * `discover` - Equivalent to the `-discover` command-line flag.
228
229 * `encrypt_key` - Equivalent to the `-encrypt` command-line flag.
230
231 * `log_level` - Equivalent to the `-log-level` command-line flag.
232
233 * `profile` - Equivalent to the `-profile` command-line flag.
234
235 * `protocol` - Equivalent to the `-protocol` command-line flag.
236
237 * `rpc_addr` - Equivalent to the `-rpc-addr` command-line flag.
238
239 * `rpc_auth` - Used to provide an RPC auth token. If this token is set, then
240 all RPC clients are required to provide this token to make RPC requests.
241 This is a simple security mechanism that can be used to prevent other users
242 from making RPC requests to Serf without the token.
243
244 * `event_handlers` - An array of strings specifying the event handlers.
245 The format of the strings is equivalent to the format specified for
246 the `-event-handler` command-line flag.
247
248 * `start_join` - An array of strings specifying addresses of nodes to
249 join upon startup.
250
251 * `replay_on_join` - Equivalent to the `-replay` command-line flag.
252
253 * `snapshot_path` - Equivalent to the `-snapshot` command-line flag.
254
255 * `leave_on_terminate` - If enabled, when the agent receives a TERM signal,
256 it will send a Leave message to the rest of the cluster and gracefully
257 leave. Defaults to false.
258
259 * `skip_leave_on_interrupt` - This is the similar to`leave_on_terminate` but
260 only affects interrupt handling. By default, an interrupt causes Serf to
261 gracefully leave, but setting this to true disables that. Defaults to false.
262 Interrupts are usually from a Control-C from a shell. (This was previously
263 `leave_on_interrupt` but has since changed).
264
265 * `reconnect_interval` - This controls how often the agent will attempt to
266 connect to a failed node. By default this is every 30 seconds.
267
268 * `reconnect_timeout` - This controls for how long the agent attempts to connect
269 to a failed node before reaping it from the cluster. By default this is 24 hours.
270
271 * `tombstone_timeout` - This controls for how long the agent remembers nodes that
272 have gracefully left the cluster before reaping. By default this is 24 hours.
273
274 * `disable_name_resolution` - If enabled, then Serf will not attempt to automatically
275 resolve name conflicts. Serf relies on the each node having a unique name, but as a
276 result of misconfiguration sometimes Serf agents have conflicting names. By default,
277 the agents that are conflicting will query the cluster to determine which node is
278 believed to be "correct" by the majority of other nodes. The node(s) that are in the
279 minority will shutdown at the end of the conflict resolution. Setting this flag prevents
280 this behavior, and instead Serf will merely log a warning. This is not recommended since
281 the cluster will disagree about the mapping of NodeName -> IP:Port and cannot reconcile
282 this.
283
284 * `enable_syslog` - Equivalent to the `-syslog` command-line flag.
285
286 * `syslog_facility` - When used with `enable_syslog`, specifies the syslog
287 facility messages are sent to. By default `LOCAL0` is used.
288
289 * `retry_join` - An array of strings specifying addresses of nodes to
290 join upon startup with retries if we fail to join.
291
292 * `retry_max_attempts` - Equivalent to the `-retry-max` command-line flag.
293
294 * `retry_interval` - Equivalent to the `-retry-interval` command-line flag.
295
296 * `rejoin_after_leave` - Equivalent to the `-rejoin` command-line flag.
297
298 * `statsite_addr` - This provides the address of a statsite instance. If provided
299 Serf will stream various telemetry information to that instance for aggregation.
300 This can be used to capture various runtime information.
301
302 #### Example Keyring File
303
304 The keyring file is a simple JSON-formatted text file. It is important to
305 understand how Serf will use its contents. Following is an example of a keyring
306 file:
307
308 ```javascript
309 [
310 "QHOYjmYlxSCBhdfiolhtDQ==",
311 "daZ2wnuw+Ql+2hCm7vQB6A==",
312 "keTZydopxtiTY7HVoqeWGw=="
313 ]
314 ```
315
316 The order in which the keys appear is important. The key appearing first in the
317 list is the primary key, which is the key used to encrypt all outgoing messages.
318 The remaining keys in the list are considered secondary and are used for
319 decryption only. During message decryption, Serf uses the configured encryption
320 keys in the order they appear in the keyring file until all keys are exhausted.
0 ---
1 layout: "docs"
2 page_title: "RPC"
3 sidebar_current: "docs-agent-rpc"
4 description: |-
5 The Serf agent provides a complete RPC mechanism that can be used to control the agent programmatically. This RPC mechanism is the same one used by the CLI, but can be used by other applications to easily leverage the power of Serf without directly embedding. Additionally, it can be used as a fast IPC mechanism to allow applications to receive events immediately instead of using the fork/exec model of event handlers.
6 ---
7
8 # RPC Protocol
9
10 The Serf agent provides a complete RPC mechanism that can
11 be used to control the agent programmatically. This RPC
12 mechanism is the same one used by the CLI, but can be
13 used by other applications to easily leverage the power
14 of Serf without directly embedding. Additionally, it can
15 be used as a fast IPC mechanism to allow applications to
16 receive events immediately instead of using the fork/exec
17 model of event handlers.
18
19 A reference implementation in Go can be found [here](https://github.com/hashicorp/serf/blob/master/client/rpc_client.go).
20
21 ## Implementation Details
22
23 The RPC protocol is implemented using [MsgPack](http://msgpack.org/)
24 over TCP. This choice is driven by the fact that all operating
25 systems support TCP, and MsgPack provides a fast serialization format
26 that is broadly available across languages.
27
28 All RPC requests have a request header, and some requests have
29 a request body. The request header looks like:
30
31 ```
32 {"Command": "handshake", "Seq": 0}
33 ```
34
35 All responses have a response header, and some may contain
36 a response body. The response header looks like:
37
38 ```
39 {"Seq": 0, "Error": ""}
40 ```
41
42 The `Command` is used to specify what command the server should
43 run, and the `Seq` is used to track the request. Responses are
44 tagged with the same `Seq` as the request. This allows for some
45 concurrency on the server side, as requests are not purely FIFO.
46 Thus, the `Seq` value should not be re-used between commands.
47 All responses may be accompanied by an error.
48
49 Possible commands include:
50
51 * handshake - Used to initialize the connection, set the version
52 * auth - Used to authenticate a client
53 * event - Fires a new user event
54 * force-leave - Removes a failed node from the cluster
55 * join - Requests Serf join another node
56 * members - Returns the list of members
57 * members-filtered - Returns a subset of members
58 * tags - Modifies tags on a running Serf agent
59 * stream - Starts streaming events over the connection
60 * monitor - Starts streaming logs over the connection
61 * stop - Stops streaming logs or events
62 * leave - Serf agent performs a graceful leave and shutdown
63 * query - Initiates a new query
64 * respond - Responds to an incoming query
65 * install-key - Installs a new encryption key
66 * use-key - Changes the primary key used for encrypting messages
67 * remove-key - Removes an existing encryption key
68 * list-keys - Provides a list of encryption keys in use in the cluster
69 * stats - Provides a debugging information about the running serf agent
70
71 Below each command is documented along with any request or
72 response body that is applicable.
73
74 ### handshake
75
76 The handshake MUST be the first command that is sent, as it informs
77 the server which version the client is using.
78
79 The request header must be followed with a handshake body, like:
80
81 ```
82 {"Version": 1}
83 ```
84
85 The body specifies the IPC version being used, however only version
86 1 is currently supported. This is to ensure backwards compatibility
87 in the future.
88
89 There is no special response body, but the client should wait for the
90 response and check for an error.
91
92 ### auth
93
94 If the agent is configured to use an auth key, then the client must
95 issue an `auth` command after the handshake is complete.
96
97 The auth request body looks like:
98
99 ```
100 {"AuthKey": "my-secret-auth-token"}
101 ```
102
103 The `AuthKey` must be provided and is the authorization key.
104 There is no special response body.
105
106 ### event
107
108 The event command is used to fire a new user event. It takes the
109 following request body:
110
111 ```
112 {"Name": "foo", "Payload": "test payload", "Coalesce": true}
113 ```
114
115 The `Name` is a string, but `Payload` is just opaque bytes. Coalesce
116 is used to control if Serf should enable [event coalescing](/docs/commands/event.html).
117
118 There is no special response body.
119
120 ### force-leave
121
122 This command is used to remove failed nodes from a cluster. It takes
123 the following body:
124
125 ```
126 {"Node": "failed-node-name"}
127 ```
128
129 There is no special response body.
130
131 ### join
132
133 This command is used to join an existing cluster using a known node.
134 It takes the following body:
135
136 ```
137 {"Existing": ["192.168.0.1:6000", "192.168.0.2:6000"], "Replay": false}
138 ```
139
140 The `Existing` nodes are each contacted, and `Replay` controls if we will replay
141 old user events or if they will simply be ignored. The response body in addition
142 to the header is returned. The body looks like:
143
144 ```
145 {"Num": 2}
146 ```
147
148 The body returns the number of nodes successfully joined.
149
150 ### members
151
152 The members command is used to return all the known members and associated
153 information. There is no request body, but the response looks like:
154
155 ```
156 {"Members": [
157 {
158 "Name": "TestNode"
159 "Addr": [127, 0, 0, 1],
160 "Port": 5000,
161 "Tags": {
162 "role": "test"
163 },
164 "Status": "alive",
165 "ProtocolMin": 0,
166 "ProtocolMax": 3,
167 "ProtocolCur": 2,
168 "DelegateMin": 0,
169 "DelegateMax": 1,
170 "DelegateCur": 1,
171 },
172 ...]
173 }
174 ```
175
176 ### members-filtered
177
178 The members-filtered command is used to return a subset of the known members
179 based on their metadata. It takes the following body:
180
181 ```
182 {"Tags": {"key": "val"}, "Status": "alive", "Name": "node1"}
183 ```
184
185 `Tags` are used to filter nodes based on tag values. `Status` is used to filter
186 nodes based on operational status. `Name` is used to filter based on node names.
187 Both `Name` and `Status`, as well as all `Tags` values, can contain regular
188 expression patterns.
189
190 Note that regular expression patterns will automatically be placed between start
191 (`^`) and end (`$`) anchors.
192
193 The response will be in the same format as the `members` command.
194
195 ### tags
196
197 The tags command is used to alter the tags on a Serf agent while it is running.
198 A `member-update` event will be triggered immediately to notify the other agents
199 in the cluster of the change. The tags command can add new tags, modify existing
200 tags, or delete tags. The request body looks like:
201
202 ```
203 {"Tags": {"tag1": "val1"}, "DeleteTags": ["tag2"]}
204 ```
205
206 ### stream
207
208 The stream command is used to subscribe to a stream of all events
209 matching a given type filter. Events will continue to be sent until
210 the stream is stopped. The request body looks like:
211
212 ```
213 {"Type": "member-join,user:deploy"}`
214 ```
215
216 The format of type is the same as the [event handler](/docs/agent/event-handlers.html),
217 except no script is specified. The one exception is that `"*"` can be specified to
218 subscribe to all events.
219
220 The server will respond with a standard response header indicating if the stream
221 was successful. However, now as events occur they will be sent and tagged with
222 the same `Seq` as the stream command that matches.
223
224 Assume we issued the previous stream command with Seq `50`,
225 we may start getting messages like:
226
227 ```
228 {"Seq": 50, "Error": ""}
229 {
230 "Event": "user",
231 "LTime": 123,
232 "Name": "deploy",
233 "Payload": "9c45b87",
234 "Coalesce": true,
235 }
236
237 {"Seq": 50, "Error": ""}
238 {
239 "Event": "member-join",
240 "Members": [
241 {
242 "Name": "TestNode"
243 "Addr": [127, 0, 0, 1],
244 "Port": 5000,
245 "Tags": {
246 "role": "test"
247 },
248 "Status": "alive",
249 "ProtocolMin": 0,
250 "ProtocolMax": 3,
251 "ProtocolCur": 2,
252 "DelegateMin": 0,
253 "DelegateMax": 1,
254 "DelegateCur": 1,
255 },
256 ...
257 ]
258 }
259
260 {"Seq": 50, "Error": ""}
261 {
262 "Event": "query",
263 "ID": 1023,
264 "LTime": 125,
265 "Name": "load",
266 "Payload": "15m",
267 }
268 ```
269
270 It is important to realize that these messages are sent asynchronously,
271 and not in response to any command. That means if a client is streaming
272 commands, there may be events streamed while a client is waiting for a
273 response to a command. This is why the `Seq` must be used to pair requests
274 with their corresponding responses.
275
276 There is no limit to the number of concurrent streams a client can request,
277 however a message is not deduplicated, so if multiple streams match a given
278 event, it will be sent multiple times with the corresponding `Seq` number.
279
280 To stop streaming, the `stop` command is used.
281
282 ### monitor
283
284 The monitor command is similar to the stream command, but instead of
285 events it subscribes the channel to log messages from the Agent.
286
287 The request is like:
288
289 ```
290 {"LogLevel": "DEBUG"}
291 ```
292
293 This subscribes the client to all messages of at least DEBUG level.
294
295 The server will respond with a standard response header indicating if the monitor
296 was successful. However, now as logs occur they will be sent and tagged with
297 the same `Seq` as the monitor command that matches.
298
299 Assume we issued the previous monitor command with Seq `50`,
300 we may start getting messages like:
301
302 ```
303 {"Seq": 50, "Error": ""}
304 {"Log": "2013/12/03 13:06:53 [INFO] agent: Received event: member-join"}
305 ```
306
307 It is important to realize that these messages are sent asynchronously,
308 and not in response to any command. That means if a client is streaming
309 commands, there may be logs streamed while a client is waiting for a
310 response to a command. This is why the `Seq` must be used to pair requests
311 with their corresponding responses.
312
313 The client can only be subscribed to at most a single monitor instance.
314 To stop streaming, the `stop` command is used.
315
316 ### stop
317
318 The stop command is used to stop either a stream or monitor.
319 The request looks like:
320
321 ```
322 {"Stop": 50}
323 ```
324
325 This unsubscribes the client from the monitor and/or stream registered
326 with `Seq` value of 50.
327
328 There is no special response body.
329
330 ### leave
331
332 The leave command is used trigger a graceful leave and shutdown.
333 There is no request body, or special response body.
334
335 ### query
336
337 The query command is used to issue a new query. It takes the following request body:
338
339 ```
340 {
341 "FilterNodes": ["foo", "bar"],
342 "FilterTags": {"role": ".*web.*"},
343 "RequestAck": true,
344 "Timeout": 0,
345 "Name": "load",
346 "Payload": "15m",
347 }
348 ```
349
350 The `Name` is a string, but `Payload` is just opaque bytes. The remaining fields are
351 optional. `FilterNodes` is used to restrict the nodes that should respond to only
352 those named. `FilterTags` is used to filter tags using a regular expression on each
353 tag. `RequestAck` is used to ask that nodes send an "ack" once the message is received,
354 otherwise only responses are delivered. `Timeout` can be provided (in nanoseconds) to
355 optionally override the default.
356
357 The server will respond with a standard response header indicating if the query
358 was successful. However, the channel is now subscribed to receive any acks or
359 responses. This is similar to `stream`, except scoped only to this query. The same
360 `Seq` is used as the query command that matches.
361
362 We will start to get the following:
363
364 ```
365 {"Seq": 50, "Error": ""}
366 {
367 "Type": "ack",
368 "From": "foo",
369 }
370
371 {"Seq": 50, "Error": ""}
372 {
373 "Type": "response",
374 "From": "foo",
375 "Payload": "1.02",
376 }
377
378 {"Seq": 50, "Error": ""}
379 {
380 "Type": "done",
381 }
382 ```
383
384 Each query record has a `Type` to indicate what is being represented. This is
385 one of `ack`, `response` or `done`. Once `done` is received the client should
386 not expect any further messages corresponding to that query.
387
388 ### respond
389
390 The respond command is with `stream` to subscribe to queries and then respond.
391 It takes the following request body:
392
393 ```
394 {"ID": 1023, "Payload": "my response"}
395 ```
396
397 The `ID` is an opaque value that is assigned by the IPC layer. This number is
398 unique per client connection and cannot be used across connections. `Payload` is
399 just opaque bytes.
400
401 There is no special response body.
402
403 ### install-key
404
405 The install-key command is used to install a new encryption key onto the
406 cluster's keyring.
407 The request looks like:
408
409 ```
410 {"Key": "lkuIAePQcb/XGvuLPqwNtw=="}
411 ```
412
413 The `Key` must be 16 bytes of base64-encoded data. This value can be generated
414 easily using the [keygen command](/docs/commands/keygen.html).
415
416 Once invoked, this method will begin broadcasting the new key to all members in
417 the cluster via the gossip protocol. Once the query has completed, a response
418 like the following will be returned:
419
420 ```
421 {
422 "Messages": {
423 "node1": "message from node1",
424 "node2": "message from node2"
425 },
426 "NumErr": 0,
427 "NumNodes": 2,
428 "NumResp": 2
429 }
430 ```
431
432 The `Messages` field contains a per-node mapping of messages. Messages may be
433 informational or error messages, which can be determined by examining the other
434 fields in the response. The `NumErr` field indicates the total number of
435 errors encountered by members during the query. `NumNodes` indicates the total
436 number of members in the cluster, and `NumResp` indicates the number of
437 responses received during the query.
438
439 ### use-key
440
441 The use-key command is used to change the primary key, which is used to encrypt
442 messages.
443 The request looks like:
444
445 ```
446 {"Key": "lkuIAePQcb/XGvuLPqwNtw=="}
447 ```
448
449 The key requested must already exist in the keyring of all agents for this
450 call to succeed. Once invoked, this method will broadcast the desired key to all
451 members. The members will attempt to change their current primary key pointer
452 and respond with the result.
453
454 The response returned by this method is in the same format as the `install-key`
455 call.
456
457 ### remove-key
458
459 The remove-key command is used to remove a key from the cluster's keyring.
460 The request looks like:
461
462 ```
463 {"Key": "lkuIAePQcb/XGvuLPqwNtw=="}
464 ```
465
466 The key requested must already exist in the keyring of each agent for this
467 command to succeed. Once invoked, this method will broadcast the key requested
468 for deletion to all members in the cluster and ask them to remove it from their
469 internal keyring. Each node will reply with their individual results.
470
471 The response returned by this method is in the same format as the `install-key`
472 call.
473
474 **NOTE**: If the key requested for deletion is currently the primary key on any
475 node, that node will report failure and refuse to remove the key.
476
477 ### list-keys
478
479 The list-keys command is used to return a list of all encryption keys currently
480 in use on the cluster.
481 There is no request body, but the response looks like:
482
483 ```
484 {
485 "Messages": {
486 "node1": "message from node1",
487 "node2": "message from node2"
488 },
489 "Keys": {
490 "lkuIAePQcb/XGvuLPqwNtw==": 2,
491 "FhADzydYiGiVz3vW7wpunQ==": 1
492 },
493 "NumErr": 0,
494 "NumNodes": 2,
495 "NumResp": 2
496 }
497 ```
498
499 This response body is almost the same as the other key operations, but notice
500 that it contains a `Keys` field. The `Keys` field lists all keys known to the
501 cluster, and how many members know about it. Typically if key broadcasting is
502 successful, this number should be equivalent to the `NumNodes` field. If not all
503 members are aware of a key, you should either rebroadcast that key using the
504 `install-key` RPC command, or remove it using the `remove-key` RPC command. More
505 on encryption keys can be found on the
506 [agent encryption](/docs/agent/encryption.html) page.
507
508 ### stats
509
510 The stats command is used to obtain operator debugging information about the
511 running serf agent. There is no request body, but the response looks like:
512
513 ```
514 {
515 "agent": {
516 "name": "node1"
517 },
518 "runtime": {
519 "os": "linux",
520 "arch": "amd64",
521 "version": "go1.2",
522 "max_procs": "1",
523 "goroutines": "22",
524 "cpu_count": "4"
525 },
526 "serf": {
527 "failed": "0",
528 "left": "0",
529 "event_time": "1",
530 "query_time": "1",
531 "event_queue": "0",
532 "members": "5",
533 "member_time": "5",
534 "intent_queue": "0",
535 "query_queue": "0"
536 },
537 "tags": {}
538 }
539 ```
0 ---
1 layout: "docs"
2 page_title: "Telemetry"
3 sidebar_current: "docs-agent-telemetry"
4 description: |-
5 The Serf agent collects various metrics data at runtime about the performance of different libraries and sub-systems. These metrics are aggregated on a ten second interval and are retained for one minute.
6 ---
7
8 # Telemetry
9
10 The Serf agent collects various metrics data at runtime about the performance
11 of different libraries and sub-systems. These metrics are aggregated on a ten second
12 interval and are retained for one minute.
13
14 To view the telemetry information, you must send a `USR1` signal to the Serf
15 process. Windows users must use the `BREAK` signal instead.
16 Once Serf receives the signal, it will dump the current telemetry
17 information to the stderr of the agent.
18
19 In general, the telemetry information is used for debugging or otherwise
20 getting a better view into what Serf is doing.
21
22 Below is an example output:
23
24 ```
25 [2014-01-29 10:56:50 -0800 PST][G] 'serf-agent.runtime.num_goroutines': 19.000
26 [2014-01-29 10:56:50 -0800 PST][G] 'serf-agent.runtime.alloc_bytes': 755960.000
27 [2014-01-29 10:56:50 -0800 PST][G] 'serf-agent.runtime.malloc_count': 7550.000
28 [2014-01-29 10:56:50 -0800 PST][G] 'serf-agent.runtime.free_count': 4387.000
29 [2014-01-29 10:56:50 -0800 PST][G] 'serf-agent.runtime.heap_objects': 3163.000
30 [2014-01-29 10:56:50 -0800 PST][G] 'serf-agent.runtime.total_gc_pause_ns': 1151002.000
31 [2014-01-29 10:56:50 -0800 PST][G] 'serf-agent.runtime.total_gc_runs': 4.000
32 [2014-01-29 10:56:50 -0800 PST][C] 'serf-agent.agent.ipc.accept': Count: 5 Sum: 5.000
33 [2014-01-29 10:56:50 -0800 PST][C] 'serf-agent.agent.ipc.command': Count: 10 Sum: 10.000
34 [2014-01-29 10:56:50 -0800 PST][C] 'serf-agent.serf.events': Count: 5 Sum: 5.000
35 [2014-01-29 10:56:50 -0800 PST][C] 'serf-agent.serf.events.foo': Count: 4 Sum: 4.000
36 [2014-01-29 10:56:50 -0800 PST][C] 'serf-agent.serf.events.baz': Count: 1 Sum: 1.000
37 [2014-01-29 10:56:50 -0800 PST][S] 'serf-agent.memberlist.gossip': Count: 50 Min: 0.007 Mean: 0.020 Max: 0.041 Stddev: 0.007 Sum: 0.989
38 [2014-01-29 10:56:50 -0800 PST][S] 'serf-agent.serf.queue.Intent': Count: 10 Sum: 0.000
39 [2014-01-29 10:56:50 -0800 PST][S] 'serf-agent.serf.queue.Event': Count: 10 Min: 0.000 Mean: 2.500 Max: 5.000 Stddev: 2.121 Sum: 25.000
40 ```
41
0 ---
1 layout: "docs"
2 page_title: "Commands: Agent"
3 sidebar_current: "docs-commands-agent"
4 description: |-
5 The `serf agent` command is the heart of Serf: it runs the agent that performs the important task of maintaining membership information, propagating events, detecting failures, etc.
6 ---
7
8 # Serf Agent
9
10 Command: `serf agent`
11
12 The `serf agent` command is the heart of Serf: it runs the agent that
13 performs the important task of maintaining membership information,
14 propagating events, detecting failures, etc.
15
16 Due to the power and flexibility of this command, the Serf agent
17 is documented in its own section. See the [Serf Agent](/docs/agent/basics.html)
18 section for more information on how to use this command and the
19 options it has.
0 ---
1 layout: "docs"
2 page_title: "Commands: Event"
3 sidebar_current: "docs-commands-event"
4 description: |-
5 The `serf event` command dispatches a custom user event into a Serf cluster, leveraging Serf's gossip layer for scalable broadcasting of the event to clusters of any size.
6 ---
7
8 # Serf Event
9
10 Command: `serf event`
11
12 The `serf event` command dispatches a custom user event into a Serf cluster,
13 leveraging Serf's gossip layer for scalable broadcasting of the event to
14 clusters of any size.
15
16 Nodes in the cluster can listen for these custom events and react to them.
17 Example use cases of custom events are to trigger deploys across web nodes
18 by sending a "deploy" event, possibly with a commit payload. Another use
19 case might be to send a "restart" event, asking the cluster members to
20 restart.
21
22 Ultimately, `serf event` is used to send custom events of your choosing
23 that you can respond to in _any way_ you want. The power in Serf's custom
24 events is the scalability over other systems.
25
26 The main distinction between a Serf query and an event is that events
27 are fire-and-forget. The Serf client will send the event immediately and
28 will broadcast to the entire cluster. Events have no filtering mechanism
29 and cannot reply or acknowledge receipt. Serf also tries harder to deliver
30 events, by performing anti-entropy over TCP as well as message replay.
31
32 Queries are intended to be a real-time request and response mechanism.
33 Since they are intended to be time sensitive, Serf will not do message
34 replay or anti-entropy, as a response to a very old query is not useful.
35 Queries have more advanced filtering mechanisms and can be used to build
36 more complex control flow. For example, a code deploy could check that at
37 least 90% of nodes successfully deployed before continuing.
38
39 ## Usage
40
41 Usage: `serf event [options] name [payload]`
42
43 The following command-line options are available for this command.
44 Every option is optional:
45
46 * `-coalesce=true/false` - Sets whether or not this event can be coalesced
47 by Serf. By default this is set to true. Read the section on event
48 coalescing for more information on what this means.
49
50 * `-rpc-addr` - Address to the RPC server of the agent you want to contact
51 to send this command. If this isn't specified, the command will contact
52 "127.0.0.1:7373" which is the default RPC address of a Serf agent. This option
53 can also be controlled using the `SERF_RPC_ADDR` environment variable.
54
55 * `-rpc-auth` - Optional RPC auth token. If the agent is configured to use
56 an auth token, then this must be provided or the agent will refuse the
57 command.
58
59 ## Sending an Event
60
61 To send an event, use `serf event NAME` where NAME is the name of the
62 event to send. This call will return immediately, and Serf will use its
63 gossip layer to broadcast the event.
64
65 An event may also contain a payload. You may specify the payload using
66 the second parameter. For example: `serf event deploy 1234567890` would
67 send the "deploy" event with "1234567890" as the payload.
68
69 ## Receiving an Event
70
71 The events can be handled by registering an
72 [event handler](/docs/agent/event-handlers.html) with the Serf agent. The
73 documentation for how the user event is dispatched is all contained within
74 that linked page.
75
76 ## Event Coalescing
77
78 By default, Serf coalesces events of the same name within a short time
79 period. This means that if many events of the same name are received within
80 a short amount of time, the event handler is only invoked once, with the
81 last event of that name received during that time period.
82
83 Event coalescence works great for idempotent events such as "restart" or
84 events where only the last value in the payload really matters, like the
85 commit in a "deploy" event. In these cases, things just work.
86
87 Some events, however, shouldn't be coalesced. For example, if you had
88 an event "queue" that queues some item, then you want to make sure all
89 of those events are seen, even if many are sent in a short period of time.
90 In this case, the `-coalesce=false` flag should be passed to the
91 `serf event` command.
92
93 If you send some events of the same name with coalescence enabled and some
94 without, then only the events that have coalescing enabled will actually
95 coalesce. The others will always be delivered.
0 ---
1 layout: "docs"
2 page_title: "Commands: Force Leave"
3 sidebar_current: "docs-commands-forceleave"
4 description: |-
5 The `force-leave` command forces a member of a Serf cluster to enter the left state. Note that if the member is still actually alive, it will eventually rejoin the cluster. The true purpose of this method is to force remove "failed" nodes.
6 ---
7
8 # Serf Force Leave
9
10 Command: `serf force-leave`
11
12 The `force-leave` command forces a member of a Serf cluster to enter the
13 "left" state. Note that if the member is still actually alive, it will
14 eventually rejoin the cluster. The true purpose of this method is to force
15 remove "failed" nodes.
16
17 Serf periodically tries to reconnect to "failed" nodes in case it is a
18 network partition. After some configured amount of time (by default 24 hours),
19 Serf will reap "failed" nodes and stop trying to reconnect. The `force-leave`
20 command can be used to transition the "failed" nodes to "left" nodes more
21 quickly.
22
23 ## Usage
24
25 Usage: `serf force-leave [options] node`
26
27 The following command-line options are available for this command.
28 Every option is optional:
29
30 * `-rpc-addr` - Address to the RPC server of the agent you want to contact
31 to send this command. If this isn't specified, the command will contact
32 "127.0.0.1:7373" which is the default RPC address of a Serf agent. This option
33 can also be controlled using the `SERF_RPC_ADDR` environment variable.
34
35 * `-rpc-auth` - Optional RPC auth token. If the agent is configured to use
36 an auth token, then this must be provided or the agent will refuse the
37 command.
38
39
0 ---
1 layout: "docs"
2 page_title: "Commands"
3 sidebar_current: "docs-commands"
4 description: |-
5 Serf is controlled via a very easy to use command-line interface (CLI). Serf is only a single command-line application: `serf`. This application then takes a subcommand such as agent or members. The complete list of subcommands is in the navigation to the left.
6 ---
7
8 # Serf Commands (CLI)
9
10 Serf is controlled via a very easy to use command-line interface (CLI).
11 Serf is only a single command-line application: `serf`. This application
12 then takes a subcommand such as "agent" or "members". The complete list of
13 subcommands is in the navigation to the left.
14
15 The `serf` CLI is a well-behaved command line application. In erroneous
16 cases, a non-zero exit status will be returned. It also responds to `-h` and `--help`
17 as you'd most likely expect. And some commands that expect input accept
18 "-" as a parameter to tell Serf to read the input from stdin.
19
20 To view a list of the available commands at any time, just run `serf` with
21 no arguments:
22
23 ```
24 $ serf
25 usage: serf [--version] [--help] <command> [<args>]
26
27 Available commands are:
28 agent Runs a Serf agent
29 event Send a custom event through the Serf cluster
30 force-leave Forces a member of the cluster to enter the "left" state
31 info Provides debugging information for operators
32 join Tell Serf agent to join cluster
33 keygen Generates a new encryption key
34 keys Manipulate the internal encryption keyring used by Serf
35 leave Gracefully leaves the Serf cluster and shuts down
36 members Lists the members of a Serf cluster
37 monitor Stream logs from a Serf agent
38 query Send a query to the Serf cluster
39 reachability Test network reachability
40 tags Modify tags of a running Serf agent
41 version Prints the Serf version
42 ```
43
44 To get help for any specific command, pass the `-h` flag to the relevant
45 subcommand. For example, to see help about the `members` subcommand:
46
47 ```
48 $ serf members -h
49 Usage: serf members [options]
50
51 Outputs the members of a running Serf agent.
52
53 Options:
54
55 -rpc-addr=127.0.0.1:7373 RPC address of the Serf agent.
56 ```
0 ---
1 layout: "docs"
2 page_title: "Commands: Info"
3 sidebar_current: "docs-commands-info"
4 description: |-
5 The `serf info` command outputs the various debugging information that can be used by operators. It also provides the agent's local name, and currently set tags. It can be used as a way to gain more insight into the state of the local agent.
6 ---
7
8 # Serf Info
9
10 Command: `serf info`
11
12 The `serf info` command outputs the various debugging information that can
13 be used by operators. It also provides the agent's local name, and
14 currently set tags. It can be used as a way to gain more insight
15 into the state of the local agent.
16
17 ## Usage
18
19 Usage: `serf info [options]`
20
21 The command-line flags are all optional. The list of available flags are:
22
23 * `-format` - Controls the output format. Supports `text` and `json`.
24 The default format is `text`.
25
26 * `-rpc-addr` - Address to the RPC server of the agent you want to contact
27 to send this command. If this isn't specified, the command will contact
28 "127.0.0.1:7373" which is the default RPC address of a Serf agent. This option
29 can also be controlled using the `SERF_RPC_ADDR` environment variable.
30
31 * `-rpc-auth` - Optional RPC auth token. If the agent is configured to use
32 an auth token, then this must be provided or the agent will refuse the
33 command.
34
0 ---
1 layout: "docs"
2 page_title: "Commands: Join"
3 sidebar_current: "docs-commands-join"
4 description: |-
5 The `serf join` command tells a Serf agent to join an existing cluster. A new Serf agent must join with at least one existing member of a cluster in order to join an existing cluster. After joining that one member, the gossip layer takes over, propagating the updated membership state across the cluster.
6 ---
7
8 # Serf Join
9
10 Command: `serf join`
11
12 The `serf join` command tells a Serf agent to join an existing cluster.
13 A new Serf agent must join with at least one existing member of a cluster
14 in order to join an existing cluster. After joining that one member,
15 the gossip layer takes over, propagating the updated membership state across
16 the cluster.
17
18 If you don't join an existing cluster, then that agent is part of its own
19 isolated cluster. Other nodes can join it.
20
21 Agents can join other agents multiple times without issue. If a node that
22 is already part of a cluster joins another node, then the clusters of the
23 two nodes join to become a single cluster.
24
25 ## Usage
26
27 Usage: `serf join [options] address ...`
28
29 You may call join with multiple addresses if you want to try to join
30 multiple clusters. Serf will attempt to join all clusters, and the join
31 command will fail only if Serf was unable to join with any.
32
33 The command-line flags are all optional. The list of available flags are:
34
35 * `-replay` - If set, old user events from the past will be replayed for the
36 agent/cluster that is joining. Otherwise, past events will be ignored.
37
38 * `-rpc-addr` - Address to the RPC server of the agent you want to contact
39 to send this command. If this isn't specified, the command will contact
40 "127.0.0.1:7373" which is the default RPC address of a Serf agent. This option
41 can also be controlled using the `SERF_RPC_ADDR` environment variable.
42
43 * `-rpc-auth` - Optional RPC auth token. If the agent is configured to use
44 an auth token, then this must be provided or the agent will refuse the
45 command.
46
47 ## Replaying User Events
48
49 When joining a cluster, the past events that were sent to the cluster are
50 usually ignored. However, if you specify the `-replay` flag, then past events
51 will be replayed on the joining cluster.
52
53 Note that only a fixed amount of past events are stored. At the time of writing
54 this documentation, that fixed amount is 1024 events. Therefore, if you replay
55 events, you'll only receive the past 1024 events (if there are that many).
0 ---
1 layout: "docs"
2 page_title: "Commands: Keygen"
3 sidebar_current: "docs-commands-keygen"
4 description: |-
5 The `serf keygen` command generates an encryption key that can be used for Serf agent traffic encryption. The keygen command uses a cryptographically strong pseudo-random number generator to generate the key.
6 ---
7
8 # Serf Keygen
9
10 Command: `serf keygen`
11
12 The `serf keygen` command generates an encryption key that can be used for
13 [Serf agent traffic encryption](/docs/agent/encryption.html).
14 The keygen command uses a cryptographically
15 strong pseudo-random number generator to generate the key.
0 ---
1 layout: "docs"
2 page_title: "Commands: Key"
3 sidebar_current: "docs-commands-key"
4 description: |-
5 The `serf keys` command performs cluster-wide encryption key operations, such as installing new keys and removing old keys. When used properly, the `keys` command allows you to achieve non-disruptive encryption key rotation across a Serf cluster.
6 ---
7
8 # Serf Keys
9
10 Command: `serf keys`
11
12 The `serf keys` command performs cluster-wide encryption key operations, such as
13 installing new keys and removing old keys. When used properly, the `keys`
14 command allows you to achieve non-disruptive encryption key rotation across a
15 Serf cluster.
16
17 By default, changes made to the encryption keys will not be written to disk, and
18 will be lost upon agent restart. It is possible to enable persistence by using
19 the `-keyring-file` option to the Serf agent. More information is available on
20 the <a href="/docs/agent/options.html">agent configuration options</a> page.
21
22 Serf allows multiple encryption keys to be in use simultaneously. This is
23 intended to provide a transition state while the cluster converges. It is the
24 responsibility of the operator to ensure that only the required encryption keys
25 are installed on the cluster. You can ensure that a key is not installed using
26 the `-list` and `-remove` options.
27
28 All variations of the `keys` command will return 0 if all nodes reply and there
29 are no errors. If any node fails to reply or reports failure, the exit code will
30 be 1.
31
32 ## Usage
33
34 Usage: `serf keys [options]`
35
36 All operations are idempotent. The list of available flags are:
37
38 * `-install` - Install a new encryption key to the Serf keyring. This will
39 broadcast the new key to the cluster.
40
41 * `-use` - Change the primary encryption key. The primary key is the only key
42 used to encrypt messages, and is the first key used while decrypting messages.
43
44 * `-remove` - Remove a currently installed encryption key from the Serf keyring.
45 Any messages transmitted using this key after this operation completes will
46 fail verification and be rejected.
47
48 * `-list` - Ask all members in the cluster for a list of the keys they have
49 installed. After gathering keys from all members, the results will be returned
50 in a summary showing each key and the number of members which have that key
51 installed. This is useful to operators to ensure that a given key has been
52 installed on or removed from all members.
53
54 * `-rpc-addr` - Address to the RPC server of the agent you want to contact
55 to send this command. If this isn't specified, the command will contact
56 "127.0.0.1:7373" which is the default RPC address of a Serf agent. This option
57 can also be controlled using the `SERF_RPC_ADDR` environment variable.
58
59 * `-rpc-auth` - Optional RPC auth token. If the agent is configured to use
60 an auth token, then this must be provided or the agent will refuse the
61 command.
0 ---
1 layout: "docs"
2 page_title: "Commands: Leave"
3 sidebar_current: "docs-commands-leave"
4 description: |-
5 The `serf leave` command triggers a graceful leave and shutdown of the agent. This is used to ensure other nodes see the agent as left instead of failed. Nodes that leave will not attempt to re-join the cluster on restarting with a snapshot.
6 ---
7
8 # Serf Leave
9
10 Command: `serf leave`
11
12 The `serf leave` command triggers a graceful leave and shutdown of the agent.
13 This is used to ensure other nodes see the agent as "left" instead of "failed".
14 Nodes that leave will not attempt to re-join the cluster on restarting with a
15 snapshot.
16
17 ## Usage
18
19 Usage: `serf leave`
20
21 The command-line flags are all optional. The list of available flags are:
22
23 * `-rpc-addr` - Address to the RPC server of the agent you want to contact
24 to send this command. If this isn't specified, the command will contact
25 "127.0.0.1:7373" which is the default RPC address of a Serf agent. This option
26 can also be controlled using the `SERF_RPC_ADDR` environment variable.
27
28 * `-rpc-auth` - Optional RPC auth token. If the agent is configured to use
29 an auth token, then this must be provided or the agent will refuse the
30 command.
31
0 ---
1 layout: "docs"
2 page_title: "Commands: Members"
3 sidebar_current: "docs-commands-members"
4 description: |-
5 The `serf members` command outputs the current list of members that a Serf agent knows about, along with their state. The state of a node can only be alive, left or failed.
6 ---
7
8 # Serf Members
9
10 Command: `serf members`
11
12 The `serf members` command outputs the current list of members that a Serf
13 agent knows about, along with their state. The state of a node can only
14 be "alive", "left" or "failed".
15
16 Nodes in the "failed" state are still listed because Serf attempts to
17 reconnect with failed nodes for a certain amount of time in the case
18 that the failure is actually just a network partition.
19
20 ## Usage
21
22 Usage: `serf members [options]`
23
24 The command-line flags are all optional. The list of available flags are:
25
26 * `-detailed` - Will show additional information per member, such as the
27 protocol version that each can understand and that each is speaking.
28
29 * `-format` - Controls the output format. Supports `text` and `json`.
30 The default format is `text`.
31
32 * `-name` - If provided, only members with names matching this regular
33 expression will be returned.
34
35 * `-role` - If provided, output is filtered to only nodes matching
36 the regular expression for role. `-role` is deprecated in favor of
37 `-tag role=foo`. The regexp is anchored at the start and end,
38 and must be a full match.
39
40 * `-status` - If provided, output is filtered to only nodes matching
41 the regular expression for status. The regexp is anchored at the start
42 and end, and must be a full match.
43
44 * `-tag key=value` - If provided, output is filtered to only nodes with the specified
45 tag if its value matches the regular expression. tag can be specified
46 multiple times to filter on multiple keys. The regexp is anchored at the start
47 and end, and must be a full match.
48
49 * `-rpc-addr` - Address to the RPC server of the agent you want to contact
50 to send this command. If this isn't specified, the command will contact
51 "127.0.0.1:7373" which is the default RPC address of a Serf agent. This option
52 can also be controlled using the `SERF_RPC_ADDR` environment variable.
53
54 * `-rpc-auth` - Optional RPC auth token. If the agent is configured to use
55 an auth token, then this must be provided or the agent will refuse the
56 command.
57
0 ---
1 layout: "docs"
2 page_title: "Commands: Monitor"
3 sidebar_current: "docs-commands-monitor"
4 description: |-
5 The `serf monitor` command is used to connect and follow the logs of a running Serf agent. Monitor will show the recent logs and then continue to follow the logs, not exiting until interrupted or until the remote agent quits.
6 ---
7
8 # Serf Monitor
9
10 Command: `serf monitor`
11
12 The `serf monitor` command is used to connect and follow the logs of a running
13 Serf agent. Monitor will show the recent logs and then continue to follow
14 the logs, not exiting until interrupted or until the remote agent quits.
15
16 The power of the monitor command is that it allows you to log the agent
17 at a relatively high log level (such as "warn"), but still access debug
18 logs and watch the debug logs if necessary.
19
20 ## Usage
21
22 Usage: `serf monitor [options]`
23
24 The command-line flags are all optional. The list of available flags are:
25
26 * `-log-level` - The log level of the messages to show. By default this
27 is "info". This log level can be more verbose than what the agent is
28 configured to run at. Available log levels are "trace", "debug", "info",
29 "warn", and "err".
30
31 * `-rpc-addr` - Address to the RPC server of the agent you want to contact
32 to send this command. If this isn't specified, the command will contact
33 "127.0.0.1:7373" which is the default RPC address of a Serf agent. This option
34 can also be controlled using the `SERF_RPC_ADDR` environment variable.
35
36 * `-rpc-auth` - Optional RPC auth token. If the agent is configured to use
37 an auth token, then this must be provided or the agent will refuse the
38 command.
39
0 ---
1 layout: "docs"
2 page_title: "Commands: Query"
3 sidebar_current: "docs-commands-query"
4 description: |-
5 The `serf query` command dispatches a custom user query into a Serf cluster, efficiently broadcasting the query to all nodes, and gathering responses.
6 ---
7
8 # Serf Query
9
10 Command: `serf query`
11
12 The `serf query` command dispatches a custom user query into a Serf cluster,
13 efficiently broadcasting the query to all nodes, and gathering responses.
14
15 Nodes in the cluster can listen for queries and respond to them.
16 Example use cases of queries are to request load average, ask for the
17 version of an app that is deployed, or to trigger deploys across web nodes
18 by sending a "deploy" query, possibly with a commit payload.
19
20 The command will wait until the query finishes (by reaching a timeout) and
21 will report all acknowledgements and responses that are received.
22
23 The open ended nature of `serf query` allows you to send and respond to
24 queries in _any way_ you want.
25
26 The main distinction between a Serf query and an event is that events
27 are fire-and-forget. The Serf client will send the event immediately and
28 will broadcast to the entire cluster. Events have no filtering mechanism
29 and cannot reply or acknowledge receipt. Serf also tries harder to deliver
30 events, by performing anti-entropy over TCP as well as message replay.
31
32 Queries are intended to be a real-time request and response mechanism.
33 Since they are intended to be time sensitive, Serf will not do message
34 replay or anti-entropy, as a response to a very old query is not useful.
35 Queries have more advanced filtering mechanisms and can be used to build
36 more complex control flow. For example, a code deploy could check that at
37 least 90% of nodes successfully deployed before continuing.
38
39 ## Usage
40
41 Usage: `serf query [options] name [payload]`
42
43 The command-line flags are all optional. The list of available flags are:
44
45 * `-format` - Controls the output format. Supports `text` and `json`.
46 The default format is `text`.
47
48 * `-no-ack` - If provided, the query will not request that nodes acknowledge
49 receipt of the query. By default, any nodes that pass the `-node` and `-tag` filters
50 will acknowledge receipt of a query and potentially respond if they have a configured
51 event handler.
52
53 * `-node node` - If provided, output is filtered to only nodes with the given
54 node name. `-node` can be specified multiple times to allow multiple nodes.
55
56 * `-tag key=value` - If provided, output is filtered to only nodes with the specified
57 tag if its value matches the regular expression. tag can be specified
58 multiple times to filter on multiple keys.
59
60 * `-timeout=15s` - When provided, the given timeout overrides the default query timeout.
61 By default, a query has a timeout that is designed to give the cluster enough time
62 to gossip the message out and to respond. This is a computed multiple of the
63 GossipInterval, a QueryTimeoutMultipler and a logarithmic scale based on cluster size.
64 This time may be low for long running queries, so this flag can be used to specify
65 an alternative timeout.
66
67 * `-rpc-addr` - Address to the RPC server of the agent you want to contact
68 to send this command. If this isn't specified, the command will contact
69 "127.0.0.1:7373" which is the default RPC address of a Serf agent. This option
70 can also be controlled using the `SERF_RPC_ADDR` environment variable.
71
72 * `-rpc-auth` - Optional RPC auth token. If the agent is configured to use
73 an auth token, then this must be provided or the agent will refuse the
74 command.
75
0 ---
1 layout: "docs"
2 page_title: "Commands: Reachability"
3 sidebar_current: "docs-commands-reachability"
4 description: |-
5 The `reachability` command performs a basic network reachability test. The local node will gossip out a ping message and request that all other nodes acknowledge delivery of the message.
6 ---
7
8 # Serf Reachability
9
10 Command: `serf reachability`
11
12 The `reachability` command performs a basic network reachability test.
13 The local node will gossip out a "ping" message and request that all other
14 nodes acknowledge delivery of the message.
15
16 This can be used to troubleshoot configurations or network issues, since
17 nodes that are detected as having failed may respond, indicating false-failure
18 detection, or live nodes may fail to respond, indicating networking issues.
19
20 In general, the following troubleshooting tips are recommended:
21
22 * Ensure that the bind addr:port is accessible by all other nodes
23 * If an advertise address is set, ensure it routes to the bind address
24 * Check that no nodes are behind a NAT
25 * If nodes are behind firewalls or iptables, check that Serf traffic is permitted (UDP and TCP)
26 * Verify networking equipment is functional
27
28 ## Usage
29
30 Usage: `serf reachability [options]`
31
32 The following command-line options are available for this command.
33 Every option is optional:
34
35 * `-rpc-addr` - Address to the RPC server of the agent you want to contact
36 to send this command. If this isn't specified, the command will contact
37 "127.0.0.1:7373" which is the default RPC address of a Serf agent. This option
38 can also be controlled using the `SERF_RPC_ADDR` environment variable.
39
40 * `-rpc-auth` - Optional RPC auth token. If the agent is configured to use
41 an auth token, then this must be provided or the agent will refuse the
42 command.
43
44 * `-verbose` - Enables verbose output
45
0 ---
1 layout: "docs"
2 page_title: "Commands: Tags"
3 sidebar_current: "docs-commands-tags"
4 description: |-
5 The `serf tags` command modifies a member's tags while the Serf agent is running. The changed tags will be immediately propagated to other members in the cluster.
6 ---
7
8 # Serf Tags
9
10 Command: `serf tags`
11
12 The `serf tags` command modifies a member's tags while the Serf agent is running.
13 The changed tags will be immediately propagated to other members in the
14 cluster.
15
16 By default, any tag changes made at runtime are not written to disk.
17 Tag persistence can be enabled using the `-tags-file` option to the Serf
18 agent. More information is available on the
19 <a href="/docs/agent/options.html">agent configuration options</a> page.
20
21 Tag changes can also be handled using
22 <a href="/intro/getting-started/event-handlers.html">event handlers</a> and the
23 `member-update` event.
24
25 ## Usage
26
27 Usage: `serf tags [options]`
28
29 At least one of `-set` or `-delete` must be passed. The list of available
30 flags are:
31
32 * `-set` - Will either create a new tag on a member, or update it if it
33 already exists with a new value. Must be passed as `-set tag=value`. Can
34 be passed multiple times to set multiple tags.
35
36 * `-delete` - Delete an existing tag from a member. Can be passed multiple
37 times to delete multiple tags.
38
39 * `-rpc-addr` - Address to the RPC server of the agent you want to contact
40 to send this command. If this isn't specified, the command will contact
41 "127.0.0.1:7373" which is the default RPC address of a Serf agent. This option
42 can also be controlled using the `SERF_RPC_ADDR` environment variable.
43
44 * `-rpc-auth` - Optional RPC auth token. If the agent is configured to use
45 an auth token, then this must be provided or the agent will refuse the
46 command.
0 ---
1 layout: "docs"
2 page_title: "Serf Protocol Compatibility Promise"
3 sidebar_current: "docs-upgrading-compatibility"
4 description: |-
5 We expect Serf to run in large clusters as long-running agents. Because upgrading agents in this sort of environment relies heavily on protocol compatibility, this page makes it clear on our promise to keeping different Serf versions compatible with each other.
6 ---
7
8 # Protocol Compatibility Promise
9
10 We expect Serf to run in large clusters as long-running agents. Because
11 upgrading agents in this sort of environment relies heavily on protocol
12 compatibility, this page makes it clear on our promise to keeping different
13 Serf versions compatible with each other.
14
15 We promise that every subsequent release of Serf will remain backwards
16 compatible with _at least_ one prior version. Concretely: version 0.5 can
17 speak to 0.4 (and vice versa), but may not be able to speak to 0.1.
18
19 The backwards compatibility must be explicitly enabled: Serf agents by
20 default will speak the latest protocol, but can be configured to speak earlier
21 ones. If speaking an earlier protocol, _new features may not be available_.
22 The ability for an agent to speak an earlier protocol is only so that they
23 can be upgraded without cluster disruption.
24
25 This compatibility guarantee makes it possible to upgrade Serf agents one
26 at a time, one version at a time. For more details on the specifics of
27 upgrading, see the [upgrading page](/docs/upgrading.html).
28
29 ## Protocol Compatibility Table
30
31 <table class="table table-bordered table-striped">
32 <tr>
33 <th>Version</th>
34 <th>Protocol Compatibility</th>
35 </tr>
36 <tr>
37 <td>0.1</td>
38 <td>0</td>
39 </tr>
40 <tr>
41 <td>0.2</td>
42 <td>0, 1</td>
43 </tr>
44 <tr>
45 <td>0.3</td>
46 <td>0, 1, 2&nbsp;&nbsp;&nbsp;<span class="label label-info">see warning below</span></td>
47 </tr>
48 <tr>
49 <td>0.4</td>
50 <td>1, 2, 3&nbsp;&nbsp;&nbsp;<span class="label label-info">see warning below</span></td>
51 </tr>
52 <tr>
53 <td>0.5</td>
54 <td>2, 3, 4&nbsp;&nbsp;&nbsp;<span class="label label-info">see warning below</span></td>
55 </tr>
56 <tr>
57 <td>0.6</td>
58 <td>2, 3, 4&nbsp;&nbsp;&nbsp;<span class="label label-info">see warning below</span></td>
59 </tr>
60 </table>
61
62 ~> **Warning!** Version 0.3 introduces support for dynamic ports, allowing each
63 agent to bind to a different port. However, this feature is only supported
64 if all agents are running protocol version 2. Due to the nature of this
65 feature, it is hard to detect using the versioning scheme. If ports are kept
66 consistent across the cluster, then protocol version 2 is fully backwards
67 compatible.
68
69 ~> **Warning!** Version 0.4 introduces support for dynamic tags, allowing each
70 agent to provide key/value tags and update them without restarting. This feature is only supported
71 if all agents are running protocol version 3. If an agent is running an older protocol,
72 then only the "role" tag is supported for backwards compatibility.
73
74 ~> **Warning!** Version 0.6 introduces support for key rotation. This feature
75 uses the same protocol version, but requires that all agents be on 0.6. Attempting to use
76 key rotation will result in errors.
0 ---
1 layout: "docs"
2 page_title: "Documentation"
3 sidebar_current: "docs-home"
4 description: |-
5 Welcome to the Serf documentation! This documentation is more of a reference guide for all available features and options of Serf. If you're just getting started with Serf, please start with the introduction and getting started guide instead.
6 ---
7
8 # Serf Documentation
9
10 Welcome to the Serf documentation! This documentation is more of a reference
11 guide for all available features and options of Serf. If you're just getting
12 started with Serf, please start with the
13 [introduction and getting started guide](/intro/index.html) instead.
0 ---
1 layout: "docs"
2 page_title: "Gossip Protocol"
3 sidebar_current: "docs-internals-gossip"
4 description: |-
5 Serf uses a gossip protocol to broadcast messages to the cluster. This page documents the details of this internal protocol. The gossip protocol is based on SWIM: Scalable Weakly-consistent Infection-style Process Group Membership Protocol, with a few minor adaptations, mostly to increase propagation speed and convergence rate.
6 ---
7
8 # Gossip Protocol
9
10 Serf uses a [gossip protocol](http://en.wikipedia.org/wiki/Gossip_protocol)
11 to broadcast messages to the cluster. This page documents the details of
12 this internal protocol. The gossip protocol is based on
13 ["SWIM: Scalable Weakly-consistent Infection-style Process Group Membership Protocol"](http://www.cs.cornell.edu/~asdas/research/dsn02-swim.pdf),
14 with a few minor adaptations, mostly to increase propagation speed
15 and convergence rate.
16
17 ~> **Advanced Topic!** This page covers the technical details of
18 the internals of Serf. You don't need to know these details to effectively
19 operate and use Serf. These details are documented here for those who wish
20 to learn about them without having to go spelunking through the source code.
21
22 ## SWIM Protocol Overview
23
24 Serf begins by joining an existing cluster or starting a new
25 cluster. If starting a new cluster, additional nodes are expected to join
26 it. New nodes in an existing cluster must be given the address of at
27 least one existing member in order to join the cluster. The new member
28 does a full state sync with the existing member over TCP and begins gossiping its
29 existence to the cluster.
30
31 Gossip is done over UDP with a configurable but fixed fanout and interval.
32 This ensures that network usage is constant with regards to number of nodes.
33 Complete state exchanges with a random node are done periodically over
34 TCP, but much less often than gossip messages. This increases the likelihood
35 that the membership list converges properly since the full state is exchanged
36 and merged. The interval between full state exchanges is configurable or can
37 be disabled entirely.
38
39 Failure detection is done by periodic random probing using a configurable interval.
40 If the node fails to ack within a reasonable time (typically some multiple
41 of RTT), then an indirect probe is attempted. An indirect probe asks a
42 configurable number of random nodes to probe the same node, in case there
43 are network issues causing our own node to fail the probe. If both our
44 probe and the indirect probes fail within a reasonable time, then the
45 node is marked "suspicious" and this knowledge is gossiped to the cluster.
46 A suspicious node is still considered a member of cluster. If the suspect member
47 of the cluster does not dispute the suspicion within a configurable period of
48 time, the node is finally considered dead, and this state is then gossiped
49 to the cluster.
50
51 This is a brief and incomplete description of the protocol. For a better idea,
52 please read the
53 [SWIM paper](http://www.cs.cornell.edu/~asdas/research/dsn02-swim.pdf)
54 in its entirety, along with the Serf source code.
55
56 ## SWIM Modifications
57
58 As mentioned earlier, the gossip protocol is based on SWIM but includes
59 minor changes, mostly to increase propagation speed and convergence rates.
60
61 The changes from SWIM are noted here:
62
63 * Serf does a full state sync over TCP periodically. SWIM only propagates
64 changes over gossip. While both are eventually consistent, Serf is able to
65 more quickly reach convergence, as well as gracefully recover from network
66 partitions.
67
68 * Serf has a dedicated gossip layer separate from the failure detection
69 protocol. SWIM only piggybacks gossip messages on top of probe/ack messages.
70 Serf uses piggybacking along with dedicated gossip messages. This
71 feature lets you have a higher gossip rate (for example once per 200ms)
72 and a slower failure detection rate (such as once per second), resulting
73 in overall faster convergence rates and data propagation speeds.
74
75 * Serf keeps the state of dead nodes around for a set amount of time,
76 so that when full syncs are requested, the requester also receives information
77 about dead nodes. Because SWIM doesn't do full syncs, SWIM deletes dead node
78 state immediately upon learning that the node is dead. This change again helps
79 the cluster converge more quickly.
80
81 ## Serf-Specific Messages
82
83 On top of the SWIM-based gossip layer, Serf sends some custom message types.
84
85 Serf makes heavy use of [Lamport clocks](http://en.wikipedia.org/wiki/Lamport_timestamps)
86 to maintain some notion of message ordering despite being eventually
87 consistent. Every message sent by Serf contains a Lamport clock time.
88
89 When a node gracefully leaves the cluster, Serf sends a _leave intent_ through
90 the gossip layer. Because the underlying gossip layer makes no differentiation
91 between a node leaving the cluster and a node being detected as failed, this
92 allows the higher level Serf layer to detect a failure versus a graceful
93 leave.
94
95 When a node joins the cluster, Serf sends a _join intent_. The purpose
96 of this intent is solely to attach a Lamport clock time to a join so that
97 it can be ordered properly in case a leave comes out of order.
98
99 For custom events and queries, Serf sends either a _user event_,
100 or _user query_ message. This message contains a Lamport time, event name, and event payload.
101 Because user events are sent along the gossip layer, which uses UDP, the payload and entire message framing
102 must fit within a single UDP packet.
0 ---
1 layout: "docs"
2 page_title: "Internals"
3 sidebar_current: "docs-internals"
4 description: |-
5 This section goes over some of the internals of Serf, such as the gossip protocol, ordering of messages via Lamport clocks, etc. This section also contains a useful convergence simulator that can be used to see how fast a Serf cluster will converge under various conditions with specific configurations.
6 ---
7
8 # Serf Internals
9
10 This section goes over some of the internals of Serf, such as the gossip
11 protocol, ordering of messages via Lamport clocks, etc. This section
12 also contains a useful [convergence simulator](/docs/internals/simulator.html)
13 that can be used to see how fast a Serf cluster will converge under
14 various conditions with specific configurations.
15
16 -> **Note:** Knowing about the internals of Serf is not necessary to
17 successfully use it, but we document it here to be completely transparent
18 about how the "magic" of Serf works.
0 ---
1 layout: "docs"
2 page_title: "Security Model"
3 sidebar_current: "docs-internals-security"
4 description: |-
5 Serf uses a symmetric key, or shared secret, cryptosystem to provide confidentiality, integrity and authentication.
6 ---
7
8 # Security Model
9
10 Serf uses a symmetric key, or shared secret, cryptosystem to provide
11 [confidentiality, integrity and authentication](http://en.wikipedia.org/wiki/Information_security).
12
13 This means Serf communication is protected against eavesdropping, tampering,
14 or attempts to generate fake events. This makes it possible to run Serf over
15 untrusted networks such as EC2 and other shared hosting providers.
16
17 ~> **Advanced Topic!** This page covers the technical details of
18 the security model of Serf. You don't need to know these details to
19 operate and use Serf. These details are documented here for those who wish
20 to learn about them without having to go spelunking through the source code.
21
22 ## Security Primitives
23
24 The Serf security model is built on around a symmetric key, or shared secret system.
25 All members of the Serf cluster must be provided the shared secret ahead of time.
26 This places the burden of key distribution on the user.
27
28 To support confidentiality, all messages are encrypted using the
29 [AES-128 standard](http://en.wikipedia.org/wiki/Advanced_Encryption_Standard). The
30 AES standard is considered one of the most secure and modern encryption standards.
31 Additionally, it is a fast algorithm, and modern CPUs provide hardware instructions to
32 make encryption and decryption very lightweight.
33
34 AES is used with the [Galois Counter Mode (GCM)](http://en.wikipedia.org/wiki/Galois/Counter_Mode),
35 using a randomly generated nonce. The use of GCM provides message integrity,
36 as the ciphertext is suffixed with a 'tag' that is used to verify integrity.
37
38 ## Message Format
39
40 In the previous section we described the crypto primitives that are used. In this
41 section we cover how messages are framed on the wire and interpreted.
42
43 ### UDP Message Format
44
45 UDP messages do not require any framing since they are packet oriented. This
46 allows the message to be simple and saves space. The format is as follows:
47
48 -------------------------------------------------------------------
49 | Version (byte) | Nonce (12 bytes) | CipherText | Tag (16 bytes) |
50 -------------------------------------------------------------------
51
52 The UDP message has an overhead of 29 bytes per message.
53 Tampering or bit corruption will cause the GCM tag verification to fail.
54
55 Once we receive a packet, we first verify the GCM tag, and only on verification,
56 decrypt the payload. The version byte is provided to allow future versions to
57 change the algorithm they use. It is currently always set to 0.
58
59 ### TCP Message Format
60
61 TCP provides a stream abstraction and therefore we must provide our own framing.
62 This introduces a potential attack vector since we cannot verify the tag
63 until the entire message is received, and the message length must be in plaintext.
64 Our current strategy is to limit the maximum size of a framed message to prevent
65 a malicious attacker from being able to send enough data to cause a Denial of Service.
66
67 The TCP format is similar to the UDP format, but prepends the message with
68 a message type byte (similar to other Serf messages). It also adds a 4 byte length
69 field, encoded in Big Endian format. This increases its maximum overhead to 33 bytes.
70
71 When we first receive a TCP encrypted message, we check the message type. If any
72 party has encryption enabled, the other party must as well. Otherwise we are vulnerable
73 to a downgrade attack where one side can force the other into a non-encrypted mode of
74 operation.
75
76 Once this is verified, we determine the message length and if it is less than our limit,.
77 After the entire message is received, the tag is used to verify the entire message.
78
79 ## Threat Model
80
81 The following are the various parts of our threat model:
82
83 * Non-members getting access to events
84 * Cluster state manipulation due to malicious messages
85 * Fake event generation due to malicious messages
86 * Tampering of messages causing state corruption
87 * Denial of Service against a node
88
89 We are specifically not concerned about replay attacks, as the gossip
90 protocol is designed to handle that due to the nature of its broadcast mechanism.
91
92 Additionally, we recognize that an attacker that can observe network
93 traffic for an extended period of time may infer the cluster members.
94 The gossip mechanism used by Serf relies on sending messages to random
95 members, so an attacker can record all destinations and determine all
96 members of the cluster.
97
98 When designing security into a system you design it to fit the threat model.
99 Our goal is not to protect top secret data but to provide a "reasonable"
100 level of security that would require an attacker to commit a considerable
101 amount of resources to defeat.
102
103 ## Key Rotation
104
105 Serf supports rotating keys. Because our security model assumes that all current
106 Serf members are not compromised, we are able to use our own gossip mechanism to
107 distribute new keys.
108
109 The basic flow of changing the encryption key on a given Serf cluster is:
110
111 * Broadcast new key to cluster via gossip
112 * Instruct all members to update the key used to encrypt messages
113 * Remove old key
114
115 Due to the nature of distributed systems, it is difficult to reason about when
116 to change the key used for message encryption on any given member in a
117 cluster. Therefore, Serf allows multiple keys to be used to decrypt messages
118 while the cluster converges. Decrypting messages becomes more expensive while
119 there is more than one key active, as multiple attempts to decrypt any given
120 message are required. For this reason, utilizing multiple keys is only
121 recommended as a transition state.
122
123 ## Future Roadmap
124
125 Eventually, Serf will be able to use the versioning byte to support
126 different encryption algorithms. These could be configured at the
127 start time of the agent.
0 ---
1 layout: "docs"
2 page_title: "Convergence Simulator"
3 sidebar_current: "docs-internals-simulator"
4 description: |-
5 The graph below shows the expected time to reach various states of convergence depending on the settings which are tunable below the graph. Below the graph, the estimated maximum bandwidth usage is shown per node in kilobits per second.
6 ---
7
8 <h1>Serf Convergence Simulator</h1>
9
10 <p>
11 The graph below shows the expected time to reach various states of convergence
12 depending on the settings which are tunable below the graph. Below the graph,
13 the estimated maximum bandwidth usage is shown per node in <em>kilobits</em>
14 per second.
15 </p>
16 <p>
17 The default values in the boxes are also the default values that Serf
18 is configured with, where applicable.
19 </p>
20
21 <div class="row">
22 <div id="graph"></div>
23 </div>
24 <div class="row">
25 <div class="col-md-12">
26 <h4>Estimated max bandwidth: <span id="bytes">0</span> kbps/node</h4>
27 </div>
28 </div>
29 <div class="row">
30 <div class="col-md-6">
31 <h5>Gossip Interval</h5>
32 <p>The gossip interval controls how often messages are gossiped to other nodes</p>
33 <input type="text" id="interval" value="0.2"> seconds
34 </div>
35 <div class="col-md-6">
36 <h5>Gossip Fanout</h5>
37 <p>The gossip fanout controls how many nodes we gossip with</p>
38 <input type="text" id="fanout" value="3"> nodes
39 </div>
40 </div>
41 <div class="row">
42 <div class="col-md-6">
43 <h5>Nodes</h5>
44 <p>This controls how many simulated nodes are in the cluster</p>
45 <input type="text" id="nodes" value="30">
46 </div>
47 <div class="col-md-6">
48 <h5>Packet Loss</h5>
49 <p>This controls the amount of simulated packet loss [0, 100)</p>
50 <input type="text" id="packetloss" value="0"> % lost packets
51 </div>
52 </div>
53 <div class="row">
54 <div class="col-md-6">
55 <h5>Node failures</h5>
56 <p>This controls what percent of simulated nodes are failed</p>
57 <input type="text" id="failed" value="0"> % failed
58 </div>
59 <div class="col-md-6">
60 </div>
61 </div>
0 ---
1 layout: "docs"
2 page_title: "Agent Uptime Recipe"
3 sidebar_current: "docs-recipes"
4 description: |-
5 On occasion users have asked for the ability to identify the uptime of a Serf agent. While Serf does not expose this directly in the RPC layer, it is quite easy to utilize tags to accomplish this.
6 ---
7
8 # Recipe: Agent Uptime
9
10 On occasion users have asked for the ability to identify the uptime of a Serf
11 agent. While Serf does not expose this directly in the RPC layer, it is quite
12 easy to utilize tags to accomplish this.
13
14 The solution is simple. While starting a Serf agent, add a tag to indicate the
15 time at which it was started:
16
17 ```
18 serf agent -tag start_time=`date +%s`
19 ```
20
21 While the agent is running, you can retrieve its uptime in seconds by getting
22 the current UNIX timestamp and doing simple subtraction.
0 ---
1 layout: "docs"
2 page_title: "Event Handler Router Recipe"
3 sidebar_current: "docs-recipes"
4 description: |-
5 Typically you must configure a handler for each type of event you expect to encounter. To change handler configuration, one must update the Serf configuration and reload the agent with a SIGHUP or a restart.
6 ---
7
8 # Recipe: Event handler router
9
10 Typically you must configure a handler for each type of event you expect to
11 encounter (more about events and handlers [here](/docs/agent/event-handlers.html)).
12 To change handler configuration, one must update the Serf configuration and reload
13 the agent with a SIGHUP or a restart.
14
15 Thanks to the flexibility and open-endedness Serf offers for configuring and
16 executing handlers, it is possible to achieve similar functionality by
17 configuring only a single "router" handler, which never needs to be updated.
18 This removes the orchestration work of reloading the agents by allowing one to
19 simply drop new executables with predictable names into a directory.
20
21 Handler executables must be named by event type for this recipe to work
22 (e.g. `member-join`). User events get prefixed with "user-", and queries with
23 "query-" (`user-deploy`, `query-uptime`, etc.).
24
25 What you will end up with is a directory structure that looks like this:
26
27 ```
28 $ tree /etc/serf
29 /etc/serf
30 └── handlers
31 ├── member-failed
32 ├── member-join
33 ├── member-leave
34 ├── member-update
35 ├── user-deploy
36 └── query-uptime
37 ```
38
39 ## Handler code
40
41 The following code must be configured as the only handler for this recipe to
42 work. It will act as a catch-all this way and be able to make decisions on what
43 script handler to invoke based on the Serf environment variables.
44
45 ```
46 #!/bin/sh
47 HANDLER_DIR="/etc/serf/handlers"
48
49 if [ "$SERF_EVENT" = "user" ]; then
50 EVENT="user-$SERF_USER_EVENT"
51 elif [ "$SERF_EVENT" = "query" ]; then
52 EVENT="query-$SERF_QUERY_NAME"
53 else
54 EVENT=$SERF_EVENT
55 fi
56
57 HANDLER="$HANDLER_DIR/$EVENT"
58 [ -f "$HANDLER" -a -x "$HANDLER" ] && exec "$HANDLER" || :
59 ```
60
61 ## pluginhook
62
63 The [pluginhook](https://github.com/progrium/pluginhook) project offers
64 an alternative way that enables multiple plugins to handle a hook, instead
65 of only a single handler.
66
67 Using this enables a tree structure that looks like:
68
69 ```
70 $ tree /etc/serf
71 /etc/serf
72 └── handlers
73 ├── a-1st-plugin
74 | ├── member-failed
75 | ├── member-join
76 | ├── member-leave
77 | └── member-update
78 ├── a-2nd-plugin
79 | ├── member-update
80 | ├── user-deploy
81 | └── query-uptime
82 └── an-nth-plugin
83 ├── member-join
84 └── member-leave
85 ```
86
87 Using this requires only requires minor modifications to our handler script
88 above:
89
90 ```
91 #!/bin/sh
92 HANDLER_DIR="/etc/serf/handlers"
93
94 if [ "$SERF_EVENT" = "user" ]; then
95 EVENT="user-$SERF_USER_EVENT"
96 elif [ "$SERF_EVENT" = "query" ]; then
97 EVENT="query-$SERF_QUERY_NAME"
98 else
99 EVENT=$SERF_EVENT
100 fi
101
102 cd $HANDLER_DIR
103 pluginhook $EVENT
104 ```
105
106 # serf-master
107
108 The [serf-master](https://github.com/garethr/serf-master) project provides the same functionality but allows you to write handlers strictly in Python. Using serf-master might be a better approach if you are more comfortable with Python than Bash.
0 ---
1 layout: "docs"
2 page_title: "Recipes"
3 sidebar_current: "docs-recipes"
4 description: |-
5 The examples on the following pages have proven useful to users in the Serf community.
6 ---
7
8 # Serf Recipes
9
10 The examples on the following pages have proven useful to users in the Serf
11 community.
12
13 -> If you have any tricks or tips that help you better leverage Serf, consider
14 sharing your recipe with the community by opening a new issue or creating a pull
15 request.
16
17 ---
18
19 * [Event handler router](/docs/recipes/event-handler-router.html) - Provides
20 dynamic event handler configuration using a simple shell script.
21
22 * [Get agent uptime](/docs/recipes/agent-uptime.html) - Expose the elapsed time
23 since startup of a Serf agent.
0 ---
1 layout: "docs"
2 page_title: "Product Roadmap"
3 sidebar_current: "docs-roadmap"
4 description: |-
5 Serf is a young project with big ambitions. What we've built and shipped already is a solid, powerful piece of software that solves many real world problems. But we have many plans to improve and iterate on Serf to make it even better. This page outlines some of the plans we have for future versions of Serf.
6 ---
7
8 # Serf Project Roadmap
9
10 Serf is a young project with big ambitions. What we've built and shipped
11 already is a solid, powerful piece of software that solves many real world
12 problems. But we have
13 many plans to improve and iterate on Serf to make it even better. This
14 page outlines some of the plans we have for future versions of Serf.
15
16 Because Serf is an open source project, you as a member of the Serf
17 community have a big say in what features and improvements you want
18 to see in Serf.
19 If you have ideas for Serf, please feel free to post them to our
20 [issue tracker](https://github.com/hashicorp/serf/issues) so that we can
21 discuss them.
22
23 Finally, note that this roadmap is not exhaustive. We may be working on
24 features or changes that aren't listed here.
25
26 ## Roadmap
27
28 * **More fine-grained configuration**. The current release of Serf doesn't
29 give you fine-grained control over many of the tunables of the gossip
30 layer. A future version of Serf will allow you to modify these tunables
31 so that Serf may work more efficiently in any environment you put it in.
32
33 * **Event handler library**. We think that there are many cases
34 for generic event handlers. We plan on building into Serf a method of
35 sharing and quickly "installing" event handlers so that you can more
36 easily get Serf working with common software projects. Imagine, for example:
37 `serf plugin install haproxy` to instantly get event handlers for adding
38 and removing nodes to HAProxy.
0 ---
1 layout: "docs"
2 page_title: "Upgrading Serf"
3 sidebar_current: "docs-upgrading-upgrading"
4 description: |-
5 Serf is meant to be a long-running agent on any nodes participating in a Serf cluster. These nodes consistently communicate with each other. As such, protocol level compatibility and ease of upgrades is an important thing to keep in mind when using Serf.
6 ---
7
8 # Upgrading Serf
9
10 Serf is meant to be a long-running agent on any nodes participating in a
11 Serf cluster. These nodes consistently communicate with each other. As such,
12 protocol level compatibility and ease of upgrades is an important thing to
13 keep in mind when using Serf.
14
15 This page documents how to upgrade Serf when a new version is released.
16
17 ## Upgrading Serf
18
19 In short, upgrading Serf is a short series of easy steps. For the steps
20 below, assume you're running version A of Serf, and then version B comes out.
21
22 1. On each node, install version B of Serf.
23
24 2. Shut down version A, and start version B with the `-protocol=PREVIOUS`
25 flag, where "PREVIOUS" is the protocol version of version A (which can
26 be discovered by running `serf -v` or `serf members -detailed).
27
28 3. Once all nodes are running version B, go through every node and restart
29 the version B agent _without_ the `-protocol` flag.
30
31 4. Done! You're now running the latest Serf agent speaking the latest protocol.
32 You can verify this is the case by running `serf members -detailed` to
33 make sure all members are speaking the same, latest protocol version.
34
35 The key to making this work is the [protocol compatibility](/docs/compatibility.html)
36 of Serf. The protocol version system is discussed below.
37
38 ## Protocol Versions
39
40 By default, Serf agents speak the latest protocol they can. However, each
41 new version of Serf is also able to speak the previous protocol, if there
42 were any protocol changes.
43
44 You can see what protocol versions your version of Serf understands by
45 running `serf -v`. You'll see output similar to that below:
46
47 ```
48 $ serf -v
49 Serf v0.2.0
50 Agent Protocol: 1 (Understands back to: 0)
51 ```
52
53 This says the version of Serf as well as the latest protocol version (1,
54 in this case). It also says the earliest protocol version that this Serf
55 agent can understand (0, in this case).
56
57 By specifying the `-protocol` flag on `serf agent`, you can tell the
58 Serf agent to speak any protocol version that it can understand. This
59 only specifies the protocol version to _speak_. Every Serf agent can
60 always understand the entire range of protocol versions it claims to
61 on `serf -v`.
62
63 ~> **By running a previous protocol version**, some features
64 of Serf, especially newer features, may not be available. If this is the
65 case, Serf will typically warn you. In general, you should always upgrade
66 your cluster so that you can run the latest protocol version.
0 ---
1 page_title: "Downloads"
2 description: |-
3 Below are all available downloads for the latest version of Serf. Please download the proper package for your operating system and architecture.
4 ---
5
6 <section class="downloads">
7 <div class="container bs-docs-section">
8 <div class="description row">
9 <div class="col-md-8 col-md-offset-2">
10 <h1>Downloads</h1>
11 <p>
12 Below are all available downloads for the latest version of Serf
13 (<%= latest_version %>). Please download the proper package for your
14 operating system and architecture. You can find SHA256 checksums
15 for packages <a href="https://dl.bintray.com/mitchellh/serf/<%= latest_version %>_SHA256SUMS?direct">here</a>.
16 </p>
17 </div>
18 </div>
19 <% product_versions.each do |os, versions| %>
20 <div class="row">
21 <div class="col-md-8 col-md-offset-2 download">
22 <div class="icon pull-left"><%= system_icon(os) %></div>
23 <div class="details">
24 <h2 class="os-name"><%= os %></h2>
25 <ul>
26 <% versions.each do |url| %>
27 <li><a href="<%= url %>"><%= arch_for_filename(url) %></a></li>
28 <% end %>
29 </ul>
30 <div class="clearfix"></div>
31 </div>
32 </div>
33 </div>
34 <% end %>
35
36 <div class="row">
37 <div class="col-md-8 col-md-offset-2 poweredby">
38 <a href='http://www.bintray.com'>
39 <img src='https://www.bintray.com/docs/images/poweredByBintray_ColorTransparent.png'>
40 </a>
41 </div>
42 </div>
43 </div>
44 </section>
0 ---
1 description: |-
2 Serf is a decentralized solution for cluster membership, failure detection, and orchestration. Lightweight and highly available.
3 ---
4 <!-- Main jumbotron for a primary marketing message or call to action -->
5 <div id="jumbotron">
6 <div class="container">
7 <div class="jumbo-logo hidden-xs hidden-sm"></div>
8 <div class="col-lg-5 col-md-5">
9 <h2 class="rls-l">Serf is a decentralized solution for cluster membership, failure detection, and orchestration. Lightweight and highly available.</h2>
10 </div>
11 <!-- <p><a class="btn btn-primary btn-lg">Learn more &raquo;</a></p> -->
12 </div>
13 </div>
14
15 <div id="features">
16 <div class="container">
17 <!-- Example row of columns -->
18 <div class="row">
19 <div class="col-lg-5 col-md-5">
20 <span class="icn icn1"></span>
21 </div>
22 <div class="col-lg-7 col-md-7">
23 <h2>Gossip-based Membership</h2>
24 <p>Serf relies on an efficient and lightweight gossip protocol to communicate with nodes. The Serf agents periodically exchange messages with each other in much the same way that a zombie apocalypse would occur: it starts with one zombie but soon infects everyone. In practice, the gossip is <a href="/docs/internals/simulator.html">very fast and extremely efficient.</a></p>
25 </div>
26 </div>
27 <div class="row">
28 <div class="col-lg-7 col-md-7">
29 <h2>Failure Detection</h2>
30 <p>Serf is able to quickly detect failed members and notify the rest of the cluster. This failure detection is built into the heart of the gossip protocol used by Serf. Like humans in a zombie apocalypse, everybody checks their peers for infection and quickly alerts the other living humans. Serf relies on a random probing technique which is proven to efficiently scale to clusters of any size.</p>
31 </div>
32 <div class="col-lg-5 col-md-5">
33 <span class="icn icn2"></span>
34 </div>
35 </div>
36 <div class="row">
37 <div class="col-lg-5 col-md-5">
38 <span class="icn icn3"></span>
39 </div>
40 <div class="col-lg-7 col-md-5">
41 <h2>Custom Events</h2>
42 <p>In addition to managing membership, Serf can broadcast custom events and queries. These can be used to trigger deploys, restart processes, spread tales of human heroism, and anything else you may want. The event system is flexible and lightweight, making it easy for application developers and sysadmins alike to leverage.</p>
43 </div>
44 </div>
45 </div> <!-- /container -->
46 </div> <!-- /features -->
0 ---
1 layout: "intro"
2 page_title: "Run the Agent"
3 sidebar_current: "gettingstarted-agent"
4 description: |-
5 After Serf is installed, the agent must be run. The agent is a lightweight process that runs until told to quit and maintains cluster membership and communication. The agent must be run for every node that will be part of the cluster.
6 ---
7
8 # Run the Serf Agent
9
10 After Serf is installed, the agent must be run. The agent is a lightweight
11 process that runs until told to quit and maintains cluster membership
12 and communication. The agent must be run for every node that will be part of
13 the cluster.
14
15 It is possible to run multiple agents, and thus participate in multiple Serf
16 clusters. For example, you may want to run a separate Serf cluster to
17 maintain web server membership info for a load balancer from another Serf
18 cluster that manages membership of Memcached nodes, but perhaps the web
19 servers need to be part of the Memcached cluster too so they can be notified
20 when Memcached nodes come online or go offline. Other examples include a Serf
21 cluster within a datacenter, and a separate cluster used for cross WAN gossip
22 which has more relaxed timing.
23
24 ## Starting the Agent
25
26 For simplicity, we'll run a single Serf agent right now:
27
28 ```
29 $ serf agent
30 ==> Starting Serf agent...
31 ==> Serf agent running!
32 Node name: 'foobar'
33 Bind addr: '0.0.0.0:7946'
34 RPC addr: '127.0.0.1:7373'
35
36 ==> Log data will now stream in as it occurs:
37
38 2013/10/21 18:57:15 [INFO] Serf agent starting
39 2013/10/21 18:57:15 [INFO] serf: EventMemberJoin: mitchellh.local 10.0.1.60
40 2013/10/21 18:57:15 [INFO] Serf agent started
41 2013/10/21 18:57:15 [INFO] agent: Received event: member-join
42 ```
43
44 As you can see, the Serf agent has started and has output some log
45 data. From the log data, you can see that a member has joined the cluster.
46 This member is yourself.
47
48 ## Cluster Members
49
50 If you run `serf members` in another terminal, you can see the members of
51 the Serf cluster. You should only see one member (yourself). We'll cover
52 joining clusters in the next section.
53
54 ```
55 $ serf members
56 mitchellh.local 10.0.1.60 alive
57 ```
58
59 This command, along with many others, communicates with a running Serf
60 agent via an internal RPC protocol. When starting the Serf agent, you
61 may have noticed that it tells you the "RPC addr". This is the address
62 that commands such as `serf members` use to communicate with the agent.
63
64 By default, RPC listens only on loopback, so it is inaccessible outside
65 of your machine for security reasons.
66
67 If you're running multiple Serf agents, you'll have to specify
68 an `-rpc-addr` to both the agent and any commands so that it doesn't
69 collide with other agents.
70
71 ## Stopping the Agent
72
73 You can use `Ctrl-C` (the interrupt signal) to gracefully halt the agent.
74 After interrupting the agent, you should see it leave the cluster gracefully
75 and shut down.
76
77 By gracefully leaving, Serf notifies other cluster members that the
78 node _left_. If you had forcibly killed the agent process, other members
79 of the cluster would have detected that the node _failed_. This can be a
80 crucial difference depending on what your use case of Serf is. Serf will
81 automatically try to reconnect to _failed_ nodes, which allows it to recover
82 from certain network conditions, while _left_ nodes are no longer contacted.
83
0 ---
1 layout: "intro"
2 page_title: "Event Handlers"
3 sidebar_current: "gettingstarted-eventhandlers"
4 description: |-
5 We've now seen how to start Serf agents and join them into a cluster. While this is cool to see on its own, the true power and utility of Serf is being able to react to membership changes and other events that Serf invokes. By specifying event handlers, Serf will invoke custom scripts whenever an event is received.
6 ---
7
8 # Event Handlers
9
10 We've now seen how to start Serf agents and join them into a cluster.
11 While this is cool to see on its own, the true power and utility of Serf
12 is being able to react to membership changes and other events that Serf
13 invokes. By specifying _event handlers_, Serf will invoke custom scripts
14 whenever an event is received.
15
16 ## Our First Event Handler
17
18 To start, let's create our first event handler. Create a shell script
19 named `handler.sh` with the following contents and make sure that it is
20 set to be executable (`chmod +x handler.sh`).
21
22 ```bash
23 #!/bin/bash
24
25 echo
26 echo "New event: ${SERF_EVENT}. Data follows..."
27 while read line; do
28 printf "${line}\n"
29 done
30 ```
31
32 This will be the script that we'll tell Serf to invoke for any event.
33 The script outputs the event, which Serf puts into the `SERF_EVENT`
34 environmental variable. The data for a Serf event always comes in via
35 stdin, so the script then reads stdin and outputs any data it received.
36
37 By sending data to stdin, Serf works extremely well with standard Unix
38 tools such as `grep`, `sed`, `awk`, etc. Shell commands work when specifying
39 event handlers, so by using standard Unix methodologies, complex event
40 handlers can often be built up without resorting to custom scripts.
41
42 ## Specifying an Event Handler
43
44 With the event handler written, let's start an agent with that event handler.
45 By setting the log-level to "debug", Serf will output the stdout/stderr
46 of the event handlers, so we can see them being run:
47
48 ```
49 $ serf agent -log-level=debug -event-handler=handler.sh
50 ==> Starting Serf agent...
51 ==> Serf agent running!
52 Node name: 'foobar'
53 Bind addr: '0.0.0.0:7946'
54 RPC addr: '127.0.0.1:7373'
55
56 ==> Log data will now stream in as it occurs:
57
58 2013/10/22 06:54:04 [INFO] Serf agent starting
59 2013/10/22 06:54:04 [INFO] serf: EventMemberJoin: mitchellh 127.0.0.1
60 2013/10/22 06:54:04 [INFO] Serf agent started
61 2013/10/22 06:54:04 [INFO] agent: Received event: member-join
62 2013/10/22 06:54:04 [DEBUG] Event 'member-join' script output:
63 New event: member-join. Data follows...
64 mitchellh.local 127.0.0.1
65 ```
66
67 As you can see from the tail end of the output, the event script was
68 executed and displayed the event that was run along with the data
69 of that event.
70 In this case, the event was a "member-join" event and the data was
71 a single member, ourself.
72
73 In practice, an event script would do something like adding a web
74 server to a load balancer, monitoring that node with Nagios, etc.
75
76 ## Types of Events
77
78 There are currently four types of events that Serf invokes:
79
80 * `member-join` - One or more members have joined the cluster.
81 * `member-leave` - One or more members have gracefully left the cluster.
82 * `member-failed` - One or more members have failed, meaning that they
83 didn't properly respond to ping requests.
84 * `member-update` - One or more members have updated, likely to update the
85 associated tags
86 * `member-reap` - Serf has removed one or more members from it's list of members.
87 This means a failed node exceeded the `reconnect_timeout`, or a left node reached
88 the `tombstone_timeout`.
89 * `user` - A custom user event, covered later in this guide.
90 * `query` - A query event, covered later in this guide
91
92 ## Multiple Event Scripts, Filtering, And More
93
94 For the purposes of introduction, we showed how to use a single event
95 handler with Serf. This event handler responded to all events. However,
96 the event handling system is actually far more robust: Serf is able
97 to invoke multiple event handlers as well as invoke certain event handlers
98 for only certain Serf events.
99
100 To learn more about these features, see the full documentation section
101 of [event handlers](/docs/agent/event-handlers.html).
102
0 ---
1 layout: "intro"
2 page_title: "Installing Serf"
3 sidebar_current: "gettingstarted-install"
4 description: |-
5 Serf must first be installed on every node that will be a member of a Serf cluster. To make installation easy, Serf is distributed as a binary package for all supported platforms and architectures. This page will not cover how to compile Serf from source.
6 ---
7
8 # Install Serf
9
10 Serf must first be installed on every node that will be a member of a
11 Serf cluster. To make installation easy, Serf is distributed as a
12 [binary package](/downloads.html) for all supported platforms and
13 architectures. This page will not cover how to compile Serf from
14 source.
15
16 ## Installing Serf
17
18 To install Serf, find the [appropriate package](/downloads.html) for
19 your system and download it. Serf is packaged as a "zip" archive.
20
21 After downloading Serf, unzip the package. Copy the `serf` binary to
22 somewhere on the PATH so that it can be executed. On Unix systems,
23 `~/bin` and `/usr/local/bin` are common installation directories,
24 depending on if you want to restrict the install to a single user or
25 expose it to the entire system. On Windows systems, you can put it wherever
26 you would like.
27
28 ### OS X
29
30 If you are using [homebrew](http://brew.sh/#install) as a package manager,
31 than you can install serf as simple as:
32 ```
33 brew cask install serf
34 ```
35
36 if you are missing the [cask plugin](http://caskroom.io/) you can install it with:
37 ```
38 brew install caskroom/cask/brew-cask
39 ```
40
41 ## Verifying the Installation
42
43 After installing Serf, verify the installation worked by opening a new
44 terminal session and checking that `serf` is available. By executing
45 `serf` you should see help output similar to that below:
46
47 ```
48 $ serf
49 usage: serf [--version] [--help] <command> [<args>]
50
51 Available commands are:
52 agent Runs a Serf agent
53 event Send a custom event through the Serf cluster
54 force-leave Forces a member of the cluster to enter the "left" state
55 info Provides debugging information for operators
56 join Tell Serf agent to join cluster
57 keygen Generates a new encryption key
58 keys Manipulate the internal encryption keyring used by Serf
59 leave Gracefully leaves the Serf cluster and shuts down
60 members Lists the members of a Serf cluster
61 monitor Stream logs from a Serf agent
62 query Send a query to the Serf cluster
63 reachability Test network reachability
64 tags Modify tags of a running Serf agent
65 version Prints the Serf version
66 ```
67
68 If you get an error that `serf` could not be found, then your PATH
69 environmental variable was not setup properly. Please go back and ensure
70 that your PATH variable contains the directory where Serf was installed.
71
72 Otherwise, Serf is installed and ready to go!
0 ---
1 layout: "intro"
2 page_title: "Join a Cluster"
3 sidebar_current: "gettingstarted-join"
4 description: |-
5 In the previous page, we started our first agent. While it showed how easy it is to run Serf, it wasn't very exciting since we simply made a cluster of one member. In this page, we'll create a real cluster with multiple members.
6 ---
7
8 # Join a Cluster
9
10 In the previous page, we started our first agent. While it showed how easy
11 it is to run Serf, it wasn't very exciting since we simply made a cluster of
12 one member. In this page, we'll create a real cluster with multiple members.
13
14 When starting a Serf agent, it begins without knowledge of any other node, and is
15 an isolated cluster of one. To learn about other cluster members, the agent must
16 _join_ an existing cluster. To join an existing cluster, Serf only needs to know
17 about a _single_ existing member. After it joins, the agent will gossip with this
18 member and quickly discover the other members in the cluster.
19
20 ## Starting the Agents
21
22 To simulate a more realistic cluster, we are using a two node cluster in
23 Vagrant. The Vagrantfile can be found in the demo section of the repo
24 [here](https://github.com/hashicorp/serf/tree/master/demo/vagrant-cluster).
25
26 We start the first agent on our first node and also
27 specify a node name. The node name must be unique and is how a machine
28 is uniquely identified. By default it is the hostname of the machine, but
29 we'll manually override it. We are also providing a bind address. This is the
30 address that Serf listens on, and it *must* be accessible by all other nodes
31 in the cluster.
32
33 ```
34 $ serf agent -node=agent-one -bind=172.20.20.10
35 ...
36 ```
37
38 Then, in another terminal, start the second agent on the new node.
39 This time, we set the bind address to match the IP of the second node
40 as specified in the Vagrantfile. In production, you will generally want
41 to provide a bind address or interface as well.
42
43 ```
44 $ serf agent -node=agent-two -bind=172.20.20.11
45 ...
46 ```
47
48 At this point, you have two Serf agents running. The two Serf agents
49 still don't know anything about each other, and are each part of their own
50 clusters (of one member). You can verify this by running `serf members`
51 against each agent and noting that only one member is a part of each.
52
53 ## Joining a Cluster
54
55 Now, let's tell the first agent to join the second agent by running
56 the following command in a new terminal:
57
58 ```
59 $ serf join 172.20.20.11
60 Successfully joined cluster by contacting 1 nodes.
61 ```
62
63 You should see some log output in each of the agent logs. If you read
64 carefully, you'll see that they received join information. If you
65 run `serf members` against each agent, you'll see that both agents now
66 know about each other:
67
68 ```
69 $ serf members
70 agent-one 172.20.20.10:7946 alive
71 agent-two 172.20.20.11:7946 alive
72 ```
73
74 -> **Note:** To join a cluster, a Serf agent needs to only
75 learn about <em>one existing member</em>. After joining the cluster, the
76 agents gossip with each other to propagate full membership information.
77
78 In addition to using `serf join` you can use the `-join` flag on
79 `serf agent` to join a cluster as part of starting up the agent.
80
81 ## Leaving a Cluster
82
83 To leave the cluster, you can either gracefully quit an agent (using
84 `Ctrl-C`) or force kill one of the agents. Gracefully leaving allows
85 the node to transition into the _left_ state, otherwise other nodes
86 will detect it as having _failed_. The difference is covered
87 in more detail [here](/intro/getting-started/agent.html#toc_3).
0 ---
1 layout: "intro"
2 page_title: "Next Steps"
3 sidebar_current: "gettingstarted-nextsteps"
4 description: |-
5 That concludes the getting started guide for Serf. Hopefully you're able to see that while Serf is an incredibly simple tool, it is also extremely powerful. The dead simple membership information and events system that Serf provides make up the building blocks of incredible use cases.
6 ---
7
8 # Next Steps
9
10 That concludes the getting started guide for Serf. Hopefully you're able to
11 see that while Serf is an incredibly simple tool, it is also extremely
12 powerful. The dead simple membership information and events system that
13 Serf provides make up the building blocks of [incredible use cases](/intro/use-cases.html).
14
15 And because Serf is completely decentralized, fault tolerant, self-healing,
16 etc. it is a dream tool for system administrators and built specifically
17 for modern, elastic infrastructures.
18
19 As a next step, the following resources are available:
20
21 * [Documentation](/docs/index.html) - The documentation is an in-depth reference
22 guide to all the features of Serf, including technical details about the
23 internals of how Serf operates.
24
25 * [Examples](https://github.com/hashicorp/serf/tree/master/demo) - The work-in-progress examples folder within the GitHub
26 repository for Serf contains functional examples of various use cases
27 of Serf to help you get started with exactly what you need.
0 ---
1 layout: "intro"
2 page_title: "Custom Queries"
3 sidebar_current: "gettingstarted-queries"
4 description: |-
5 While custom events provide an efficient "fire-and-forget" mechanism, queries send a request and nodes can provide responds. Custom queries provide even more flexibility than events, since the target nodes can be filtered, delivery can be acknowledged and custom responses can be sent back. This makes queries useful for gathering information about a running cluster in real-time.
6 ---
7
8 # Custom Queries
9
10 While custom events provide an efficient "fire-and-forget" mechanism, queries
11 send a request and nodes can provide responds. Custom queries provide even more
12 flexibility than events, since the target nodes can be filtered, delivery
13 can be acknowledged and custom responses can be sent back. This makes queries
14 useful for gathering information about a running cluster in real-time.
15
16 ## Sending Custom Queries
17
18 First, start a Serf agent so we can see the query being sent. Since we'll
19 just be running a single agent, running `serf agent` by itself is fine.
20 Then, to send a custom query, use the `serf query` command:
21
22 ```
23 $ serf query load
24 ```
25
26 If you look at the output of `serf agent`, you should see that it received
27 the user query:
28
29 ```
30 ...
31 2013/10/22 07:06:32 [INFO] agent: Received event: query: load
32 ```
33
34 If the cluster were made up of multiple members, all of the reachable
35 members would have received this query.
36
37 Event handlers used with queries are even more powerful, as their
38 output is sent back to the query originator. This enables the query make
39 a "request", while the event handler can generate the response.
40
41 For example, if we had a "load" custom event, we might create return
42 the current load average of the machine.
43
44 ## Query Payloads
45
46 Queries are not limited to just a query name. The query can also contain
47 a payload: arbitrary data associated with the query. With our same agent
48 running, let's deliver an event with a payload: `serf query my-name-is Mitchell`
49
50 In practice, query payloads can contain information such as the git commit
51 to deploy if you're using Serf as a deployment tool. Or perhaps it contains
52 some updated configuration to modify on the nodes. It can contain anything
53 you'd like; it is up to the event handler to use it in some meaningful way.
54
55 ## Limitations
56
57 Custom queries are delivered using the Serf gossip layer. The benefits of
58 this approach is that you get completely decentralized messaging across
59 your entire cluster that is fault tolerant.
60
61 Due to the mechanics of gossip, custom queries are highly scalable: Serf doesn't
62 need to connect to each and every node to send the message, it only needs to connect
63 to a handful, regardless of cluster size. It does require that all responses are
64 sent directly to the originator, which can be an issue if many nodes are responding.
65
66 Custom events come with some trade-offs, however:
67
68 * Queries may not be delivered to all nodes: Because events are delivered over
69 gossip, the message arrives at every node unless there is a network partition.
70 Queries are designed for real-time use cases, and because responses to old queries
71 are not useful, Serf will not attempt to redeliver queries to nodes that were
72 offline or could not be reached. This is a critical distinction from custom events,
73 which do attempt a later delivery.
74
75 * Responses and acknowledgements are sent over UDP back to the query originator.
76 Since UDP does not provide any of the reliability or retry mechanisms of TCP,
77 this means that although a query handler may be invoked, the response may
78 not be received due to network issues.
79
80 * Payload size is limited: Serf gossips via UDP, so the payload must fit
81 within a single UDP packet (alongside any other data Serf sends). This
82 limits the potential size of a payload to less than 1 KB.
83
0 ---
1 layout: "intro"
2 page_title: "Custom User Events"
3 sidebar_current: "gettingstarted-userevents"
4 description: |-
5 In addition to the standard membership-related events that Serf fires, Serf is able to propagate custom events across the cluster. Events are fire-and-forget. There is no originating node, an event has no response, and Serf works hard to ensure it is delivered to all nodes. Custom events are useful for tasks such as: triggering deploys, telling the cluster to restart, etc.
6 ---
7
8 # Custom User Events
9
10 In addition to the standard membership-related events that Serf fires,
11 Serf is able to propagate custom events across the cluster. Events are
12 "fire-and-forget". There is no originating node, an event has no response,
13 and Serf works hard to ensure it is delivered to all nodes. Custom events
14 are useful for tasks such as: triggering deploys, telling the cluster to
15 restart, etc.
16
17 ## Sending Custom Events
18
19 First, start a Serf agent so we can see the event being sent. Since we'll
20 just be running a single agent, running `serf agent` by itself is fine.
21 Then, to send a custom event, use the `serf event` command:
22
23 ```
24 $ serf event hello-there
25 ```
26
27 If you look at the output of `serf agent`, you should see that it received
28 the user event:
29
30 ```
31 ...
32 2013/10/22 07:06:32 [INFO] agent: Received event: user-event: hello-there
33 ```
34
35 If the cluster were made up of multiple members, all of the members
36 would have received this event, eventually.
37
38 Just like normal Serf events, event handlers can respond to user events.
39 For example, if we had a "restart" custom event, we might create an
40 event handler that restarts some server when it receives that event.
41
42 ## Event Payloads
43
44 Events are not limited to just an event name. The event can also contain
45 a payload: arbitrary data associated with the event. With our same agent
46 running, let's deliver an event with a payload: `serf event my-name-is Mitchell`
47
48 In practice, event payloads can contain information such as the git commit
49 to deploy if you're using Serf as a deployment tool. Or perhaps it contains
50 some updated configuration to modify on the nodes. It can contain anything
51 you'd like; it is up to the event handler to use it in some meaningful way.
52
53 ## Custom Event Limitations
54
55 Custom events are delivered using the Serf gossip layer. The benefits of
56 this approach is that you get completely decentralized messaging across
57 your entire cluster that is fault tolerant. Even if a node is down, it will
58 eventually receive that event message.
59
60 Due to the mechanics of gossip, custom
61 events are highly scalable: Serf doesn't need to connect to each and every
62 node to send the message, it only needs to connect to a handful, regardless
63 of cluster size.
64
65 Custom events come with some trade-offs, however:
66
67 * Events are eventually consistent: Because events are delivered over
68 gossip, the messages _eventually_ arrive at every node. In theory
69 (and anecdotally in practice), the state of the cluster
70 [converges rapidly](/docs/internals/simulator.html).
71
72 * Payload size is limited: Serf gossips via UDP, so the payload must fit
73 within a single UDP packet (alongside any other data Serf sends). This
74 limits the potential size of a payload to less than 1 KB. In practice,
75 Serf limits the payload to a much smaller size.
76
0 ---
1 layout: "intro"
2 page_title: "Introduction"
3 sidebar_current: "what"
4 description: |-
5 Welcome to the intro guide to Serf! This guide will show you what Serf is, explain the problems Serf solves, compare Serf versus other similar software, and show how easy it is to actually use Serf.
6 ---
7
8 # Introduction to Serf
9
10 Welcome to the intro guide to Serf! This guide will show you what Serf is,
11 explain the problems Serf solves, compare Serf versus other similar
12 software, and show how easy it is to actually use Serf. If you're already familiar
13 with the basics of Serf, the [documentation](/docs/index.html) provides more
14 of a reference for all available features.
15
16 ## What is Serf?
17
18 Serf is a tool for cluster membership, failure detection,
19 and orchestration that is decentralized, fault-tolerant and
20 highly available. Serf runs on every major platform: Linux, Mac OS X, and Windows. It is
21 extremely lightweight: it uses 5 to 10 MB of resident memory and primarily
22 communicates using infrequent UDP messages.
23
24 Serf uses an efficient [gossip protocol](/docs/internals/gossip.html)
25 to solve three major problems:
26
27 * **Membership**: Serf maintains cluster membership lists and is able to
28 execute custom handler scripts when that membership changes. For example,
29 Serf can maintain the list of web servers for a load balancer and notify
30 that load balancer whenever a node comes online or goes offline.
31
32 * **Failure detection and recovery**: Serf automatically detects failed nodes within
33 seconds, notifies the rest of the cluster,
34 and executes handler scripts allowing you to handle these events.
35 Serf will attempt to recover failed nodes by reconnecting to them
36 periodically.
37
38 * **Custom event propagation**: Serf can broadcast custom events and queries
39 to the cluster. These can be used to trigger deploys, propagate configuration, etc.
40 Events are simply fire-and-forget broadcast, and Serf makes a best effort to
41 deliver messages in the face of offline nodes or network partitions. Queries
42 provide a simple realtime request/response mechanism.
43
44 See the [use cases page](/intro/use-cases.html) for a list of concrete use
45 cases built on top of the features Serf provides. See the page on
46 [how Serf compares to other software](/intro/vs-other-sw.html) to see just
47 how it fits into your existing infrastructure. Or continue onwards with
48 the [getting started guide](/intro/getting-started/install.html) to get
49 Serf up and running and see how it works.
0 ---
1 layout: "intro"
2 page_title: "Use Cases"
3 sidebar_current: "use-cases"
4 description: |-
5 At this point you should know what Serf is and the high-level features Serf provides. This page lists a handful of concrete use cases of Serf. Note that this list is not exhaustive by any means. Serf is a general purpose tool and has infinitely many more use cases. But this list should give you a good idea of how Serf might be useful to you.
6 ---
7
8 # Use Cases
9
10 At this point you should know [what Serf is](/intro/index.html) and
11 the high-level features Serf provides. This page lists a handful of
12 concrete use cases of Serf. Note that this list is not exhaustive by
13 any means. Serf is a general purpose tool and has infinitely many more
14 use cases. But this list should give you a good idea of how Serf
15 might be useful to you.
16
17 It is important to remember that all the use cases available below
18 require _no centralized state_, are masterless, and are completely
19 fault tolerant.
20
21 #### Web Servers and Load Balancers
22
23 Using Serf, it is trivial to create a Serf cluster
24 consisting of web servers and load balancers. The load balancers can
25 listen for membership changes and when a web server comes online or goes
26 offline, they can update their node list.
27
28 #### Clustering Memcached or Redis
29
30 Servers such as Memcached or Redis can be easily clustered by creating
31 a Serf cluster for these nodes. When membership changes, you can update
32 something like [twemproxy](https://github.com/twitter/twemproxy) or your
33 own application's list of available servers.
34
35 #### Triggering Deploys
36
37 Serf can send custom events to a Serf cluster. If you cluster your web
38 applications into a single cluster, you can use Serf's event system to
39 trigger things such as deploys. Just call `serf event deploy` and have
40 event handlers installed on all the nodes and the entire cluster will
41 receive this message within seconds and begin deploying.
42
43 #### Updating DNS Records
44
45 Keeping your internal DNS servers updated can be a finicky process.
46 By using Serf, the DNS server can know within seconds when nodes join,
47 leave, or fail, and can update records appropriately. No more stale DNS
48 records, or waiting for a Chef or Puppet run to clear out the records
49 within X minutes. With Serf, the records can be updated nearly instantly.
50
51 #### Simple Observability
52
53 Serf provider queries which can be used as a simple request/response
54 mechanism. It can be used very simply to provide cluster and application
55 observability. Calling `serf query load` can trigger all the nodes to
56 call `uptime` and send their load averages to the query initiator, making
57 it easy to check on the cluster health.
58
59 #### A Building Block for Service Discovery
60
61 One of the most difficult parts of service discovery is simply knowing
62 what nodes are online, at what addresses, and for what purpose. An effective
63 service discovery layer can easily be built on top of Serf's seamless
64 membership system. Serf handles all the problems of keeping an up-to-date
65 node list along with some information about those nodes. The service
66 discovery layer then only needs to answer basic questions above that.
0 ---
1 layout: "intro"
2 page_title: "Serf vs. Chef, Puppet, etc."
3 sidebar_current: "vs-other-chef"
4 description: |-
5 It may seem strange to compare Serf to configuration management tools, but most of them provide mechanisms to incorporate global state into the configuration of a node. For example, Puppet provides exported resources and Chef has node searching. As an example, if you generate a config file for a load balancer to include the web servers, the config management tool is being used to manage membership.
6 ---
7
8 # Serf vs. Chef, Puppet, etc.
9
10 It may seem strange to compare Serf to configuration management tools,
11 but most of them provide mechanisms to incorporate global state into the
12 configuration of a node. For example, Puppet provides exported resources
13 and Chef has node searching. As an example, if you generate a config file
14 for a load balancer to include the web servers, the config management
15 tool is being used to manage membership.
16
17 However, none of these config management tools are designed to perform
18 this task. They are not designed to propagate information quickly,
19 handle failure detection, or tolerate network partitions. Generally,
20 they rely on very infrequent convergence runs to bring things up to date.
21 Lastly, these tools are not friendly for immutable infrastructure as they
22 require constant operation to keep nodes up to date.
23
24 That said, Serf is designed to be used alongside config management tools.
25 Once configured, Serf can be used to handle changes to the cluster and
26 update configuration files nearly instantly instead of relying on convergence
27 runs. This way, a web server can join a cluster in seconds instead of hours.
28 The separation of configuration management and cluster management also has
29 a number of advantageous side effects: Chef recipes and Puppet manifests become
30 simpler without global state, periodic runs are no longer required for
31 membership updates, and the infrastructure can become immutable since
32 config management runs require no global state.
0 ---
1 layout: "intro"
2 page_title: "Serf vs. Consul"
3 sidebar_current: "vs-other-consul"
4 description: |-
5 Consul is a tool for service discovery and configuration. It provides high level features such as service discovery, health checking and key/value storage. It makes use of a group of strongly consistent servers to manage the datacenter.
6 ---
7
8 # Serf vs. Consul
9
10 [Consul](http://www.consul.io) is a tool for service discovery and configuration.
11 It provides high level features such as service discovery, health checking
12 and key/value storage. It makes use of a group of strongly consistent servers
13 to manage the datacenter.
14
15 Serf can also be used for service discovery and orchestration, but it is built on
16 an eventually consistent gossip model, with no centralized servers. It provides a
17 number of features, including group membership, failure detection, event broadcasts
18 and a query mechanism. However, Serf does not provide any of the high-level features
19 of Consul.
20
21 In fact, the internal gossip protocol used within Consul, is powered by the Serf library.
22 Consul leverages the membership and failure detection features, and builds upon them.
23
24 The health checking provided by Serf is very low level, and only indicates if the
25 agent is alive. Consul extends this to provide a rich health checking system,
26 that handles liveness, in addition to arbitrary host and service-level checks.
27 Health checks are integrated with a central catalog that operators can easily
28 query to gain insight into the cluster.
29
30 The membership provided by Serf is at a node level, while Consul focuses
31 on the service level abstraction, with a single node to multiple service model.
32 This can be simulated in Serf using tags, but it is much more limited, and does
33 not provide rich query interfaces. Consul also makes use of a strongly consistent
34 Catalog, while Serf is only eventually consistent.
35
36 In addition to the service level abstraction and improved health checking,
37 Consul provides a key/value store and support for multiple datacenters.
38 Serf can run across the WAN but with degraded performance. Consul makes use
39 of [multiple gossip pools](http://www.consul.io/docs/internals/architecture.html),
40 so that the performance of Serf over a LAN can be retained while still using it over
41 a WAN for linking together multiple datacenters.
42
43 Consul is opinionated in its usage, while Serf is a more flexible and
44 general purpose tool. Consul uses a CP architecture, favoring consistency over
45 availability. Serf is a AP system, and sacrifices consistency for availability.
46 This means Consul cannot operate if the central servers cannot form a quorum,
47 while Serf will continue to function under almost all circumstances.
48
0 ---
1 layout: "intro"
2 page_title: "Serf vs. Custom Solutions"
3 sidebar_current: "vs-other-custom"
4 description: |-
5 Many organizations find themselves building home grown solutions for service discovery and administration. It is an undisputed fact that distributed systems are hard; building one is error prone and time consuming. Most systems cut corners by introducing single points of failure such as a single Redis or RDBMS to maintain cluster state. These solutions may work in the short term, but they are rarely fault tolerant or scalable. Besides these limitations, they require time and resources to build and maintain.
6 ---
7
8 # Serf vs. Custom Solutions
9
10 Many organizations find themselves building home grown solutions
11 for service discovery and administration. It is an undisputed fact that
12 distributed systems are hard; building one is error prone and time consuming.
13 Most systems cut corners by introducing single points of failure such
14 as a single Redis or RDBMS to maintain cluster state. These solutions may work in the short term,
15 but they are rarely fault tolerant or scalable. Besides these limitations,
16 they require time and resources to build and maintain.
17
18 Serf provides many features that are effortless to use out of the box.
19 However, Serf still may not provide the exact feature set needed by an organization.
20 Instead it can be used as building block, composed with other systems it provides generally
21 useful features for building distributed systems.
22
23 Serf is built on top of well-cited academic research where the pros, cons,
24 failure scenarios, scalability, etc. are all well defined enabling you to
25 build on the shoulder of giants.
26
0 ---
1 layout: "intro"
2 page_title: "Serf vs. Fabric"
3 sidebar_current: "vs-other-fabric"
4 description: |-
5 Fabric is a widely used tool for system administration over SSH. Broadly, it is used to SSH into a group of nodes and execute commands. Both Fabric and Serf can be used for service management in different ways.
6 ---
7
8 # Serf vs. Fabric
9
10 Fabric is a widely used tool for system administration over SSH. Broadly,
11 it is used to SSH into a group of nodes and execute commands. Both Fabric
12 and Serf can be used for service management in different ways.
13
14 While Fabric sends commands from a single box, Serf instead rapidly broadcasts a message
15 to the entire cluster in a distributed fashion. Fabric has a number of advantages
16 in that it can collect unlimited output of commands and stop execution if an
17 error is encountered. Serf queries can collect a limited amount of output, but lacks
18 the more complex control flow of Fabric. However, Fabric must be provided with a list
19 of nodes to contact, whereas membership is built directly into Serf. Additionally, Serf
20 is able to propagate a message within seconds to an entire cluster, allowing for much higher
21 parallelism and scalability.
22
23 Fabric is much more capable than Serf at system administration, but it is
24 limited by its execution speed and lack of node discovery. Combined together,
25 Fabric can query Serf for nodes and make use of message broadcasts where
26 appropriate, using direct SSH execution when and where output is needed.
0 ---
1 layout: "intro"
2 page_title: "Serf vs. Other Software"
3 sidebar_current: "vs-other"
4 description: |-
5 The problems Serf solves are not new; they've existed for a long time. It should come as no surprise then that there are other options available to solve some of these problems. In this section, we compare Serf to some other options. In most cases, Serf can be used alongside these other systems, strengthening them in areas they are weak.
6 ---
7
8 # Serf vs. Other Software
9
10 The problems Serf solves are not new; they've existed for a long time.
11 It should come as no surprise then that there are other options available
12 to solve some of these problems. In this section, we compare Serf to some
13 other options. In most cases, Serf can be used alongside these other systems, strengthening
14 them in areas they are weak.
15
16 Use the navigation to the left to read the comparison of Serf to specific
17 systems.
0 ---
1 layout: "intro"
2 page_title: "Serf vs. ZooKeeper, doozerd, etcd"
3 sidebar_current: "vs-other-zk"
4 description: |-
5 ZooKeeper, doozerd and etcd are all similar in their client/server architecture. All three have server nodes that require a quorum of nodes to operate (usually a simple majority). They are strongly consistent, and expose various primitives that can be used through client libraries within applications to build complex distributed systems.
6 ---
7
8 # Serf vs. ZooKeeper, doozerd, etcd
9
10 ZooKeeper, doozerd and etcd are all similar in their client/server
11 architecture. All three have server nodes that require a quorum of
12 nodes to operate (usually a simple majority). They are strongly consistent,
13 and expose various primitives that can be used through client libraries within
14 applications to build complex distributed systems.
15
16 Serf has a radically different architecture based on gossip and provides a
17 smaller feature set. Serf only provides membership, failure detection,
18 and user events. Serf is designed to operate under network partitions
19 and embraces eventual consistency. Designed as a tool, it is friendly
20 for both system administrators and application developers.
21
22 ZooKeeper et al. by contrast are much more complex, and cannot be used directly
23 as a tool. Application developers must use libraries to build the features
24 they need, although some libraries exist for common patterns. Most failure
25 detection schemes built on these systems also have intrinsic scalability issues.
26 Most naive failure detection schemes depend on heartbeating, which use
27 periodic updates and timeouts. These schemes require work linear to
28 the number of nodes and place the demand on a fixed number of servers.
29 Additionally, the failure detection window is at least as long as the timeout,
30 meaning that in many cases failures may not be detected for a long time.
31 Additionally, ZooKeeper ephemeral nodes require that many active connections
32 be maintained to a few nodes.
33
34 The strong consistency provided by these systems is essential for building leader
35 election and other types of coordination for distributed systems, but it limits
36 their ability to operate under network partitions. At a minimum, if a majority of
37 nodes are not available, writes are disallowed. Since a failure is indistinguishable
38 from a slow response, the performance of these systems may rapidly degrade
39 under certain network conditions. All of these issues can be highly
40 problematic when partition tolerance is needed, for example in a service
41 discovery layer.
42
43 Additionally, Serf is not mutually exclusive with any of these strongly
44 consistent systems. Instead, they can be used in combination to create systems
45 that are more scalable and fault tolerant, without sacrificing features.
0 <% wrap_layout :inner do %>
1 <% content_for :sidebar do %>
2 <div class="docs-sidebar hidden-print affix-top" role="complementary">
3 <ul class="nav docs-sidenav">
4 <li<%= sidebar_current("docs-home") %>>
5 <a href="/docs/index.html">Documentation Home</a>
6 </li>
7
8 <li<%= sidebar_current("docs-upgrading") %>>
9 <a href="/docs/upgrading.html">Upgrading and Compatibility</a>
10 <ul class="nav">
11 <li<%= sidebar_current("docs-upgrading-upgrading") %>>
12 <a href="/docs/upgrading.html">Upgrading Serf</a>
13 </li>
14
15 <li<%= sidebar_current("docs-upgrading-compat") %>>
16 <a href="/docs/compatibility.html">Compatibility Promise</a>
17 </li>
18 </ul>
19 </li>
20
21 <li<%= sidebar_current("docs-internals") %>>
22 <a href="/docs/internals/index.html">Serf Internals</a>
23 <ul class="nav">
24 <li<%= sidebar_current("docs-internals-gossip") %>>
25 <a href="/docs/internals/gossip.html">Gossip Protocol</a>
26 </li>
27
28 <li<%= sidebar_current("docs-internals-security") %>>
29 <a href="/docs/internals/security.html">Security Model</a>
30 </li>
31
32 <li<%= sidebar_current("docs-internals-simulator") %>>
33 <a href="/docs/internals/simulator.html">Convergence Simulator</a>
34 </li>
35 </ul>
36 </li>
37
38 <li<%= sidebar_current("docs-commands") %>>
39 <a href="/docs/commands/index.html">Serf Commands (CLI)</a>
40 <ul class="nav">
41 <li<%= sidebar_current("docs-commands-agent") %>>
42 <a href="/docs/commands/agent.html">agent</a>
43 </li>
44
45 <li<%= sidebar_current("docs-commands-event") %>>
46 <a href="/docs/commands/event.html">event</a>
47 </li>
48
49 <li<%= sidebar_current("docs-commands-forceleave") %>>
50 <a href="/docs/commands/force-leave.html">force-leave</a>
51 </li>
52
53 <li<%= sidebar_current("docs-commands-info") %>>
54 <a href="/docs/commands/info.html">info</a>
55 </li>
56
57 <li<%= sidebar_current("docs-commands-join") %>>
58 <a href="/docs/commands/join.html">join</a>
59 </li>
60
61 <li<%= sidebar_current("docs-commands-keys") %>>
62 <a href="/docs/commands/keys.html">keys</a>
63 </li>
64
65 <li<%= sidebar_current("docs-commands-keygen") %>>
66 <a href="/docs/commands/keygen.html">keygen</a>
67 </li>
68
69 <li<%= sidebar_current("docs-commands-leave") %>>
70 <a href="/docs/commands/leave.html">leave</a>
71 </li>
72
73 <li<%= sidebar_current("docs-commands-members") %>>
74 <a href="/docs/commands/members.html">members</a>
75 </li>
76
77 <li<%= sidebar_current("docs-commands-monitor") %>>
78 <a href="/docs/commands/monitor.html">monitor</a>
79 </li>
80
81 <li<%= sidebar_current("docs-commands-query") %>>
82 <a href="/docs/commands/query.html">query</a>
83 </li>
84
85 <li<%= sidebar_current("docs-commands-reachability") %>>
86 <a href="/docs/commands/reachability.html">reachability</a>
87 </li>
88
89 <li<%= sidebar_current("docs-commands-tags") %>>
90 <a href="/docs/commands/tags.html">tags</a>
91 </li>
92
93 </ul>
94 </li>
95
96 <li<%= sidebar_current("docs-agent") %>>
97 <a href="/docs/agent/basics.html">Serf Agent</a>
98 <ul class="nav">
99 <li<%= sidebar_current("docs-agent-running") %>>
100 <a href="/docs/agent/basics.html">Running and Stopping</a>
101 </li>
102
103 <li<%= sidebar_current("docs-agent-config") %>>
104 <a href="/docs/agent/options.html">Configuration</a>
105 </li>
106
107 <li<%= sidebar_current("docs-agent-events") %>>
108 <a href="/docs/agent/event-handlers.html">Event Handlers</a>
109 </li>
110
111 <li<%= sidebar_current("docs-agent-encryption") %>>
112 <a href="/docs/agent/encryption.html">Encryption</a>
113 </li>
114
115 <li<%= sidebar_current("docs-agent-rpc") %>>
116 <a href="/docs/agent/rpc.html">RPC Protocol</a>
117 </li>
118
119 <li<%= sidebar_current("docs-agent-telemetry") %>>
120 <a href="/docs/agent/telemetry.html">Telemetry</a>
121 </li>
122 </ul>
123
124 <li<%= sidebar_current("docs-recipes") %>>
125 <a href="/docs/recipes.html">Recipes</a>
126 </li>
127
128 <li<%= sidebar_current("docs-roadmap") %>>
129 <a href="/docs/roadmap.html">Roadmap</a>
130 </li>
131
132 </ul>
133 </div>
134 <% end %>
135
136 <%= yield %>
137 <% end %>
0 <% wrap_layout :layout do %>
1 <div class="container">
2 <div class="col-md-4">
3 <%= yield_content :sidebar %>
4 </div> <!-- /col-md-4 -->
5 <div class="col-md-8" role="main">
6 <div class="bs-docs-section">
7 <%= yield %>
8 </div>
9 </div>
10 </div>
11 <% end %>
0 <% wrap_layout :inner do %>
1 <% content_for :sidebar do %>
2 <div class="docs-sidebar hidden-print affix-top" role="complementary">
3 <ul class="nav docs-sidenav">
4 <li<%= sidebar_current("what") %>>
5 <a href="/intro/index.html">What is Serf?</a>
6 </li>
7
8 <li<%= sidebar_current("use-cases") %>>
9 <a href="/intro/use-cases.html">Use Cases</a>
10 </li>
11
12 <li<%= sidebar_current("vs-other") %>>
13 <a href="/intro/vs-other-sw.html">Serf vs. Other Software</a>
14 <ul class="nav">
15 <li<%= sidebar_current("vs-other-zk") %>>
16 <a href="/intro/vs-zookeeper.html">ZooKeeper, doozerd, etcd</a>
17 </li>
18
19 <li<%= sidebar_current("vs-other-chef") %>>
20 <a href="/intro/vs-chef-puppet.html">Chef, Puppet, etc.</a>
21 </li>
22
23 <li<%= sidebar_current("vs-other-fabric") %>>
24 <a href="/intro/vs-fabric.html">Fabric</a>
25 </li>
26
27 <li<%= sidebar_current("vs-other-consul") %>>
28 <a href="/intro/vs-consul.html">Consul</a>
29 </li>
30
31 <li<%= sidebar_current("vs-other-custom") %>>
32 <a href="/intro/vs-custom.html">Custom Solutions</a>
33 </li>
34 </ul>
35 </li>
36
37 <li<%= sidebar_current("gettingstarted") %>>
38 <a href="/intro/getting-started/install.html">Getting Started</a>
39 <ul class="nav">
40 <li<%= sidebar_current("gettingstarted-install") %>>
41 <a href="/intro/getting-started/install.html">Install Serf</a>
42 </li>
43
44 <li<%= sidebar_current("gettingstarted-agent") %>>
45 <a href="/intro/getting-started/agent.html">Run the Agent</a>
46 </li>
47
48 <li<%= sidebar_current("gettingstarted-join") %>>
49 <a href="/intro/getting-started/join.html">Join a Cluster</a>
50 </li>
51
52 <li<%= sidebar_current("gettingstarted-eventhandlers") %>>
53 <a href="/intro/getting-started/event-handlers.html">Event Handlers</a>
54 </li>
55
56 <li<%= sidebar_current("gettingstarted-userevents") %>>
57 <a href="/intro/getting-started/user-events.html">Custom User Events</a>
58 </li>
59
60 <li<%= sidebar_current("gettingstarted-queries") %>>
61 <a href="/intro/getting-started/queries.html">Custom Queries</a>
62 </li>
63
64 <li<%= sidebar_current("gettingstarted-nextsteps") %>>
65 <a href="/intro/getting-started/next-steps.html">Next Steps</a>
66 </li>
67
68 </ul>
69 </li>
70 </ul>
71 </div>
72 <% end %>
73
74 <%= yield %>
75 <% end %>
0 <!DOCTYPE html>
1 <html lang="en">
2 <head>
3 <meta charset="utf-8">
4
5 <title><%= [current_page.data.page_title, "Serf by HashiCorp"].compact.join(" - ") %></title>
6 <meta name="description" content="<%= current_page.data.description %>">
7
8 <meta name="HandheldFriendly" content="True" />
9 <meta name="MobileOptimized" content="320" />
10 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
11
12 <%= stylesheet_link_tag "application" %>
13 </head>
14
15 <body class="page-<%= current_page.data.page_title ? "#{current_page.data.page_title}" : "home" %>">
16 <div id="header">
17 <div class="container">
18 <a class="navbar-brand logo" href="/">
19 <span></span>
20 </a>
21 <a class="navbar-brand text rls-l" href="/">SERF</a>
22
23 <ul class="buttons nav navbar-nav navbar-right rls-sb">
24 <li class="first"><a href="/downloads.html">Download</a></li>
25 <li><a href="https://github.com/hashicorp/serf">Github</a></li>
26 </ul>
27
28 <ul class="main-links nav navbar-nav navbar-right rls-sb">
29 <li><a href="/intro/index.html">Intro</a></li>
30 <li><a href="/docs/index.html">Docs</a></li>
31 <li><a href="/community.html">Community</a></li>
32 </ul>
33
34 </div>
35 </div>
36
37 <%= yield %>
38
39 <div id="footer">
40 <div class="container">
41 <div class="footer-links">
42 <ul class="main-links nav navbar-nav rls-sb">
43 <li><a href="/intro/index.html">Intro</a></li>
44 <li class="active"><a href="/docs/index.html">Docs</a></li>
45 <li><a href="/community.html">Community</a></li>
46 </ul>
47
48 <ul class="buttons nav navbar-nav rls-sb">
49 <li class="first"><a href="/downloads.html">Download</a></li>
50 <li><a href="https://github.com/hashicorp/serf">Github</a></li>
51 </ul>
52 </div>
53 <div class="footer-logo">
54 <span></span>
55 </div>
56 <div class="footer-hashi os">
57 <span>Copyright &copy; <%= Time.now.year %>. A <a href="http://www.hashicorp.com">HashiCorp</a> Project.</span>
58 <a href="http://www.hashicorp.com"><%= image_tag("hashi-logo-s.png") %></a>
59 </div>
60 </div>
61 </div>
62
63 <!--[if lt IE 9]>
64 <%= javascript_include_tag "html5shiv", "respond.min" %>
65 <![endif]-->
66
67 <script>
68 (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
69 (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
70 m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
71 })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
72
73 ga('create', 'UA-45101516-1', 'serfdom.io');
74 ga('send', 'pageview');
75 </script>
76
77 <%= javascript_include_tag "application" %>
78
79 <script>
80 Serf.initialize();
81 </script>
82 </body>
83 </html>
0 ---
1 layout: false
2 noindex: true
3 ---
4
5 User-agent: *
6 Disallow: /404
7 Disallow: /500
0 ---
1 layout: false
2 ---
3
4 xml.instruct!
5 xml.urlset 'xmlns' => "http://www.sitemaps.org/schemas/sitemap/0.9" do
6 sitemap
7 .resources
8 .select { |page| page.path =~ /\.html/ }
9 .select { |page| !page.data.noindex }
10 .each do |page|
11 xml.url do
12 xml.loc File.join(base_url, page.url)
13 xml.lastmod Date.today.to_time.iso8601
14 xml.changefreq page.data.changefreq || "monthly"
15 xml.priority page.data.priority || "0.5"
16 end
17 end
18 end