Vendor main dependencies.
This commit is contained in:
parent
49a09ab7dd
commit
dd5e3fba01
2738 changed files with 1045689 additions and 0 deletions
354
vendor/github.com/hashicorp/consul/LICENSE
generated
vendored
Normal file
354
vendor/github.com/hashicorp/consul/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,354 @@
|
|||
Mozilla Public License, version 2.0
|
||||
|
||||
1. Definitions
|
||||
|
||||
1.1. “Contributor”
|
||||
|
||||
means each individual or legal entity that creates, contributes to the
|
||||
creation of, or owns Covered Software.
|
||||
|
||||
1.2. “Contributor Version”
|
||||
|
||||
means the combination of the Contributions of others (if any) used by a
|
||||
Contributor and that particular Contributor’s Contribution.
|
||||
|
||||
1.3. “Contribution”
|
||||
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. “Covered Software”
|
||||
|
||||
means Source Code Form to which the initial Contributor has attached the
|
||||
notice in Exhibit A, the Executable Form of such Source Code Form, and
|
||||
Modifications of such Source Code Form, in each case including portions
|
||||
thereof.
|
||||
|
||||
1.5. “Incompatible With Secondary Licenses”
|
||||
means
|
||||
|
||||
a. that the initial Contributor has attached the notice described in
|
||||
Exhibit B to the Covered Software; or
|
||||
|
||||
b. that the Covered Software was made available under the terms of version
|
||||
1.1 or earlier of the License, but not also under the terms of a
|
||||
Secondary License.
|
||||
|
||||
1.6. “Executable Form”
|
||||
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. “Larger Work”
|
||||
|
||||
means a work that combines Covered Software with other material, in a separate
|
||||
file or files, that is not Covered Software.
|
||||
|
||||
1.8. “License”
|
||||
|
||||
means this document.
|
||||
|
||||
1.9. “Licensable”
|
||||
|
||||
means having the right to grant, to the maximum extent possible, whether at the
|
||||
time of the initial grant or subsequently, any and all of the rights conveyed by
|
||||
this License.
|
||||
|
||||
1.10. “Modifications”
|
||||
|
||||
means any of the following:
|
||||
|
||||
a. any file in Source Code Form that results from an addition to, deletion
|
||||
from, or modification of the contents of Covered Software; or
|
||||
|
||||
b. any new file in Source Code Form that contains any Covered Software.
|
||||
|
||||
1.11. “Patent Claims” of a Contributor
|
||||
|
||||
means any patent claim(s), including without limitation, method, process,
|
||||
and apparatus claims, in any patent Licensable by such Contributor that
|
||||
would be infringed, but for the grant of the License, by the making,
|
||||
using, selling, offering for sale, having made, import, or transfer of
|
||||
either its Contributions or its Contributor Version.
|
||||
|
||||
1.12. “Secondary License”
|
||||
|
||||
means either the GNU General Public License, Version 2.0, the GNU Lesser
|
||||
General Public License, Version 2.1, the GNU Affero General Public
|
||||
License, Version 3.0, or any later versions of those licenses.
|
||||
|
||||
1.13. “Source Code Form”
|
||||
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. “You” (or “Your”)
|
||||
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, “You” includes any entity that controls, is
|
||||
controlled by, or is under common control with You. For purposes of this
|
||||
definition, “control” means (a) the power, direct or indirect, to cause
|
||||
the direction or management of such entity, whether by contract or
|
||||
otherwise, or (b) ownership of more than fifty percent (50%) of the
|
||||
outstanding shares or beneficial ownership of such entity.
|
||||
|
||||
|
||||
2. License Grants and Conditions
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
a. under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or as
|
||||
part of a Larger Work; and
|
||||
|
||||
b. under Patent Claims of such Contributor to make, use, sell, offer for
|
||||
sale, have made, import, and otherwise transfer either its Contributions
|
||||
or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution become
|
||||
effective for each Contribution on the date the Contributor first distributes
|
||||
such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under this
|
||||
License. No additional rights or licenses will be implied from the distribution
|
||||
or licensing of Covered Software under this License. Notwithstanding Section
|
||||
2.1(b) above, no patent license is granted by a Contributor:
|
||||
|
||||
a. for any code that a Contributor has removed from Covered Software; or
|
||||
|
||||
b. for infringements caused by: (i) Your and any other third party’s
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
c. under Patent Claims infringed by Covered Software in the absence of its
|
||||
Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks, or
|
||||
logos of any Contributor (except as may be necessary to comply with the
|
||||
notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this License
|
||||
(see Section 10.2) or under the terms of a Secondary License (if permitted
|
||||
under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its Contributions
|
||||
are its original creation(s) or it has sufficient rights to grant the
|
||||
rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under applicable
|
||||
copyright doctrines of fair use, fair dealing, or other equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
|
||||
Section 2.1.
|
||||
|
||||
|
||||
3. Responsibilities
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under the
|
||||
terms of this License. You must inform recipients that the Source Code Form
|
||||
of the Covered Software is governed by the terms of this License, and how
|
||||
they can obtain a copy of this License. You may not attempt to alter or
|
||||
restrict the recipients’ rights in the Source Code Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
a. such Covered Software must also be made available in Source Code Form,
|
||||
as described in Section 3.1, and You must inform recipients of the
|
||||
Executable Form how they can obtain a copy of such Source Code Form by
|
||||
reasonable means in a timely manner, at a charge no more than the cost
|
||||
of distribution to the recipient; and
|
||||
|
||||
b. You may distribute such Executable Form under the terms of this License,
|
||||
or sublicense it under different terms, provided that the license for
|
||||
the Executable Form does not attempt to limit or alter the recipients’
|
||||
rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for the
|
||||
Covered Software. If the Larger Work is a combination of Covered Software
|
||||
with a work governed by one or more Secondary Licenses, and the Covered
|
||||
Software is not Incompatible With Secondary Licenses, this License permits
|
||||
You to additionally distribute such Covered Software under the terms of
|
||||
such Secondary License(s), so that the recipient of the Larger Work may, at
|
||||
their option, further distribute the Covered Software under the terms of
|
||||
either this License or such Secondary License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices (including
|
||||
copyright notices, patent notices, disclaimers of warranty, or limitations
|
||||
of liability) contained within the Source Code Form of the Covered
|
||||
Software, except that You may alter any license notices to the extent
|
||||
required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on behalf
|
||||
of any Contributor. You must make it absolutely clear that any such
|
||||
warranty, support, indemnity, or liability obligation is offered by You
|
||||
alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this License
|
||||
with respect to some or all of the Covered Software due to statute, judicial
|
||||
order, or regulation then You must: (a) comply with the terms of this License
|
||||
to the maximum extent possible; and (b) describe the limitations and the code
|
||||
they affect. Such description must be placed in a text file included with all
|
||||
distributions of the Covered Software under this License. Except to the
|
||||
extent prohibited by statute or regulation, such description must be
|
||||
sufficiently detailed for a recipient of ordinary skill to be able to
|
||||
understand it.
|
||||
|
||||
5. Termination
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically if You
|
||||
fail to comply with any of its terms. However, if You become compliant,
|
||||
then the rights granted under this License from a particular Contributor
|
||||
are reinstated (a) provisionally, unless and until such Contributor
|
||||
explicitly and finally terminates Your grants, and (b) on an ongoing basis,
|
||||
if such Contributor fails to notify You of the non-compliance by some
|
||||
reasonable means prior to 60 days after You have come back into compliance.
|
||||
Moreover, Your grants from a particular Contributor are reinstated on an
|
||||
ongoing basis if such Contributor notifies You of the non-compliance by
|
||||
some reasonable means, this is the first time You have received notice of
|
||||
non-compliance with this License from such Contributor, and You become
|
||||
compliant prior to 30 days after Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions, counter-claims,
|
||||
and cross-claims) alleging that a Contributor Version directly or
|
||||
indirectly infringes any patent, then the rights granted to You by any and
|
||||
all Contributors for the Covered Software under Section 2.1 of this License
|
||||
shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
|
||||
license agreements (excluding distributors and resellers) which have been
|
||||
validly granted by You or Your distributors under this License prior to
|
||||
termination shall survive termination.
|
||||
|
||||
6. Disclaimer of Warranty
|
||||
|
||||
Covered Software is provided under this License on an “as is” basis, without
|
||||
warranty of any kind, either expressed, implied, or statutory, including,
|
||||
without limitation, warranties that the Covered Software is free of defects,
|
||||
merchantable, fit for a particular purpose or non-infringing. The entire
|
||||
risk as to the quality and performance of the Covered Software is with You.
|
||||
Should any Covered Software prove defective in any respect, You (not any
|
||||
Contributor) assume the cost of any necessary servicing, repair, or
|
||||
correction. This disclaimer of warranty constitutes an essential part of this
|
||||
License. No use of any Covered Software is authorized under this License
|
||||
except under this disclaimer.
|
||||
|
||||
7. Limitation of Liability
|
||||
|
||||
Under no circumstances and under no legal theory, whether tort (including
|
||||
negligence), contract, or otherwise, shall any Contributor, or anyone who
|
||||
distributes Covered Software as permitted above, be liable to You for any
|
||||
direct, indirect, special, incidental, or consequential damages of any
|
||||
character including, without limitation, damages for lost profits, loss of
|
||||
goodwill, work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses, even if such party shall have been
|
||||
informed of the possibility of such damages. This limitation of liability
|
||||
shall not apply to liability for death or personal injury resulting from such
|
||||
party’s negligence to the extent applicable law prohibits such limitation.
|
||||
Some jurisdictions do not allow the exclusion or limitation of incidental or
|
||||
consequential damages, so this exclusion and limitation may not apply to You.
|
||||
|
||||
8. Litigation
|
||||
|
||||
Any litigation relating to this License may be brought only in the courts of
|
||||
a jurisdiction where the defendant maintains its principal place of business
|
||||
and such litigation shall be governed by laws of that jurisdiction, without
|
||||
reference to its conflict-of-law provisions. Nothing in this Section shall
|
||||
prevent a party’s ability to bring cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
|
||||
This License represents the complete agreement concerning the subject matter
|
||||
hereof. If any provision of this License is held to be unenforceable, such
|
||||
provision shall be reformed only to the extent necessary to make it
|
||||
enforceable. Any law or regulation which provides that the language of a
|
||||
contract shall be construed against the drafter shall not be used to construe
|
||||
this License against a Contributor.
|
||||
|
||||
|
||||
10. Versions of the License
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version of
|
||||
the License under which You originally received the Covered Software, or
|
||||
under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a modified
|
||||
version of this License if you rename the license and remove any
|
||||
references to the name of the license steward (except to note that such
|
||||
modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular file, then
|
||||
You may include the notice in a location (such as a LICENSE file in a relevant
|
||||
directory) where a recipient would be likely to look for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - “Incompatible With Secondary Licenses” Notice
|
||||
|
||||
This Source Code Form is “Incompatible
|
||||
With Secondary Licenses”, as defined by
|
||||
the Mozilla Public License, v. 2.0.
|
||||
|
140
vendor/github.com/hashicorp/consul/api/acl.go
generated
vendored
Normal file
140
vendor/github.com/hashicorp/consul/api/acl.go
generated
vendored
Normal file
|
@ -0,0 +1,140 @@
|
|||
package api
|
||||
|
||||
const (
|
||||
// ACLCLientType is the client type token
|
||||
ACLClientType = "client"
|
||||
|
||||
// ACLManagementType is the management type token
|
||||
ACLManagementType = "management"
|
||||
)
|
||||
|
||||
// ACLEntry is used to represent an ACL entry
|
||||
type ACLEntry struct {
|
||||
CreateIndex uint64
|
||||
ModifyIndex uint64
|
||||
ID string
|
||||
Name string
|
||||
Type string
|
||||
Rules string
|
||||
}
|
||||
|
||||
// ACL can be used to query the ACL endpoints
|
||||
type ACL struct {
|
||||
c *Client
|
||||
}
|
||||
|
||||
// ACL returns a handle to the ACL endpoints
|
||||
func (c *Client) ACL() *ACL {
|
||||
return &ACL{c}
|
||||
}
|
||||
|
||||
// Create is used to generate a new token with the given parameters
|
||||
func (a *ACL) Create(acl *ACLEntry, q *WriteOptions) (string, *WriteMeta, error) {
|
||||
r := a.c.newRequest("PUT", "/v1/acl/create")
|
||||
r.setWriteOptions(q)
|
||||
r.obj = acl
|
||||
rtt, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
wm := &WriteMeta{RequestTime: rtt}
|
||||
var out struct{ ID string }
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return out.ID, wm, nil
|
||||
}
|
||||
|
||||
// Update is used to update the rules of an existing token
|
||||
func (a *ACL) Update(acl *ACLEntry, q *WriteOptions) (*WriteMeta, error) {
|
||||
r := a.c.newRequest("PUT", "/v1/acl/update")
|
||||
r.setWriteOptions(q)
|
||||
r.obj = acl
|
||||
rtt, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
wm := &WriteMeta{RequestTime: rtt}
|
||||
return wm, nil
|
||||
}
|
||||
|
||||
// Destroy is used to destroy a given ACL token ID
|
||||
func (a *ACL) Destroy(id string, q *WriteOptions) (*WriteMeta, error) {
|
||||
r := a.c.newRequest("PUT", "/v1/acl/destroy/"+id)
|
||||
r.setWriteOptions(q)
|
||||
rtt, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
wm := &WriteMeta{RequestTime: rtt}
|
||||
return wm, nil
|
||||
}
|
||||
|
||||
// Clone is used to return a new token cloned from an existing one
|
||||
func (a *ACL) Clone(id string, q *WriteOptions) (string, *WriteMeta, error) {
|
||||
r := a.c.newRequest("PUT", "/v1/acl/clone/"+id)
|
||||
r.setWriteOptions(q)
|
||||
rtt, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
wm := &WriteMeta{RequestTime: rtt}
|
||||
var out struct{ ID string }
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return out.ID, wm, nil
|
||||
}
|
||||
|
||||
// Info is used to query for information about an ACL token
|
||||
func (a *ACL) Info(id string, q *QueryOptions) (*ACLEntry, *QueryMeta, error) {
|
||||
r := a.c.newRequest("GET", "/v1/acl/info/"+id)
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var entries []*ACLEntry
|
||||
if err := decodeBody(resp, &entries); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(entries) > 0 {
|
||||
return entries[0], qm, nil
|
||||
}
|
||||
return nil, qm, nil
|
||||
}
|
||||
|
||||
// List is used to get all the ACL tokens
|
||||
func (a *ACL) List(q *QueryOptions) ([]*ACLEntry, *QueryMeta, error) {
|
||||
r := a.c.newRequest("GET", "/v1/acl/list")
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var entries []*ACLEntry
|
||||
if err := decodeBody(resp, &entries); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return entries, qm, nil
|
||||
}
|
404
vendor/github.com/hashicorp/consul/api/agent.go
generated
vendored
Normal file
404
vendor/github.com/hashicorp/consul/api/agent.go
generated
vendored
Normal file
|
@ -0,0 +1,404 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// AgentCheck represents a check known to the agent
|
||||
type AgentCheck struct {
|
||||
Node string
|
||||
CheckID string
|
||||
Name string
|
||||
Status string
|
||||
Notes string
|
||||
Output string
|
||||
ServiceID string
|
||||
ServiceName string
|
||||
}
|
||||
|
||||
// AgentService represents a service known to the agent
|
||||
type AgentService struct {
|
||||
ID string
|
||||
Service string
|
||||
Tags []string
|
||||
Port int
|
||||
Address string
|
||||
EnableTagOverride bool
|
||||
}
|
||||
|
||||
// AgentMember represents a cluster member known to the agent
|
||||
type AgentMember struct {
|
||||
Name string
|
||||
Addr string
|
||||
Port uint16
|
||||
Tags map[string]string
|
||||
Status int
|
||||
ProtocolMin uint8
|
||||
ProtocolMax uint8
|
||||
ProtocolCur uint8
|
||||
DelegateMin uint8
|
||||
DelegateMax uint8
|
||||
DelegateCur uint8
|
||||
}
|
||||
|
||||
// AgentServiceRegistration is used to register a new service
|
||||
type AgentServiceRegistration struct {
|
||||
ID string `json:",omitempty"`
|
||||
Name string `json:",omitempty"`
|
||||
Tags []string `json:",omitempty"`
|
||||
Port int `json:",omitempty"`
|
||||
Address string `json:",omitempty"`
|
||||
EnableTagOverride bool `json:",omitempty"`
|
||||
Check *AgentServiceCheck
|
||||
Checks AgentServiceChecks
|
||||
}
|
||||
|
||||
// AgentCheckRegistration is used to register a new check
|
||||
type AgentCheckRegistration struct {
|
||||
ID string `json:",omitempty"`
|
||||
Name string `json:",omitempty"`
|
||||
Notes string `json:",omitempty"`
|
||||
ServiceID string `json:",omitempty"`
|
||||
AgentServiceCheck
|
||||
}
|
||||
|
||||
// AgentServiceCheck is used to create an associated
|
||||
// check for a service
|
||||
type AgentServiceCheck struct {
|
||||
Script string `json:",omitempty"`
|
||||
DockerContainerID string `json:",omitempty"`
|
||||
Shell string `json:",omitempty"` // Only supported for Docker.
|
||||
Interval string `json:",omitempty"`
|
||||
Timeout string `json:",omitempty"`
|
||||
TTL string `json:",omitempty"`
|
||||
HTTP string `json:",omitempty"`
|
||||
TCP string `json:",omitempty"`
|
||||
Status string `json:",omitempty"`
|
||||
}
|
||||
type AgentServiceChecks []*AgentServiceCheck
|
||||
|
||||
// Agent can be used to query the Agent endpoints
|
||||
type Agent struct {
|
||||
c *Client
|
||||
|
||||
// cache the node name
|
||||
nodeName string
|
||||
}
|
||||
|
||||
// Agent returns a handle to the agent endpoints
|
||||
func (c *Client) Agent() *Agent {
|
||||
return &Agent{c: c}
|
||||
}
|
||||
|
||||
// Self is used to query the agent we are speaking to for
|
||||
// information about itself
|
||||
func (a *Agent) Self() (map[string]map[string]interface{}, error) {
|
||||
r := a.c.newRequest("GET", "/v1/agent/self")
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var out map[string]map[string]interface{}
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// NodeName is used to get the node name of the agent
|
||||
func (a *Agent) NodeName() (string, error) {
|
||||
if a.nodeName != "" {
|
||||
return a.nodeName, nil
|
||||
}
|
||||
info, err := a.Self()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
name := info["Config"]["NodeName"].(string)
|
||||
a.nodeName = name
|
||||
return name, nil
|
||||
}
|
||||
|
||||
// Checks returns the locally registered checks
|
||||
func (a *Agent) Checks() (map[string]*AgentCheck, error) {
|
||||
r := a.c.newRequest("GET", "/v1/agent/checks")
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var out map[string]*AgentCheck
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Services returns the locally registered services
|
||||
func (a *Agent) Services() (map[string]*AgentService, error) {
|
||||
r := a.c.newRequest("GET", "/v1/agent/services")
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var out map[string]*AgentService
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Members returns the known gossip members. The WAN
|
||||
// flag can be used to query a server for WAN members.
|
||||
func (a *Agent) Members(wan bool) ([]*AgentMember, error) {
|
||||
r := a.c.newRequest("GET", "/v1/agent/members")
|
||||
if wan {
|
||||
r.params.Set("wan", "1")
|
||||
}
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var out []*AgentMember
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ServiceRegister is used to register a new service with
|
||||
// the local agent
|
||||
func (a *Agent) ServiceRegister(service *AgentServiceRegistration) error {
|
||||
r := a.c.newRequest("PUT", "/v1/agent/service/register")
|
||||
r.obj = service
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ServiceDeregister is used to deregister a service with
|
||||
// the local agent
|
||||
func (a *Agent) ServiceDeregister(serviceID string) error {
|
||||
r := a.c.newRequest("PUT", "/v1/agent/service/deregister/"+serviceID)
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// PassTTL is used to set a TTL check to the passing state.
|
||||
//
|
||||
// DEPRECATION NOTICE: This interface is deprecated in favor of UpdateTTL().
|
||||
// The client interface will be removed in 0.8 or changed to use
|
||||
// UpdateTTL()'s endpoint and the server endpoints will be removed in 0.9.
|
||||
func (a *Agent) PassTTL(checkID, note string) error {
|
||||
return a.updateTTL(checkID, note, "pass")
|
||||
}
|
||||
|
||||
// WarnTTL is used to set a TTL check to the warning state.
|
||||
//
|
||||
// DEPRECATION NOTICE: This interface is deprecated in favor of UpdateTTL().
|
||||
// The client interface will be removed in 0.8 or changed to use
|
||||
// UpdateTTL()'s endpoint and the server endpoints will be removed in 0.9.
|
||||
func (a *Agent) WarnTTL(checkID, note string) error {
|
||||
return a.updateTTL(checkID, note, "warn")
|
||||
}
|
||||
|
||||
// FailTTL is used to set a TTL check to the failing state.
|
||||
//
|
||||
// DEPRECATION NOTICE: This interface is deprecated in favor of UpdateTTL().
|
||||
// The client interface will be removed in 0.8 or changed to use
|
||||
// UpdateTTL()'s endpoint and the server endpoints will be removed in 0.9.
|
||||
func (a *Agent) FailTTL(checkID, note string) error {
|
||||
return a.updateTTL(checkID, note, "fail")
|
||||
}
|
||||
|
||||
// updateTTL is used to update the TTL of a check. This is the internal
|
||||
// method that uses the old API that's present in Consul versions prior to
|
||||
// 0.6.4. Since Consul didn't have an analogous "update" API before it seemed
|
||||
// ok to break this (former) UpdateTTL in favor of the new UpdateTTL below,
|
||||
// but keep the old Pass/Warn/Fail methods using the old API under the hood.
|
||||
//
|
||||
// DEPRECATION NOTICE: This interface is deprecated in favor of UpdateTTL().
|
||||
// The client interface will be removed in 0.8 and the server endpoints will
|
||||
// be removed in 0.9.
|
||||
func (a *Agent) updateTTL(checkID, note, status string) error {
|
||||
switch status {
|
||||
case "pass":
|
||||
case "warn":
|
||||
case "fail":
|
||||
default:
|
||||
return fmt.Errorf("Invalid status: %s", status)
|
||||
}
|
||||
endpoint := fmt.Sprintf("/v1/agent/check/%s/%s", status, checkID)
|
||||
r := a.c.newRequest("PUT", endpoint)
|
||||
r.params.Set("note", note)
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkUpdate is the payload for a PUT for a check update.
|
||||
type checkUpdate struct {
|
||||
// Status is one of the api.Health* states: HealthPassing
|
||||
// ("passing"), HealthWarning ("warning"), or HealthCritical
|
||||
// ("critical").
|
||||
Status string
|
||||
|
||||
// Output is the information to post to the UI for operators as the
|
||||
// output of the process that decided to hit the TTL check. This is
|
||||
// different from the note field that's associated with the check
|
||||
// itself.
|
||||
Output string
|
||||
}
|
||||
|
||||
// UpdateTTL is used to update the TTL of a check. This uses the newer API
|
||||
// that was introduced in Consul 0.6.4 and later. We translate the old status
|
||||
// strings for compatibility (though a newer version of Consul will still be
|
||||
// required to use this API).
|
||||
func (a *Agent) UpdateTTL(checkID, output, status string) error {
|
||||
switch status {
|
||||
case "pass", HealthPassing:
|
||||
status = HealthPassing
|
||||
case "warn", HealthWarning:
|
||||
status = HealthWarning
|
||||
case "fail", HealthCritical:
|
||||
status = HealthCritical
|
||||
default:
|
||||
return fmt.Errorf("Invalid status: %s", status)
|
||||
}
|
||||
|
||||
endpoint := fmt.Sprintf("/v1/agent/check/update/%s", checkID)
|
||||
r := a.c.newRequest("PUT", endpoint)
|
||||
r.obj = &checkUpdate{
|
||||
Status: status,
|
||||
Output: output,
|
||||
}
|
||||
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckRegister is used to register a new check with
|
||||
// the local agent
|
||||
func (a *Agent) CheckRegister(check *AgentCheckRegistration) error {
|
||||
r := a.c.newRequest("PUT", "/v1/agent/check/register")
|
||||
r.obj = check
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckDeregister is used to deregister a check with
|
||||
// the local agent
|
||||
func (a *Agent) CheckDeregister(checkID string) error {
|
||||
r := a.c.newRequest("PUT", "/v1/agent/check/deregister/"+checkID)
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Join is used to instruct the agent to attempt a join to
|
||||
// another cluster member
|
||||
func (a *Agent) Join(addr string, wan bool) error {
|
||||
r := a.c.newRequest("PUT", "/v1/agent/join/"+addr)
|
||||
if wan {
|
||||
r.params.Set("wan", "1")
|
||||
}
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ForceLeave is used to have the agent eject a failed node
|
||||
func (a *Agent) ForceLeave(node string) error {
|
||||
r := a.c.newRequest("PUT", "/v1/agent/force-leave/"+node)
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnableServiceMaintenance toggles service maintenance mode on
|
||||
// for the given service ID.
|
||||
func (a *Agent) EnableServiceMaintenance(serviceID, reason string) error {
|
||||
r := a.c.newRequest("PUT", "/v1/agent/service/maintenance/"+serviceID)
|
||||
r.params.Set("enable", "true")
|
||||
r.params.Set("reason", reason)
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// DisableServiceMaintenance toggles service maintenance mode off
|
||||
// for the given service ID.
|
||||
func (a *Agent) DisableServiceMaintenance(serviceID string) error {
|
||||
r := a.c.newRequest("PUT", "/v1/agent/service/maintenance/"+serviceID)
|
||||
r.params.Set("enable", "false")
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnableNodeMaintenance toggles node maintenance mode on for the
|
||||
// agent we are connected to.
|
||||
func (a *Agent) EnableNodeMaintenance(reason string) error {
|
||||
r := a.c.newRequest("PUT", "/v1/agent/maintenance")
|
||||
r.params.Set("enable", "true")
|
||||
r.params.Set("reason", reason)
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// DisableNodeMaintenance toggles node maintenance mode off for the
|
||||
// agent we are connected to.
|
||||
func (a *Agent) DisableNodeMaintenance() error {
|
||||
r := a.c.newRequest("PUT", "/v1/agent/maintenance")
|
||||
r.params.Set("enable", "false")
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
576
vendor/github.com/hashicorp/consul/api/api.go
generated
vendored
Normal file
576
vendor/github.com/hashicorp/consul/api/api.go
generated
vendored
Normal file
|
@ -0,0 +1,576 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-cleanhttp"
|
||||
)
|
||||
|
||||
// QueryOptions are used to parameterize a query
|
||||
type QueryOptions struct {
|
||||
// Providing a datacenter overwrites the DC provided
|
||||
// by the Config
|
||||
Datacenter string
|
||||
|
||||
// AllowStale allows any Consul server (non-leader) to service
|
||||
// a read. This allows for lower latency and higher throughput
|
||||
AllowStale bool
|
||||
|
||||
// RequireConsistent forces the read to be fully consistent.
|
||||
// This is more expensive but prevents ever performing a stale
|
||||
// read.
|
||||
RequireConsistent bool
|
||||
|
||||
// WaitIndex is used to enable a blocking query. Waits
|
||||
// until the timeout or the next index is reached
|
||||
WaitIndex uint64
|
||||
|
||||
// WaitTime is used to bound the duration of a wait.
|
||||
// Defaults to that of the Config, but can be overridden.
|
||||
WaitTime time.Duration
|
||||
|
||||
// Token is used to provide a per-request ACL token
|
||||
// which overrides the agent's default token.
|
||||
Token string
|
||||
|
||||
// Near is used to provide a node name that will sort the results
|
||||
// in ascending order based on the estimated round trip time from
|
||||
// that node. Setting this to "_agent" will use the agent's node
|
||||
// for the sort.
|
||||
Near string
|
||||
}
|
||||
|
||||
// WriteOptions are used to parameterize a write
|
||||
type WriteOptions struct {
|
||||
// Providing a datacenter overwrites the DC provided
|
||||
// by the Config
|
||||
Datacenter string
|
||||
|
||||
// Token is used to provide a per-request ACL token
|
||||
// which overrides the agent's default token.
|
||||
Token string
|
||||
}
|
||||
|
||||
// QueryMeta is used to return meta data about a query
|
||||
type QueryMeta struct {
|
||||
// LastIndex. This can be used as a WaitIndex to perform
|
||||
// a blocking query
|
||||
LastIndex uint64
|
||||
|
||||
// Time of last contact from the leader for the
|
||||
// server servicing the request
|
||||
LastContact time.Duration
|
||||
|
||||
// Is there a known leader
|
||||
KnownLeader bool
|
||||
|
||||
// How long did the request take
|
||||
RequestTime time.Duration
|
||||
}
|
||||
|
||||
// WriteMeta is used to return meta data about a write
|
||||
type WriteMeta struct {
|
||||
// How long did the request take
|
||||
RequestTime time.Duration
|
||||
}
|
||||
|
||||
// HttpBasicAuth is used to authenticate http client with HTTP Basic Authentication
|
||||
type HttpBasicAuth struct {
|
||||
// Username to use for HTTP Basic Authentication
|
||||
Username string
|
||||
|
||||
// Password to use for HTTP Basic Authentication
|
||||
Password string
|
||||
}
|
||||
|
||||
// Config is used to configure the creation of a client
|
||||
type Config struct {
|
||||
// Address is the address of the Consul server
|
||||
Address string
|
||||
|
||||
// Scheme is the URI scheme for the Consul server
|
||||
Scheme string
|
||||
|
||||
// Datacenter to use. If not provided, the default agent datacenter is used.
|
||||
Datacenter string
|
||||
|
||||
// HttpClient is the client to use. Default will be
|
||||
// used if not provided.
|
||||
HttpClient *http.Client
|
||||
|
||||
// HttpAuth is the auth info to use for http access.
|
||||
HttpAuth *HttpBasicAuth
|
||||
|
||||
// WaitTime limits how long a Watch will block. If not provided,
|
||||
// the agent default values will be used.
|
||||
WaitTime time.Duration
|
||||
|
||||
// Token is used to provide a per-request ACL token
|
||||
// which overrides the agent's default token.
|
||||
Token string
|
||||
}
|
||||
|
||||
// TLSConfig is used to generate a TLSClientConfig that's useful for talking to
|
||||
// Consul using TLS.
|
||||
type TLSConfig struct {
|
||||
// Address is the optional address of the Consul server. The port, if any
|
||||
// will be removed from here and this will be set to the ServerName of the
|
||||
// resulting config.
|
||||
Address string
|
||||
|
||||
// CAFile is the optional path to the CA certificate used for Consul
|
||||
// communication, defaults to the system bundle if not specified.
|
||||
CAFile string
|
||||
|
||||
// CertFile is the optional path to the certificate for Consul
|
||||
// communication. If this is set then you need to also set KeyFile.
|
||||
CertFile string
|
||||
|
||||
// KeyFile is the optional path to the private key for Consul communication.
|
||||
// If this is set then you need to also set CertFile.
|
||||
KeyFile string
|
||||
|
||||
// InsecureSkipVerify if set to true will disable TLS host verification.
|
||||
InsecureSkipVerify bool
|
||||
}
|
||||
|
||||
// DefaultConfig returns a default configuration for the client. By default this
|
||||
// will pool and reuse idle connections to Consul. If you have a long-lived
|
||||
// client object, this is the desired behavior and should make the most efficient
|
||||
// use of the connections to Consul. If you don't reuse a client object , which
|
||||
// is not recommended, then you may notice idle connections building up over
|
||||
// time. To avoid this, use the DefaultNonPooledConfig() instead.
|
||||
func DefaultConfig() *Config {
|
||||
return defaultConfig(cleanhttp.DefaultPooledTransport)
|
||||
}
|
||||
|
||||
// DefaultNonPooledConfig returns a default configuration for the client which
|
||||
// does not pool connections. This isn't a recommended configuration because it
|
||||
// will reconnect to Consul on every request, but this is useful to avoid the
|
||||
// accumulation of idle connections if you make many client objects during the
|
||||
// lifetime of your application.
|
||||
func DefaultNonPooledConfig() *Config {
|
||||
return defaultConfig(cleanhttp.DefaultTransport)
|
||||
}
|
||||
|
||||
// defaultConfig returns the default configuration for the client, using the
|
||||
// given function to make the transport.
|
||||
func defaultConfig(transportFn func() *http.Transport) *Config {
|
||||
config := &Config{
|
||||
Address: "127.0.0.1:8500",
|
||||
Scheme: "http",
|
||||
HttpClient: &http.Client{
|
||||
Transport: transportFn(),
|
||||
},
|
||||
}
|
||||
|
||||
if addr := os.Getenv("CONSUL_HTTP_ADDR"); addr != "" {
|
||||
config.Address = addr
|
||||
}
|
||||
|
||||
if token := os.Getenv("CONSUL_HTTP_TOKEN"); token != "" {
|
||||
config.Token = token
|
||||
}
|
||||
|
||||
if auth := os.Getenv("CONSUL_HTTP_AUTH"); auth != "" {
|
||||
var username, password string
|
||||
if strings.Contains(auth, ":") {
|
||||
split := strings.SplitN(auth, ":", 2)
|
||||
username = split[0]
|
||||
password = split[1]
|
||||
} else {
|
||||
username = auth
|
||||
}
|
||||
|
||||
config.HttpAuth = &HttpBasicAuth{
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
}
|
||||
|
||||
if ssl := os.Getenv("CONSUL_HTTP_SSL"); ssl != "" {
|
||||
enabled, err := strconv.ParseBool(ssl)
|
||||
if err != nil {
|
||||
log.Printf("[WARN] client: could not parse CONSUL_HTTP_SSL: %s", err)
|
||||
}
|
||||
|
||||
if enabled {
|
||||
config.Scheme = "https"
|
||||
}
|
||||
}
|
||||
|
||||
if verify := os.Getenv("CONSUL_HTTP_SSL_VERIFY"); verify != "" {
|
||||
doVerify, err := strconv.ParseBool(verify)
|
||||
if err != nil {
|
||||
log.Printf("[WARN] client: could not parse CONSUL_HTTP_SSL_VERIFY: %s", err)
|
||||
}
|
||||
|
||||
if !doVerify {
|
||||
tlsClientConfig, err := SetupTLSConfig(&TLSConfig{
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
|
||||
// We don't expect this to fail given that we aren't
|
||||
// parsing any of the input, but we panic just in case
|
||||
// since this doesn't have an error return.
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
transport := transportFn()
|
||||
transport.TLSClientConfig = tlsClientConfig
|
||||
config.HttpClient.Transport = transport
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// TLSConfig is used to generate a TLSClientConfig that's useful for talking to
|
||||
// Consul using TLS.
|
||||
func SetupTLSConfig(tlsConfig *TLSConfig) (*tls.Config, error) {
|
||||
tlsClientConfig := &tls.Config{
|
||||
InsecureSkipVerify: tlsConfig.InsecureSkipVerify,
|
||||
}
|
||||
|
||||
if tlsConfig.Address != "" {
|
||||
server := tlsConfig.Address
|
||||
hasPort := strings.LastIndex(server, ":") > strings.LastIndex(server, "]")
|
||||
if hasPort {
|
||||
var err error
|
||||
server, _, err = net.SplitHostPort(server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
tlsClientConfig.ServerName = server
|
||||
}
|
||||
|
||||
if tlsConfig.CertFile != "" && tlsConfig.KeyFile != "" {
|
||||
tlsCert, err := tls.LoadX509KeyPair(tlsConfig.CertFile, tlsConfig.KeyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsClientConfig.Certificates = []tls.Certificate{tlsCert}
|
||||
}
|
||||
|
||||
if tlsConfig.CAFile != "" {
|
||||
data, err := ioutil.ReadFile(tlsConfig.CAFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read CA file: %v", err)
|
||||
}
|
||||
|
||||
caPool := x509.NewCertPool()
|
||||
if !caPool.AppendCertsFromPEM(data) {
|
||||
return nil, fmt.Errorf("failed to parse CA certificate")
|
||||
}
|
||||
tlsClientConfig.RootCAs = caPool
|
||||
}
|
||||
|
||||
return tlsClientConfig, nil
|
||||
}
|
||||
|
||||
// Client provides a client to the Consul API
|
||||
type Client struct {
|
||||
config Config
|
||||
}
|
||||
|
||||
// NewClient returns a new client
|
||||
func NewClient(config *Config) (*Client, error) {
|
||||
// bootstrap the config
|
||||
defConfig := DefaultConfig()
|
||||
|
||||
if len(config.Address) == 0 {
|
||||
config.Address = defConfig.Address
|
||||
}
|
||||
|
||||
if len(config.Scheme) == 0 {
|
||||
config.Scheme = defConfig.Scheme
|
||||
}
|
||||
|
||||
if config.HttpClient == nil {
|
||||
config.HttpClient = defConfig.HttpClient
|
||||
}
|
||||
|
||||
if parts := strings.SplitN(config.Address, "unix://", 2); len(parts) == 2 {
|
||||
trans := cleanhttp.DefaultTransport()
|
||||
trans.Dial = func(_, _ string) (net.Conn, error) {
|
||||
return net.Dial("unix", parts[1])
|
||||
}
|
||||
config.HttpClient = &http.Client{
|
||||
Transport: trans,
|
||||
}
|
||||
config.Address = parts[1]
|
||||
}
|
||||
|
||||
client := &Client{
|
||||
config: *config,
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// request is used to help build up a request
|
||||
type request struct {
|
||||
config *Config
|
||||
method string
|
||||
url *url.URL
|
||||
params url.Values
|
||||
body io.Reader
|
||||
obj interface{}
|
||||
}
|
||||
|
||||
// setQueryOptions is used to annotate the request with
|
||||
// additional query options
|
||||
func (r *request) setQueryOptions(q *QueryOptions) {
|
||||
if q == nil {
|
||||
return
|
||||
}
|
||||
if q.Datacenter != "" {
|
||||
r.params.Set("dc", q.Datacenter)
|
||||
}
|
||||
if q.AllowStale {
|
||||
r.params.Set("stale", "")
|
||||
}
|
||||
if q.RequireConsistent {
|
||||
r.params.Set("consistent", "")
|
||||
}
|
||||
if q.WaitIndex != 0 {
|
||||
r.params.Set("index", strconv.FormatUint(q.WaitIndex, 10))
|
||||
}
|
||||
if q.WaitTime != 0 {
|
||||
r.params.Set("wait", durToMsec(q.WaitTime))
|
||||
}
|
||||
if q.Token != "" {
|
||||
r.params.Set("token", q.Token)
|
||||
}
|
||||
if q.Near != "" {
|
||||
r.params.Set("near", q.Near)
|
||||
}
|
||||
}
|
||||
|
||||
// durToMsec converts a duration to a millisecond specified string. If the
|
||||
// user selected a positive value that rounds to 0 ms, then we will use 1 ms
|
||||
// so they get a short delay, otherwise Consul will translate the 0 ms into
|
||||
// a huge default delay.
|
||||
func durToMsec(dur time.Duration) string {
|
||||
ms := dur / time.Millisecond
|
||||
if dur > 0 && ms == 0 {
|
||||
ms = 1
|
||||
}
|
||||
return fmt.Sprintf("%dms", ms)
|
||||
}
|
||||
|
||||
// serverError is a string we look for to detect 500 errors.
|
||||
const serverError = "Unexpected response code: 500"
|
||||
|
||||
// IsServerError returns true for 500 errors from the Consul servers, these are
|
||||
// usually retryable at a later time.
|
||||
func IsServerError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO (slackpad) - Make a real error type here instead of using
|
||||
// a string check.
|
||||
return strings.Contains(err.Error(), serverError)
|
||||
}
|
||||
|
||||
// setWriteOptions is used to annotate the request with
|
||||
// additional write options
|
||||
func (r *request) setWriteOptions(q *WriteOptions) {
|
||||
if q == nil {
|
||||
return
|
||||
}
|
||||
if q.Datacenter != "" {
|
||||
r.params.Set("dc", q.Datacenter)
|
||||
}
|
||||
if q.Token != "" {
|
||||
r.params.Set("token", q.Token)
|
||||
}
|
||||
}
|
||||
|
||||
// toHTTP converts the request to an HTTP request
|
||||
func (r *request) toHTTP() (*http.Request, error) {
|
||||
// Encode the query parameters
|
||||
r.url.RawQuery = r.params.Encode()
|
||||
|
||||
// Check if we should encode the body
|
||||
if r.body == nil && r.obj != nil {
|
||||
if b, err := encodeBody(r.obj); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
r.body = b
|
||||
}
|
||||
}
|
||||
|
||||
// Create the HTTP request
|
||||
req, err := http.NewRequest(r.method, r.url.RequestURI(), r.body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.URL.Host = r.url.Host
|
||||
req.URL.Scheme = r.url.Scheme
|
||||
req.Host = r.url.Host
|
||||
|
||||
// Setup auth
|
||||
if r.config.HttpAuth != nil {
|
||||
req.SetBasicAuth(r.config.HttpAuth.Username, r.config.HttpAuth.Password)
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// newRequest is used to create a new request
|
||||
func (c *Client) newRequest(method, path string) *request {
|
||||
r := &request{
|
||||
config: &c.config,
|
||||
method: method,
|
||||
url: &url.URL{
|
||||
Scheme: c.config.Scheme,
|
||||
Host: c.config.Address,
|
||||
Path: path,
|
||||
},
|
||||
params: make(map[string][]string),
|
||||
}
|
||||
if c.config.Datacenter != "" {
|
||||
r.params.Set("dc", c.config.Datacenter)
|
||||
}
|
||||
if c.config.WaitTime != 0 {
|
||||
r.params.Set("wait", durToMsec(r.config.WaitTime))
|
||||
}
|
||||
if c.config.Token != "" {
|
||||
r.params.Set("token", r.config.Token)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// doRequest runs a request with our client
|
||||
func (c *Client) doRequest(r *request) (time.Duration, *http.Response, error) {
|
||||
req, err := r.toHTTP()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
start := time.Now()
|
||||
resp, err := c.config.HttpClient.Do(req)
|
||||
diff := time.Now().Sub(start)
|
||||
return diff, resp, err
|
||||
}
|
||||
|
||||
// Query is used to do a GET request against an endpoint
|
||||
// and deserialize the response into an interface using
|
||||
// standard Consul conventions.
|
||||
func (c *Client) query(endpoint string, out interface{}, q *QueryOptions) (*QueryMeta, error) {
|
||||
r := c.newRequest("GET", endpoint)
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
if err := decodeBody(resp, out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return qm, nil
|
||||
}
|
||||
|
||||
// write is used to do a PUT request against an endpoint
|
||||
// and serialize/deserialized using the standard Consul conventions.
|
||||
func (c *Client) write(endpoint string, in, out interface{}, q *WriteOptions) (*WriteMeta, error) {
|
||||
r := c.newRequest("PUT", endpoint)
|
||||
r.setWriteOptions(q)
|
||||
r.obj = in
|
||||
rtt, resp, err := requireOK(c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
wm := &WriteMeta{RequestTime: rtt}
|
||||
if out != nil {
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return wm, nil
|
||||
}
|
||||
|
||||
// parseQueryMeta is used to help parse query meta-data
|
||||
func parseQueryMeta(resp *http.Response, q *QueryMeta) error {
|
||||
header := resp.Header
|
||||
|
||||
// Parse the X-Consul-Index
|
||||
index, err := strconv.ParseUint(header.Get("X-Consul-Index"), 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to parse X-Consul-Index: %v", err)
|
||||
}
|
||||
q.LastIndex = index
|
||||
|
||||
// Parse the X-Consul-LastContact
|
||||
last, err := strconv.ParseUint(header.Get("X-Consul-LastContact"), 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to parse X-Consul-LastContact: %v", err)
|
||||
}
|
||||
q.LastContact = time.Duration(last) * time.Millisecond
|
||||
|
||||
// Parse the X-Consul-KnownLeader
|
||||
switch header.Get("X-Consul-KnownLeader") {
|
||||
case "true":
|
||||
q.KnownLeader = true
|
||||
default:
|
||||
q.KnownLeader = false
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeBody is used to JSON decode a body
|
||||
func decodeBody(resp *http.Response, out interface{}) error {
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
return dec.Decode(out)
|
||||
}
|
||||
|
||||
// encodeBody is used to encode a request body
|
||||
func encodeBody(obj interface{}) (io.Reader, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
enc := json.NewEncoder(buf)
|
||||
if err := enc.Encode(obj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// requireOK is used to wrap doRequest and check for a 200
|
||||
func requireOK(d time.Duration, resp *http.Response, e error) (time.Duration, *http.Response, error) {
|
||||
if e != nil {
|
||||
if resp != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
return d, nil, e
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
var buf bytes.Buffer
|
||||
io.Copy(&buf, resp.Body)
|
||||
resp.Body.Close()
|
||||
return d, nil, fmt.Errorf("Unexpected response code: %d (%s)", resp.StatusCode, buf.Bytes())
|
||||
}
|
||||
return d, resp, nil
|
||||
}
|
183
vendor/github.com/hashicorp/consul/api/catalog.go
generated
vendored
Normal file
183
vendor/github.com/hashicorp/consul/api/catalog.go
generated
vendored
Normal file
|
@ -0,0 +1,183 @@
|
|||
package api
|
||||
|
||||
type Node struct {
|
||||
Node string
|
||||
Address string
|
||||
}
|
||||
|
||||
type CatalogService struct {
|
||||
Node string
|
||||
Address string
|
||||
ServiceID string
|
||||
ServiceName string
|
||||
ServiceAddress string
|
||||
ServiceTags []string
|
||||
ServicePort int
|
||||
ServiceEnableTagOverride bool
|
||||
}
|
||||
|
||||
type CatalogNode struct {
|
||||
Node *Node
|
||||
Services map[string]*AgentService
|
||||
}
|
||||
|
||||
type CatalogRegistration struct {
|
||||
Node string
|
||||
Address string
|
||||
Datacenter string
|
||||
Service *AgentService
|
||||
Check *AgentCheck
|
||||
}
|
||||
|
||||
type CatalogDeregistration struct {
|
||||
Node string
|
||||
Address string
|
||||
Datacenter string
|
||||
ServiceID string
|
||||
CheckID string
|
||||
}
|
||||
|
||||
// Catalog can be used to query the Catalog endpoints
|
||||
type Catalog struct {
|
||||
c *Client
|
||||
}
|
||||
|
||||
// Catalog returns a handle to the catalog endpoints
|
||||
func (c *Client) Catalog() *Catalog {
|
||||
return &Catalog{c}
|
||||
}
|
||||
|
||||
func (c *Catalog) Register(reg *CatalogRegistration, q *WriteOptions) (*WriteMeta, error) {
|
||||
r := c.c.newRequest("PUT", "/v1/catalog/register")
|
||||
r.setWriteOptions(q)
|
||||
r.obj = reg
|
||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
wm := &WriteMeta{}
|
||||
wm.RequestTime = rtt
|
||||
|
||||
return wm, nil
|
||||
}
|
||||
|
||||
func (c *Catalog) Deregister(dereg *CatalogDeregistration, q *WriteOptions) (*WriteMeta, error) {
|
||||
r := c.c.newRequest("PUT", "/v1/catalog/deregister")
|
||||
r.setWriteOptions(q)
|
||||
r.obj = dereg
|
||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
wm := &WriteMeta{}
|
||||
wm.RequestTime = rtt
|
||||
|
||||
return wm, nil
|
||||
}
|
||||
|
||||
// Datacenters is used to query for all the known datacenters
|
||||
func (c *Catalog) Datacenters() ([]string, error) {
|
||||
r := c.c.newRequest("GET", "/v1/catalog/datacenters")
|
||||
_, resp, err := requireOK(c.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var out []string
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Nodes is used to query all the known nodes
|
||||
func (c *Catalog) Nodes(q *QueryOptions) ([]*Node, *QueryMeta, error) {
|
||||
r := c.c.newRequest("GET", "/v1/catalog/nodes")
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var out []*Node
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return out, qm, nil
|
||||
}
|
||||
|
||||
// Services is used to query for all known services
|
||||
func (c *Catalog) Services(q *QueryOptions) (map[string][]string, *QueryMeta, error) {
|
||||
r := c.c.newRequest("GET", "/v1/catalog/services")
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var out map[string][]string
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return out, qm, nil
|
||||
}
|
||||
|
||||
// Service is used to query catalog entries for a given service
|
||||
func (c *Catalog) Service(service, tag string, q *QueryOptions) ([]*CatalogService, *QueryMeta, error) {
|
||||
r := c.c.newRequest("GET", "/v1/catalog/service/"+service)
|
||||
r.setQueryOptions(q)
|
||||
if tag != "" {
|
||||
r.params.Set("tag", tag)
|
||||
}
|
||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var out []*CatalogService
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return out, qm, nil
|
||||
}
|
||||
|
||||
// Node is used to query for service information about a single node
|
||||
func (c *Catalog) Node(node string, q *QueryOptions) (*CatalogNode, *QueryMeta, error) {
|
||||
r := c.c.newRequest("GET", "/v1/catalog/node/"+node)
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var out *CatalogNode
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return out, qm, nil
|
||||
}
|
66
vendor/github.com/hashicorp/consul/api/coordinate.go
generated
vendored
Normal file
66
vendor/github.com/hashicorp/consul/api/coordinate.go
generated
vendored
Normal file
|
@ -0,0 +1,66 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/serf/coordinate"
|
||||
)
|
||||
|
||||
// CoordinateEntry represents a node and its associated network coordinate.
|
||||
type CoordinateEntry struct {
|
||||
Node string
|
||||
Coord *coordinate.Coordinate
|
||||
}
|
||||
|
||||
// CoordinateDatacenterMap represents a datacenter and its associated WAN
|
||||
// nodes and their associates coordinates.
|
||||
type CoordinateDatacenterMap struct {
|
||||
Datacenter string
|
||||
Coordinates []CoordinateEntry
|
||||
}
|
||||
|
||||
// Coordinate can be used to query the coordinate endpoints
|
||||
type Coordinate struct {
|
||||
c *Client
|
||||
}
|
||||
|
||||
// Coordinate returns a handle to the coordinate endpoints
|
||||
func (c *Client) Coordinate() *Coordinate {
|
||||
return &Coordinate{c}
|
||||
}
|
||||
|
||||
// Datacenters is used to return the coordinates of all the servers in the WAN
|
||||
// pool.
|
||||
func (c *Coordinate) Datacenters() ([]*CoordinateDatacenterMap, error) {
|
||||
r := c.c.newRequest("GET", "/v1/coordinate/datacenters")
|
||||
_, resp, err := requireOK(c.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var out []*CoordinateDatacenterMap
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Nodes is used to return the coordinates of all the nodes in the LAN pool.
|
||||
func (c *Coordinate) Nodes(q *QueryOptions) ([]*CoordinateEntry, *QueryMeta, error) {
|
||||
r := c.c.newRequest("GET", "/v1/coordinate/nodes")
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var out []*CoordinateEntry
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return out, qm, nil
|
||||
}
|
104
vendor/github.com/hashicorp/consul/api/event.go
generated
vendored
Normal file
104
vendor/github.com/hashicorp/consul/api/event.go
generated
vendored
Normal file
|
@ -0,0 +1,104 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Event can be used to query the Event endpoints
|
||||
type Event struct {
|
||||
c *Client
|
||||
}
|
||||
|
||||
// UserEvent represents an event that was fired by the user
|
||||
type UserEvent struct {
|
||||
ID string
|
||||
Name string
|
||||
Payload []byte
|
||||
NodeFilter string
|
||||
ServiceFilter string
|
||||
TagFilter string
|
||||
Version int
|
||||
LTime uint64
|
||||
}
|
||||
|
||||
// Event returns a handle to the event endpoints
|
||||
func (c *Client) Event() *Event {
|
||||
return &Event{c}
|
||||
}
|
||||
|
||||
// Fire is used to fire a new user event. Only the Name, Payload and Filters
|
||||
// are respected. This returns the ID or an associated error. Cross DC requests
|
||||
// are supported.
|
||||
func (e *Event) Fire(params *UserEvent, q *WriteOptions) (string, *WriteMeta, error) {
|
||||
r := e.c.newRequest("PUT", "/v1/event/fire/"+params.Name)
|
||||
r.setWriteOptions(q)
|
||||
if params.NodeFilter != "" {
|
||||
r.params.Set("node", params.NodeFilter)
|
||||
}
|
||||
if params.ServiceFilter != "" {
|
||||
r.params.Set("service", params.ServiceFilter)
|
||||
}
|
||||
if params.TagFilter != "" {
|
||||
r.params.Set("tag", params.TagFilter)
|
||||
}
|
||||
if params.Payload != nil {
|
||||
r.body = bytes.NewReader(params.Payload)
|
||||
}
|
||||
|
||||
rtt, resp, err := requireOK(e.c.doRequest(r))
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
wm := &WriteMeta{RequestTime: rtt}
|
||||
var out UserEvent
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return out.ID, wm, nil
|
||||
}
|
||||
|
||||
// List is used to get the most recent events an agent has received.
|
||||
// This list can be optionally filtered by the name. This endpoint supports
|
||||
// quasi-blocking queries. The index is not monotonic, nor does it provide provide
|
||||
// LastContact or KnownLeader.
|
||||
func (e *Event) List(name string, q *QueryOptions) ([]*UserEvent, *QueryMeta, error) {
|
||||
r := e.c.newRequest("GET", "/v1/event/list")
|
||||
r.setQueryOptions(q)
|
||||
if name != "" {
|
||||
r.params.Set("name", name)
|
||||
}
|
||||
rtt, resp, err := requireOK(e.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var entries []*UserEvent
|
||||
if err := decodeBody(resp, &entries); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return entries, qm, nil
|
||||
}
|
||||
|
||||
// IDToIndex is a bit of a hack. This simulates the index generation to
|
||||
// convert an event ID into a WaitIndex.
|
||||
func (e *Event) IDToIndex(uuid string) uint64 {
|
||||
lower := uuid[0:8] + uuid[9:13] + uuid[14:18]
|
||||
upper := uuid[19:23] + uuid[24:36]
|
||||
lowVal, err := strconv.ParseUint(lower, 16, 64)
|
||||
if err != nil {
|
||||
panic("Failed to convert " + lower)
|
||||
}
|
||||
highVal, err := strconv.ParseUint(upper, 16, 64)
|
||||
if err != nil {
|
||||
panic("Failed to convert " + upper)
|
||||
}
|
||||
return lowVal ^ highVal
|
||||
}
|
146
vendor/github.com/hashicorp/consul/api/health.go
generated
vendored
Normal file
146
vendor/github.com/hashicorp/consul/api/health.go
generated
vendored
Normal file
|
@ -0,0 +1,146 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
// HealthAny is special, and is used as a wild card,
|
||||
// not as a specific state.
|
||||
HealthAny = "any"
|
||||
HealthUnknown = "unknown"
|
||||
HealthPassing = "passing"
|
||||
HealthWarning = "warning"
|
||||
HealthCritical = "critical"
|
||||
)
|
||||
|
||||
// HealthCheck is used to represent a single check
|
||||
type HealthCheck struct {
|
||||
Node string
|
||||
CheckID string
|
||||
Name string
|
||||
Status string
|
||||
Notes string
|
||||
Output string
|
||||
ServiceID string
|
||||
ServiceName string
|
||||
}
|
||||
|
||||
// ServiceEntry is used for the health service endpoint
|
||||
type ServiceEntry struct {
|
||||
Node *Node
|
||||
Service *AgentService
|
||||
Checks []*HealthCheck
|
||||
}
|
||||
|
||||
// Health can be used to query the Health endpoints
|
||||
type Health struct {
|
||||
c *Client
|
||||
}
|
||||
|
||||
// Health returns a handle to the health endpoints
|
||||
func (c *Client) Health() *Health {
|
||||
return &Health{c}
|
||||
}
|
||||
|
||||
// Node is used to query for checks belonging to a given node
|
||||
func (h *Health) Node(node string, q *QueryOptions) ([]*HealthCheck, *QueryMeta, error) {
|
||||
r := h.c.newRequest("GET", "/v1/health/node/"+node)
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var out []*HealthCheck
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return out, qm, nil
|
||||
}
|
||||
|
||||
// Checks is used to return the checks associated with a service
|
||||
func (h *Health) Checks(service string, q *QueryOptions) ([]*HealthCheck, *QueryMeta, error) {
|
||||
r := h.c.newRequest("GET", "/v1/health/checks/"+service)
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var out []*HealthCheck
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return out, qm, nil
|
||||
}
|
||||
|
||||
// Service is used to query health information along with service info
|
||||
// for a given service. It can optionally do server-side filtering on a tag
|
||||
// or nodes with passing health checks only.
|
||||
func (h *Health) Service(service, tag string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) {
|
||||
r := h.c.newRequest("GET", "/v1/health/service/"+service)
|
||||
r.setQueryOptions(q)
|
||||
if tag != "" {
|
||||
r.params.Set("tag", tag)
|
||||
}
|
||||
if passingOnly {
|
||||
r.params.Set(HealthPassing, "1")
|
||||
}
|
||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var out []*ServiceEntry
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return out, qm, nil
|
||||
}
|
||||
|
||||
// State is used to retrieve all the checks in a given state.
|
||||
// The wildcard "any" state can also be used for all checks.
|
||||
func (h *Health) State(state string, q *QueryOptions) ([]*HealthCheck, *QueryMeta, error) {
|
||||
switch state {
|
||||
case HealthAny:
|
||||
case HealthWarning:
|
||||
case HealthCritical:
|
||||
case HealthPassing:
|
||||
case HealthUnknown:
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("Unsupported state: %v", state)
|
||||
}
|
||||
r := h.c.newRequest("GET", "/v1/health/state/"+state)
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var out []*HealthCheck
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return out, qm, nil
|
||||
}
|
396
vendor/github.com/hashicorp/consul/api/kv.go
generated
vendored
Normal file
396
vendor/github.com/hashicorp/consul/api/kv.go
generated
vendored
Normal file
|
@ -0,0 +1,396 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// KVPair is used to represent a single K/V entry
|
||||
type KVPair struct {
|
||||
Key string
|
||||
CreateIndex uint64
|
||||
ModifyIndex uint64
|
||||
LockIndex uint64
|
||||
Flags uint64
|
||||
Value []byte
|
||||
Session string
|
||||
}
|
||||
|
||||
// KVPairs is a list of KVPair objects
|
||||
type KVPairs []*KVPair
|
||||
|
||||
// KVOp constants give possible operations available in a KVTxn.
|
||||
type KVOp string
|
||||
|
||||
const (
|
||||
KVSet KVOp = "set"
|
||||
KVDelete = "delete"
|
||||
KVDeleteCAS = "delete-cas"
|
||||
KVDeleteTree = "delete-tree"
|
||||
KVCAS = "cas"
|
||||
KVLock = "lock"
|
||||
KVUnlock = "unlock"
|
||||
KVGet = "get"
|
||||
KVGetTree = "get-tree"
|
||||
KVCheckSession = "check-session"
|
||||
KVCheckIndex = "check-index"
|
||||
)
|
||||
|
||||
// KVTxnOp defines a single operation inside a transaction.
|
||||
type KVTxnOp struct {
|
||||
Verb string
|
||||
Key string
|
||||
Value []byte
|
||||
Flags uint64
|
||||
Index uint64
|
||||
Session string
|
||||
}
|
||||
|
||||
// KVTxnOps defines a set of operations to be performed inside a single
|
||||
// transaction.
|
||||
type KVTxnOps []*KVTxnOp
|
||||
|
||||
// KVTxnResponse has the outcome of a transaction.
|
||||
type KVTxnResponse struct {
|
||||
Results []*KVPair
|
||||
Errors TxnErrors
|
||||
}
|
||||
|
||||
// KV is used to manipulate the K/V API
|
||||
type KV struct {
|
||||
c *Client
|
||||
}
|
||||
|
||||
// KV is used to return a handle to the K/V apis
|
||||
func (c *Client) KV() *KV {
|
||||
return &KV{c}
|
||||
}
|
||||
|
||||
// Get is used to lookup a single key
|
||||
func (k *KV) Get(key string, q *QueryOptions) (*KVPair, *QueryMeta, error) {
|
||||
resp, qm, err := k.getInternal(key, nil, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if resp == nil {
|
||||
return nil, qm, nil
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var entries []*KVPair
|
||||
if err := decodeBody(resp, &entries); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(entries) > 0 {
|
||||
return entries[0], qm, nil
|
||||
}
|
||||
return nil, qm, nil
|
||||
}
|
||||
|
||||
// List is used to lookup all keys under a prefix
|
||||
func (k *KV) List(prefix string, q *QueryOptions) (KVPairs, *QueryMeta, error) {
|
||||
resp, qm, err := k.getInternal(prefix, map[string]string{"recurse": ""}, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if resp == nil {
|
||||
return nil, qm, nil
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var entries []*KVPair
|
||||
if err := decodeBody(resp, &entries); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return entries, qm, nil
|
||||
}
|
||||
|
||||
// Keys is used to list all the keys under a prefix. Optionally,
|
||||
// a separator can be used to limit the responses.
|
||||
func (k *KV) Keys(prefix, separator string, q *QueryOptions) ([]string, *QueryMeta, error) {
|
||||
params := map[string]string{"keys": ""}
|
||||
if separator != "" {
|
||||
params["separator"] = separator
|
||||
}
|
||||
resp, qm, err := k.getInternal(prefix, params, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if resp == nil {
|
||||
return nil, qm, nil
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var entries []string
|
||||
if err := decodeBody(resp, &entries); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return entries, qm, nil
|
||||
}
|
||||
|
||||
func (k *KV) getInternal(key string, params map[string]string, q *QueryOptions) (*http.Response, *QueryMeta, error) {
|
||||
r := k.c.newRequest("GET", "/v1/kv/"+key)
|
||||
r.setQueryOptions(q)
|
||||
for param, val := range params {
|
||||
r.params.Set(param, val)
|
||||
}
|
||||
rtt, resp, err := k.c.doRequest(r)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
if resp.StatusCode == 404 {
|
||||
resp.Body.Close()
|
||||
return nil, qm, nil
|
||||
} else if resp.StatusCode != 200 {
|
||||
resp.Body.Close()
|
||||
return nil, nil, fmt.Errorf("Unexpected response code: %d", resp.StatusCode)
|
||||
}
|
||||
return resp, qm, nil
|
||||
}
|
||||
|
||||
// Put is used to write a new value. Only the
|
||||
// Key, Flags and Value is respected.
|
||||
func (k *KV) Put(p *KVPair, q *WriteOptions) (*WriteMeta, error) {
|
||||
params := make(map[string]string, 1)
|
||||
if p.Flags != 0 {
|
||||
params["flags"] = strconv.FormatUint(p.Flags, 10)
|
||||
}
|
||||
_, wm, err := k.put(p.Key, params, p.Value, q)
|
||||
return wm, err
|
||||
}
|
||||
|
||||
// CAS is used for a Check-And-Set operation. The Key,
|
||||
// ModifyIndex, Flags and Value are respected. Returns true
|
||||
// on success or false on failures.
|
||||
func (k *KV) CAS(p *KVPair, q *WriteOptions) (bool, *WriteMeta, error) {
|
||||
params := make(map[string]string, 2)
|
||||
if p.Flags != 0 {
|
||||
params["flags"] = strconv.FormatUint(p.Flags, 10)
|
||||
}
|
||||
params["cas"] = strconv.FormatUint(p.ModifyIndex, 10)
|
||||
return k.put(p.Key, params, p.Value, q)
|
||||
}
|
||||
|
||||
// Acquire is used for a lock acquisition operation. The Key,
|
||||
// Flags, Value and Session are respected. Returns true
|
||||
// on success or false on failures.
|
||||
func (k *KV) Acquire(p *KVPair, q *WriteOptions) (bool, *WriteMeta, error) {
|
||||
params := make(map[string]string, 2)
|
||||
if p.Flags != 0 {
|
||||
params["flags"] = strconv.FormatUint(p.Flags, 10)
|
||||
}
|
||||
params["acquire"] = p.Session
|
||||
return k.put(p.Key, params, p.Value, q)
|
||||
}
|
||||
|
||||
// Release is used for a lock release operation. The Key,
|
||||
// Flags, Value and Session are respected. Returns true
|
||||
// on success or false on failures.
|
||||
func (k *KV) Release(p *KVPair, q *WriteOptions) (bool, *WriteMeta, error) {
|
||||
params := make(map[string]string, 2)
|
||||
if p.Flags != 0 {
|
||||
params["flags"] = strconv.FormatUint(p.Flags, 10)
|
||||
}
|
||||
params["release"] = p.Session
|
||||
return k.put(p.Key, params, p.Value, q)
|
||||
}
|
||||
|
||||
func (k *KV) put(key string, params map[string]string, body []byte, q *WriteOptions) (bool, *WriteMeta, error) {
|
||||
if len(key) > 0 && key[0] == '/' {
|
||||
return false, nil, fmt.Errorf("Invalid key. Key must not begin with a '/': %s", key)
|
||||
}
|
||||
|
||||
r := k.c.newRequest("PUT", "/v1/kv/"+key)
|
||||
r.setWriteOptions(q)
|
||||
for param, val := range params {
|
||||
r.params.Set(param, val)
|
||||
}
|
||||
r.body = bytes.NewReader(body)
|
||||
rtt, resp, err := requireOK(k.c.doRequest(r))
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &WriteMeta{}
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var buf bytes.Buffer
|
||||
if _, err := io.Copy(&buf, resp.Body); err != nil {
|
||||
return false, nil, fmt.Errorf("Failed to read response: %v", err)
|
||||
}
|
||||
res := strings.Contains(string(buf.Bytes()), "true")
|
||||
return res, qm, nil
|
||||
}
|
||||
|
||||
// Delete is used to delete a single key
|
||||
func (k *KV) Delete(key string, w *WriteOptions) (*WriteMeta, error) {
|
||||
_, qm, err := k.deleteInternal(key, nil, w)
|
||||
return qm, err
|
||||
}
|
||||
|
||||
// DeleteCAS is used for a Delete Check-And-Set operation. The Key
|
||||
// and ModifyIndex are respected. Returns true on success or false on failures.
|
||||
func (k *KV) DeleteCAS(p *KVPair, q *WriteOptions) (bool, *WriteMeta, error) {
|
||||
params := map[string]string{
|
||||
"cas": strconv.FormatUint(p.ModifyIndex, 10),
|
||||
}
|
||||
return k.deleteInternal(p.Key, params, q)
|
||||
}
|
||||
|
||||
// DeleteTree is used to delete all keys under a prefix
|
||||
func (k *KV) DeleteTree(prefix string, w *WriteOptions) (*WriteMeta, error) {
|
||||
_, qm, err := k.deleteInternal(prefix, map[string]string{"recurse": ""}, w)
|
||||
return qm, err
|
||||
}
|
||||
|
||||
func (k *KV) deleteInternal(key string, params map[string]string, q *WriteOptions) (bool, *WriteMeta, error) {
|
||||
r := k.c.newRequest("DELETE", "/v1/kv/"+key)
|
||||
r.setWriteOptions(q)
|
||||
for param, val := range params {
|
||||
r.params.Set(param, val)
|
||||
}
|
||||
rtt, resp, err := requireOK(k.c.doRequest(r))
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &WriteMeta{}
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var buf bytes.Buffer
|
||||
if _, err := io.Copy(&buf, resp.Body); err != nil {
|
||||
return false, nil, fmt.Errorf("Failed to read response: %v", err)
|
||||
}
|
||||
res := strings.Contains(string(buf.Bytes()), "true")
|
||||
return res, qm, nil
|
||||
}
|
||||
|
||||
// TxnOp is the internal format we send to Consul. It's not specific to KV,
|
||||
// though currently only KV operations are supported.
|
||||
type TxnOp struct {
|
||||
KV *KVTxnOp
|
||||
}
|
||||
|
||||
// TxnOps is a list of transaction operations.
|
||||
type TxnOps []*TxnOp
|
||||
|
||||
// TxnResult is the internal format we receive from Consul.
|
||||
type TxnResult struct {
|
||||
KV *KVPair
|
||||
}
|
||||
|
||||
// TxnResults is a list of TxnResult objects.
|
||||
type TxnResults []*TxnResult
|
||||
|
||||
// TxnError is used to return information about an operation in a transaction.
|
||||
type TxnError struct {
|
||||
OpIndex int
|
||||
What string
|
||||
}
|
||||
|
||||
// TxnErrors is a list of TxnError objects.
|
||||
type TxnErrors []*TxnError
|
||||
|
||||
// TxnResponse is the internal format we receive from Consul.
|
||||
type TxnResponse struct {
|
||||
Results TxnResults
|
||||
Errors TxnErrors
|
||||
}
|
||||
|
||||
// Txn is used to apply multiple KV operations in a single, atomic transaction.
|
||||
//
|
||||
// Note that Go will perform the required base64 encoding on the values
|
||||
// automatically because the type is a byte slice. Transactions are defined as a
|
||||
// list of operations to perform, using the KVOp constants and KVTxnOp structure
|
||||
// to define operations. If any operation fails, none of the changes are applied
|
||||
// to the state store. Note that this hides the internal raw transaction interface
|
||||
// and munges the input and output types into KV-specific ones for ease of use.
|
||||
// If there are more non-KV operations in the future we may break out a new
|
||||
// transaction API client, but it will be easy to keep this KV-specific variant
|
||||
// supported.
|
||||
//
|
||||
// Even though this is generally a write operation, we take a QueryOptions input
|
||||
// and return a QueryMeta output. If the transaction contains only read ops, then
|
||||
// Consul will fast-path it to a different endpoint internally which supports
|
||||
// consistency controls, but not blocking. If there are write operations then
|
||||
// the request will always be routed through raft and any consistency settings
|
||||
// will be ignored.
|
||||
//
|
||||
// Here's an example:
|
||||
//
|
||||
// ops := KVTxnOps{
|
||||
// &KVTxnOp{
|
||||
// Verb: KVLock,
|
||||
// Key: "test/lock",
|
||||
// Session: "adf4238a-882b-9ddc-4a9d-5b6758e4159e",
|
||||
// Value: []byte("hello"),
|
||||
// },
|
||||
// &KVTxnOp{
|
||||
// Verb: KVGet,
|
||||
// Key: "another/key",
|
||||
// },
|
||||
// }
|
||||
// ok, response, _, err := kv.Txn(&ops, nil)
|
||||
//
|
||||
// If there is a problem making the transaction request then an error will be
|
||||
// returned. Otherwise, the ok value will be true if the transaction succeeded
|
||||
// or false if it was rolled back. The response is a structured return value which
|
||||
// will have the outcome of the transaction. Its Results member will have entries
|
||||
// for each operation. Deleted keys will have a nil entry in the, and to save
|
||||
// space, the Value of each key in the Results will be nil unless the operation
|
||||
// is a KVGet. If the transaction was rolled back, the Errors member will have
|
||||
// entries referencing the index of the operation that failed along with an error
|
||||
// message.
|
||||
func (k *KV) Txn(txn KVTxnOps, q *QueryOptions) (bool, *KVTxnResponse, *QueryMeta, error) {
|
||||
r := k.c.newRequest("PUT", "/v1/txn")
|
||||
r.setQueryOptions(q)
|
||||
|
||||
// Convert into the internal format since this is an all-KV txn.
|
||||
ops := make(TxnOps, 0, len(txn))
|
||||
for _, kvOp := range txn {
|
||||
ops = append(ops, &TxnOp{KV: kvOp})
|
||||
}
|
||||
r.obj = ops
|
||||
rtt, resp, err := k.c.doRequest(r)
|
||||
if err != nil {
|
||||
return false, nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusConflict {
|
||||
var txnResp TxnResponse
|
||||
if err := decodeBody(resp, &txnResp); err != nil {
|
||||
return false, nil, nil, err
|
||||
}
|
||||
|
||||
// Convert from the internal format.
|
||||
kvResp := KVTxnResponse{
|
||||
Errors: txnResp.Errors,
|
||||
}
|
||||
for _, result := range txnResp.Results {
|
||||
kvResp.Results = append(kvResp.Results, result.KV)
|
||||
}
|
||||
return resp.StatusCode == http.StatusOK, &kvResp, qm, nil
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if _, err := io.Copy(&buf, resp.Body); err != nil {
|
||||
return false, nil, nil, fmt.Errorf("Failed to read response: %v", err)
|
||||
}
|
||||
return false, nil, nil, fmt.Errorf("Failed request: %s", buf.String())
|
||||
}
|
380
vendor/github.com/hashicorp/consul/api/lock.go
generated
vendored
Normal file
380
vendor/github.com/hashicorp/consul/api/lock.go
generated
vendored
Normal file
|
@ -0,0 +1,380 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultLockSessionName is the Session Name we assign if none is provided
|
||||
DefaultLockSessionName = "Consul API Lock"
|
||||
|
||||
// DefaultLockSessionTTL is the default session TTL if no Session is provided
|
||||
// when creating a new Lock. This is used because we do not have another
|
||||
// other check to depend upon.
|
||||
DefaultLockSessionTTL = "15s"
|
||||
|
||||
// DefaultLockWaitTime is how long we block for at a time to check if lock
|
||||
// acquisition is possible. This affects the minimum time it takes to cancel
|
||||
// a Lock acquisition.
|
||||
DefaultLockWaitTime = 15 * time.Second
|
||||
|
||||
// DefaultLockRetryTime is how long we wait after a failed lock acquisition
|
||||
// before attempting to do the lock again. This is so that once a lock-delay
|
||||
// is in effect, we do not hot loop retrying the acquisition.
|
||||
DefaultLockRetryTime = 5 * time.Second
|
||||
|
||||
// DefaultMonitorRetryTime is how long we wait after a failed monitor check
|
||||
// of a lock (500 response code). This allows the monitor to ride out brief
|
||||
// periods of unavailability, subject to the MonitorRetries setting in the
|
||||
// lock options which is by default set to 0, disabling this feature. This
|
||||
// affects locks and semaphores.
|
||||
DefaultMonitorRetryTime = 2 * time.Second
|
||||
|
||||
// LockFlagValue is a magic flag we set to indicate a key
|
||||
// is being used for a lock. It is used to detect a potential
|
||||
// conflict with a semaphore.
|
||||
LockFlagValue = 0x2ddccbc058a50c18
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrLockHeld is returned if we attempt to double lock
|
||||
ErrLockHeld = fmt.Errorf("Lock already held")
|
||||
|
||||
// ErrLockNotHeld is returned if we attempt to unlock a lock
|
||||
// that we do not hold.
|
||||
ErrLockNotHeld = fmt.Errorf("Lock not held")
|
||||
|
||||
// ErrLockInUse is returned if we attempt to destroy a lock
|
||||
// that is in use.
|
||||
ErrLockInUse = fmt.Errorf("Lock in use")
|
||||
|
||||
// ErrLockConflict is returned if the flags on a key
|
||||
// used for a lock do not match expectation
|
||||
ErrLockConflict = fmt.Errorf("Existing key does not match lock use")
|
||||
)
|
||||
|
||||
// Lock is used to implement client-side leader election. It is follows the
|
||||
// algorithm as described here: https://www.consul.io/docs/guides/leader-election.html.
|
||||
type Lock struct {
|
||||
c *Client
|
||||
opts *LockOptions
|
||||
|
||||
isHeld bool
|
||||
sessionRenew chan struct{}
|
||||
lockSession string
|
||||
l sync.Mutex
|
||||
}
|
||||
|
||||
// LockOptions is used to parameterize the Lock behavior.
|
||||
type LockOptions struct {
|
||||
Key string // Must be set and have write permissions
|
||||
Value []byte // Optional, value to associate with the lock
|
||||
Session string // Optional, created if not specified
|
||||
SessionName string // Optional, defaults to DefaultLockSessionName
|
||||
SessionTTL string // Optional, defaults to DefaultLockSessionTTL
|
||||
MonitorRetries int // Optional, defaults to 0 which means no retries
|
||||
MonitorRetryTime time.Duration // Optional, defaults to DefaultMonitorRetryTime
|
||||
LockWaitTime time.Duration // Optional, defaults to DefaultLockWaitTime
|
||||
LockTryOnce bool // Optional, defaults to false which means try forever
|
||||
}
|
||||
|
||||
// LockKey returns a handle to a lock struct which can be used
|
||||
// to acquire and release the mutex. The key used must have
|
||||
// write permissions.
|
||||
func (c *Client) LockKey(key string) (*Lock, error) {
|
||||
opts := &LockOptions{
|
||||
Key: key,
|
||||
}
|
||||
return c.LockOpts(opts)
|
||||
}
|
||||
|
||||
// LockOpts returns a handle to a lock struct which can be used
|
||||
// to acquire and release the mutex. The key used must have
|
||||
// write permissions.
|
||||
func (c *Client) LockOpts(opts *LockOptions) (*Lock, error) {
|
||||
if opts.Key == "" {
|
||||
return nil, fmt.Errorf("missing key")
|
||||
}
|
||||
if opts.SessionName == "" {
|
||||
opts.SessionName = DefaultLockSessionName
|
||||
}
|
||||
if opts.SessionTTL == "" {
|
||||
opts.SessionTTL = DefaultLockSessionTTL
|
||||
} else {
|
||||
if _, err := time.ParseDuration(opts.SessionTTL); err != nil {
|
||||
return nil, fmt.Errorf("invalid SessionTTL: %v", err)
|
||||
}
|
||||
}
|
||||
if opts.MonitorRetryTime == 0 {
|
||||
opts.MonitorRetryTime = DefaultMonitorRetryTime
|
||||
}
|
||||
if opts.LockWaitTime == 0 {
|
||||
opts.LockWaitTime = DefaultLockWaitTime
|
||||
}
|
||||
l := &Lock{
|
||||
c: c,
|
||||
opts: opts,
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// Lock attempts to acquire the lock and blocks while doing so.
|
||||
// Providing a non-nil stopCh can be used to abort the lock attempt.
|
||||
// Returns a channel that is closed if our lock is lost or an error.
|
||||
// This channel could be closed at any time due to session invalidation,
|
||||
// communication errors, operator intervention, etc. It is NOT safe to
|
||||
// assume that the lock is held until Unlock() unless the Session is specifically
|
||||
// created without any associated health checks. By default Consul sessions
|
||||
// prefer liveness over safety and an application must be able to handle
|
||||
// the lock being lost.
|
||||
func (l *Lock) Lock(stopCh <-chan struct{}) (<-chan struct{}, error) {
|
||||
// Hold the lock as we try to acquire
|
||||
l.l.Lock()
|
||||
defer l.l.Unlock()
|
||||
|
||||
// Check if we already hold the lock
|
||||
if l.isHeld {
|
||||
return nil, ErrLockHeld
|
||||
}
|
||||
|
||||
// Check if we need to create a session first
|
||||
l.lockSession = l.opts.Session
|
||||
if l.lockSession == "" {
|
||||
if s, err := l.createSession(); err != nil {
|
||||
return nil, fmt.Errorf("failed to create session: %v", err)
|
||||
} else {
|
||||
l.sessionRenew = make(chan struct{})
|
||||
l.lockSession = s
|
||||
session := l.c.Session()
|
||||
go session.RenewPeriodic(l.opts.SessionTTL, s, nil, l.sessionRenew)
|
||||
|
||||
// If we fail to acquire the lock, cleanup the session
|
||||
defer func() {
|
||||
if !l.isHeld {
|
||||
close(l.sessionRenew)
|
||||
l.sessionRenew = nil
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// Setup the query options
|
||||
kv := l.c.KV()
|
||||
qOpts := &QueryOptions{
|
||||
WaitTime: l.opts.LockWaitTime,
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
attempts := 0
|
||||
WAIT:
|
||||
// Check if we should quit
|
||||
select {
|
||||
case <-stopCh:
|
||||
return nil, nil
|
||||
default:
|
||||
}
|
||||
|
||||
// Handle the one-shot mode.
|
||||
if l.opts.LockTryOnce && attempts > 0 {
|
||||
elapsed := time.Now().Sub(start)
|
||||
if elapsed > qOpts.WaitTime {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
qOpts.WaitTime -= elapsed
|
||||
}
|
||||
attempts++
|
||||
|
||||
// Look for an existing lock, blocking until not taken
|
||||
pair, meta, err := kv.Get(l.opts.Key, qOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read lock: %v", err)
|
||||
}
|
||||
if pair != nil && pair.Flags != LockFlagValue {
|
||||
return nil, ErrLockConflict
|
||||
}
|
||||
locked := false
|
||||
if pair != nil && pair.Session == l.lockSession {
|
||||
goto HELD
|
||||
}
|
||||
if pair != nil && pair.Session != "" {
|
||||
qOpts.WaitIndex = meta.LastIndex
|
||||
goto WAIT
|
||||
}
|
||||
|
||||
// Try to acquire the lock
|
||||
pair = l.lockEntry(l.lockSession)
|
||||
locked, _, err = kv.Acquire(pair, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to acquire lock: %v", err)
|
||||
}
|
||||
|
||||
// Handle the case of not getting the lock
|
||||
if !locked {
|
||||
// Determine why the lock failed
|
||||
qOpts.WaitIndex = 0
|
||||
pair, meta, err = kv.Get(l.opts.Key, qOpts)
|
||||
if pair != nil && pair.Session != "" {
|
||||
//If the session is not null, this means that a wait can safely happen
|
||||
//using a long poll
|
||||
qOpts.WaitIndex = meta.LastIndex
|
||||
goto WAIT
|
||||
} else {
|
||||
// If the session is empty and the lock failed to acquire, then it means
|
||||
// a lock-delay is in effect and a timed wait must be used
|
||||
select {
|
||||
case <-time.After(DefaultLockRetryTime):
|
||||
goto WAIT
|
||||
case <-stopCh:
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HELD:
|
||||
// Watch to ensure we maintain leadership
|
||||
leaderCh := make(chan struct{})
|
||||
go l.monitorLock(l.lockSession, leaderCh)
|
||||
|
||||
// Set that we own the lock
|
||||
l.isHeld = true
|
||||
|
||||
// Locked! All done
|
||||
return leaderCh, nil
|
||||
}
|
||||
|
||||
// Unlock released the lock. It is an error to call this
|
||||
// if the lock is not currently held.
|
||||
func (l *Lock) Unlock() error {
|
||||
// Hold the lock as we try to release
|
||||
l.l.Lock()
|
||||
defer l.l.Unlock()
|
||||
|
||||
// Ensure the lock is actually held
|
||||
if !l.isHeld {
|
||||
return ErrLockNotHeld
|
||||
}
|
||||
|
||||
// Set that we no longer own the lock
|
||||
l.isHeld = false
|
||||
|
||||
// Stop the session renew
|
||||
if l.sessionRenew != nil {
|
||||
defer func() {
|
||||
close(l.sessionRenew)
|
||||
l.sessionRenew = nil
|
||||
}()
|
||||
}
|
||||
|
||||
// Get the lock entry, and clear the lock session
|
||||
lockEnt := l.lockEntry(l.lockSession)
|
||||
l.lockSession = ""
|
||||
|
||||
// Release the lock explicitly
|
||||
kv := l.c.KV()
|
||||
_, _, err := kv.Release(lockEnt, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to release lock: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroy is used to cleanup the lock entry. It is not necessary
|
||||
// to invoke. It will fail if the lock is in use.
|
||||
func (l *Lock) Destroy() error {
|
||||
// Hold the lock as we try to release
|
||||
l.l.Lock()
|
||||
defer l.l.Unlock()
|
||||
|
||||
// Check if we already hold the lock
|
||||
if l.isHeld {
|
||||
return ErrLockHeld
|
||||
}
|
||||
|
||||
// Look for an existing lock
|
||||
kv := l.c.KV()
|
||||
pair, _, err := kv.Get(l.opts.Key, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read lock: %v", err)
|
||||
}
|
||||
|
||||
// Nothing to do if the lock does not exist
|
||||
if pair == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check for possible flag conflict
|
||||
if pair.Flags != LockFlagValue {
|
||||
return ErrLockConflict
|
||||
}
|
||||
|
||||
// Check if it is in use
|
||||
if pair.Session != "" {
|
||||
return ErrLockInUse
|
||||
}
|
||||
|
||||
// Attempt the delete
|
||||
didRemove, _, err := kv.DeleteCAS(pair, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to remove lock: %v", err)
|
||||
}
|
||||
if !didRemove {
|
||||
return ErrLockInUse
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// createSession is used to create a new managed session
|
||||
func (l *Lock) createSession() (string, error) {
|
||||
session := l.c.Session()
|
||||
se := &SessionEntry{
|
||||
Name: l.opts.SessionName,
|
||||
TTL: l.opts.SessionTTL,
|
||||
}
|
||||
id, _, err := session.Create(se, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// lockEntry returns a formatted KVPair for the lock
|
||||
func (l *Lock) lockEntry(session string) *KVPair {
|
||||
return &KVPair{
|
||||
Key: l.opts.Key,
|
||||
Value: l.opts.Value,
|
||||
Session: session,
|
||||
Flags: LockFlagValue,
|
||||
}
|
||||
}
|
||||
|
||||
// monitorLock is a long running routine to monitor a lock ownership
|
||||
// It closes the stopCh if we lose our leadership.
|
||||
func (l *Lock) monitorLock(session string, stopCh chan struct{}) {
|
||||
defer close(stopCh)
|
||||
kv := l.c.KV()
|
||||
opts := &QueryOptions{RequireConsistent: true}
|
||||
WAIT:
|
||||
retries := l.opts.MonitorRetries
|
||||
RETRY:
|
||||
pair, meta, err := kv.Get(l.opts.Key, opts)
|
||||
if err != nil {
|
||||
// If configured we can try to ride out a brief Consul unavailability
|
||||
// by doing retries. Note that we have to attempt the retry in a non-
|
||||
// blocking fashion so that we have a clean place to reset the retry
|
||||
// counter if service is restored.
|
||||
if retries > 0 && IsServerError(err) {
|
||||
time.Sleep(l.opts.MonitorRetryTime)
|
||||
retries--
|
||||
opts.WaitIndex = 0
|
||||
goto RETRY
|
||||
}
|
||||
return
|
||||
}
|
||||
if pair != nil && pair.Session == session {
|
||||
opts.WaitIndex = meta.LastIndex
|
||||
goto WAIT
|
||||
}
|
||||
}
|
194
vendor/github.com/hashicorp/consul/api/prepared_query.go
generated
vendored
Normal file
194
vendor/github.com/hashicorp/consul/api/prepared_query.go
generated
vendored
Normal file
|
@ -0,0 +1,194 @@
|
|||
package api
|
||||
|
||||
// QueryDatacenterOptions sets options about how we fail over if there are no
|
||||
// healthy nodes in the local datacenter.
|
||||
type QueryDatacenterOptions struct {
|
||||
// NearestN is set to the number of remote datacenters to try, based on
|
||||
// network coordinates.
|
||||
NearestN int
|
||||
|
||||
// Datacenters is a fixed list of datacenters to try after NearestN. We
|
||||
// never try a datacenter multiple times, so those are subtracted from
|
||||
// this list before proceeding.
|
||||
Datacenters []string
|
||||
}
|
||||
|
||||
// QueryDNSOptions controls settings when query results are served over DNS.
|
||||
type QueryDNSOptions struct {
|
||||
// TTL is the time to live for the served DNS results.
|
||||
TTL string
|
||||
}
|
||||
|
||||
// ServiceQuery is used to query for a set of healthy nodes offering a specific
|
||||
// service.
|
||||
type ServiceQuery struct {
|
||||
// Service is the service to query.
|
||||
Service string
|
||||
|
||||
// Near allows baking in the name of a node to automatically distance-
|
||||
// sort from. The magic "_agent" value is supported, which sorts near
|
||||
// the agent which initiated the request by default.
|
||||
Near string
|
||||
|
||||
// Failover controls what we do if there are no healthy nodes in the
|
||||
// local datacenter.
|
||||
Failover QueryDatacenterOptions
|
||||
|
||||
// If OnlyPassing is true then we will only include nodes with passing
|
||||
// health checks (critical AND warning checks will cause a node to be
|
||||
// discarded)
|
||||
OnlyPassing bool
|
||||
|
||||
// Tags are a set of required and/or disallowed tags. If a tag is in
|
||||
// this list it must be present. If the tag is preceded with "!" then
|
||||
// it is disallowed.
|
||||
Tags []string
|
||||
}
|
||||
|
||||
// QueryTemplate carries the arguments for creating a templated query.
|
||||
type QueryTemplate struct {
|
||||
// Type specifies the type of the query template. Currently only
|
||||
// "name_prefix_match" is supported. This field is required.
|
||||
Type string
|
||||
|
||||
// Regexp allows specifying a regex pattern to match against the name
|
||||
// of the query being executed.
|
||||
Regexp string
|
||||
}
|
||||
|
||||
// PrepatedQueryDefinition defines a complete prepared query.
|
||||
type PreparedQueryDefinition struct {
|
||||
// ID is this UUID-based ID for the query, always generated by Consul.
|
||||
ID string
|
||||
|
||||
// Name is an optional friendly name for the query supplied by the
|
||||
// user. NOTE - if this feature is used then it will reduce the security
|
||||
// of any read ACL associated with this query/service since this name
|
||||
// can be used to locate nodes with supplying any ACL.
|
||||
Name string
|
||||
|
||||
// Session is an optional session to tie this query's lifetime to. If
|
||||
// this is omitted then the query will not expire.
|
||||
Session string
|
||||
|
||||
// Token is the ACL token used when the query was created, and it is
|
||||
// used when a query is subsequently executed. This token, or a token
|
||||
// with management privileges, must be used to change the query later.
|
||||
Token string
|
||||
|
||||
// Service defines a service query (leaving things open for other types
|
||||
// later).
|
||||
Service ServiceQuery
|
||||
|
||||
// DNS has options that control how the results of this query are
|
||||
// served over DNS.
|
||||
DNS QueryDNSOptions
|
||||
|
||||
// Template is used to pass through the arguments for creating a
|
||||
// prepared query with an attached template. If a template is given,
|
||||
// interpolations are possible in other struct fields.
|
||||
Template QueryTemplate
|
||||
}
|
||||
|
||||
// PreparedQueryExecuteResponse has the results of executing a query.
|
||||
type PreparedQueryExecuteResponse struct {
|
||||
// Service is the service that was queried.
|
||||
Service string
|
||||
|
||||
// Nodes has the nodes that were output by the query.
|
||||
Nodes []ServiceEntry
|
||||
|
||||
// DNS has the options for serving these results over DNS.
|
||||
DNS QueryDNSOptions
|
||||
|
||||
// Datacenter is the datacenter that these results came from.
|
||||
Datacenter string
|
||||
|
||||
// Failovers is a count of how many times we had to query a remote
|
||||
// datacenter.
|
||||
Failovers int
|
||||
}
|
||||
|
||||
// PreparedQuery can be used to query the prepared query endpoints.
|
||||
type PreparedQuery struct {
|
||||
c *Client
|
||||
}
|
||||
|
||||
// PreparedQuery returns a handle to the prepared query endpoints.
|
||||
func (c *Client) PreparedQuery() *PreparedQuery {
|
||||
return &PreparedQuery{c}
|
||||
}
|
||||
|
||||
// Create makes a new prepared query. The ID of the new query is returned.
|
||||
func (c *PreparedQuery) Create(query *PreparedQueryDefinition, q *WriteOptions) (string, *WriteMeta, error) {
|
||||
r := c.c.newRequest("POST", "/v1/query")
|
||||
r.setWriteOptions(q)
|
||||
r.obj = query
|
||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
wm := &WriteMeta{}
|
||||
wm.RequestTime = rtt
|
||||
|
||||
var out struct{ ID string }
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return out.ID, wm, nil
|
||||
}
|
||||
|
||||
// Update makes updates to an existing prepared query.
|
||||
func (c *PreparedQuery) Update(query *PreparedQueryDefinition, q *WriteOptions) (*WriteMeta, error) {
|
||||
return c.c.write("/v1/query/"+query.ID, query, nil, q)
|
||||
}
|
||||
|
||||
// List is used to fetch all the prepared queries (always requires a management
|
||||
// token).
|
||||
func (c *PreparedQuery) List(q *QueryOptions) ([]*PreparedQueryDefinition, *QueryMeta, error) {
|
||||
var out []*PreparedQueryDefinition
|
||||
qm, err := c.c.query("/v1/query", &out, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return out, qm, nil
|
||||
}
|
||||
|
||||
// Get is used to fetch a specific prepared query.
|
||||
func (c *PreparedQuery) Get(queryID string, q *QueryOptions) ([]*PreparedQueryDefinition, *QueryMeta, error) {
|
||||
var out []*PreparedQueryDefinition
|
||||
qm, err := c.c.query("/v1/query/"+queryID, &out, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return out, qm, nil
|
||||
}
|
||||
|
||||
// Delete is used to delete a specific prepared query.
|
||||
func (c *PreparedQuery) Delete(queryID string, q *QueryOptions) (*QueryMeta, error) {
|
||||
r := c.c.newRequest("DELETE", "/v1/query/"+queryID)
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
return qm, nil
|
||||
}
|
||||
|
||||
// Execute is used to execute a specific prepared query. You can execute using
|
||||
// a query ID or name.
|
||||
func (c *PreparedQuery) Execute(queryIDOrName string, q *QueryOptions) (*PreparedQueryExecuteResponse, *QueryMeta, error) {
|
||||
var out *PreparedQueryExecuteResponse
|
||||
qm, err := c.c.query("/v1/query/"+queryIDOrName+"/execute", &out, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return out, qm, nil
|
||||
}
|
24
vendor/github.com/hashicorp/consul/api/raw.go
generated
vendored
Normal file
24
vendor/github.com/hashicorp/consul/api/raw.go
generated
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
package api
|
||||
|
||||
// Raw can be used to do raw queries against custom endpoints
|
||||
type Raw struct {
|
||||
c *Client
|
||||
}
|
||||
|
||||
// Raw returns a handle to query endpoints
|
||||
func (c *Client) Raw() *Raw {
|
||||
return &Raw{c}
|
||||
}
|
||||
|
||||
// Query is used to do a GET request against an endpoint
|
||||
// and deserialize the response into an interface using
|
||||
// standard Consul conventions.
|
||||
func (raw *Raw) Query(endpoint string, out interface{}, q *QueryOptions) (*QueryMeta, error) {
|
||||
return raw.c.query(endpoint, out, q)
|
||||
}
|
||||
|
||||
// Write is used to do a PUT request against an endpoint
|
||||
// and serialize/deserialized using the standard Consul conventions.
|
||||
func (raw *Raw) Write(endpoint string, in, out interface{}, q *WriteOptions) (*WriteMeta, error) {
|
||||
return raw.c.write(endpoint, in, out, q)
|
||||
}
|
512
vendor/github.com/hashicorp/consul/api/semaphore.go
generated
vendored
Normal file
512
vendor/github.com/hashicorp/consul/api/semaphore.go
generated
vendored
Normal file
|
@ -0,0 +1,512 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultSemaphoreSessionName is the Session Name we assign if none is provided
|
||||
DefaultSemaphoreSessionName = "Consul API Semaphore"
|
||||
|
||||
// DefaultSemaphoreSessionTTL is the default session TTL if no Session is provided
|
||||
// when creating a new Semaphore. This is used because we do not have another
|
||||
// other check to depend upon.
|
||||
DefaultSemaphoreSessionTTL = "15s"
|
||||
|
||||
// DefaultSemaphoreWaitTime is how long we block for at a time to check if semaphore
|
||||
// acquisition is possible. This affects the minimum time it takes to cancel
|
||||
// a Semaphore acquisition.
|
||||
DefaultSemaphoreWaitTime = 15 * time.Second
|
||||
|
||||
// DefaultSemaphoreKey is the key used within the prefix to
|
||||
// use for coordination between all the contenders.
|
||||
DefaultSemaphoreKey = ".lock"
|
||||
|
||||
// SemaphoreFlagValue is a magic flag we set to indicate a key
|
||||
// is being used for a semaphore. It is used to detect a potential
|
||||
// conflict with a lock.
|
||||
SemaphoreFlagValue = 0xe0f69a2baa414de0
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrSemaphoreHeld is returned if we attempt to double lock
|
||||
ErrSemaphoreHeld = fmt.Errorf("Semaphore already held")
|
||||
|
||||
// ErrSemaphoreNotHeld is returned if we attempt to unlock a semaphore
|
||||
// that we do not hold.
|
||||
ErrSemaphoreNotHeld = fmt.Errorf("Semaphore not held")
|
||||
|
||||
// ErrSemaphoreInUse is returned if we attempt to destroy a semaphore
|
||||
// that is in use.
|
||||
ErrSemaphoreInUse = fmt.Errorf("Semaphore in use")
|
||||
|
||||
// ErrSemaphoreConflict is returned if the flags on a key
|
||||
// used for a semaphore do not match expectation
|
||||
ErrSemaphoreConflict = fmt.Errorf("Existing key does not match semaphore use")
|
||||
)
|
||||
|
||||
// Semaphore is used to implement a distributed semaphore
|
||||
// using the Consul KV primitives.
|
||||
type Semaphore struct {
|
||||
c *Client
|
||||
opts *SemaphoreOptions
|
||||
|
||||
isHeld bool
|
||||
sessionRenew chan struct{}
|
||||
lockSession string
|
||||
l sync.Mutex
|
||||
}
|
||||
|
||||
// SemaphoreOptions is used to parameterize the Semaphore
|
||||
type SemaphoreOptions struct {
|
||||
Prefix string // Must be set and have write permissions
|
||||
Limit int // Must be set, and be positive
|
||||
Value []byte // Optional, value to associate with the contender entry
|
||||
Session string // Optional, created if not specified
|
||||
SessionName string // Optional, defaults to DefaultLockSessionName
|
||||
SessionTTL string // Optional, defaults to DefaultLockSessionTTL
|
||||
MonitorRetries int // Optional, defaults to 0 which means no retries
|
||||
MonitorRetryTime time.Duration // Optional, defaults to DefaultMonitorRetryTime
|
||||
SemaphoreWaitTime time.Duration // Optional, defaults to DefaultSemaphoreWaitTime
|
||||
SemaphoreTryOnce bool // Optional, defaults to false which means try forever
|
||||
}
|
||||
|
||||
// semaphoreLock is written under the DefaultSemaphoreKey and
|
||||
// is used to coordinate between all the contenders.
|
||||
type semaphoreLock struct {
|
||||
// Limit is the integer limit of holders. This is used to
|
||||
// verify that all the holders agree on the value.
|
||||
Limit int
|
||||
|
||||
// Holders is a list of all the semaphore holders.
|
||||
// It maps the session ID to true. It is used as a set effectively.
|
||||
Holders map[string]bool
|
||||
}
|
||||
|
||||
// SemaphorePrefix is used to created a Semaphore which will operate
|
||||
// at the given KV prefix and uses the given limit for the semaphore.
|
||||
// The prefix must have write privileges, and the limit must be agreed
|
||||
// upon by all contenders.
|
||||
func (c *Client) SemaphorePrefix(prefix string, limit int) (*Semaphore, error) {
|
||||
opts := &SemaphoreOptions{
|
||||
Prefix: prefix,
|
||||
Limit: limit,
|
||||
}
|
||||
return c.SemaphoreOpts(opts)
|
||||
}
|
||||
|
||||
// SemaphoreOpts is used to create a Semaphore with the given options.
|
||||
// The prefix must have write privileges, and the limit must be agreed
|
||||
// upon by all contenders. If a Session is not provided, one will be created.
|
||||
func (c *Client) SemaphoreOpts(opts *SemaphoreOptions) (*Semaphore, error) {
|
||||
if opts.Prefix == "" {
|
||||
return nil, fmt.Errorf("missing prefix")
|
||||
}
|
||||
if opts.Limit <= 0 {
|
||||
return nil, fmt.Errorf("semaphore limit must be positive")
|
||||
}
|
||||
if opts.SessionName == "" {
|
||||
opts.SessionName = DefaultSemaphoreSessionName
|
||||
}
|
||||
if opts.SessionTTL == "" {
|
||||
opts.SessionTTL = DefaultSemaphoreSessionTTL
|
||||
} else {
|
||||
if _, err := time.ParseDuration(opts.SessionTTL); err != nil {
|
||||
return nil, fmt.Errorf("invalid SessionTTL: %v", err)
|
||||
}
|
||||
}
|
||||
if opts.MonitorRetryTime == 0 {
|
||||
opts.MonitorRetryTime = DefaultMonitorRetryTime
|
||||
}
|
||||
if opts.SemaphoreWaitTime == 0 {
|
||||
opts.SemaphoreWaitTime = DefaultSemaphoreWaitTime
|
||||
}
|
||||
s := &Semaphore{
|
||||
c: c,
|
||||
opts: opts,
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Acquire attempts to reserve a slot in the semaphore, blocking until
|
||||
// success, interrupted via the stopCh or an error is encountered.
|
||||
// Providing a non-nil stopCh can be used to abort the attempt.
|
||||
// On success, a channel is returned that represents our slot.
|
||||
// This channel could be closed at any time due to session invalidation,
|
||||
// communication errors, operator intervention, etc. It is NOT safe to
|
||||
// assume that the slot is held until Release() unless the Session is specifically
|
||||
// created without any associated health checks. By default Consul sessions
|
||||
// prefer liveness over safety and an application must be able to handle
|
||||
// the session being lost.
|
||||
func (s *Semaphore) Acquire(stopCh <-chan struct{}) (<-chan struct{}, error) {
|
||||
// Hold the lock as we try to acquire
|
||||
s.l.Lock()
|
||||
defer s.l.Unlock()
|
||||
|
||||
// Check if we already hold the semaphore
|
||||
if s.isHeld {
|
||||
return nil, ErrSemaphoreHeld
|
||||
}
|
||||
|
||||
// Check if we need to create a session first
|
||||
s.lockSession = s.opts.Session
|
||||
if s.lockSession == "" {
|
||||
if sess, err := s.createSession(); err != nil {
|
||||
return nil, fmt.Errorf("failed to create session: %v", err)
|
||||
} else {
|
||||
s.sessionRenew = make(chan struct{})
|
||||
s.lockSession = sess
|
||||
session := s.c.Session()
|
||||
go session.RenewPeriodic(s.opts.SessionTTL, sess, nil, s.sessionRenew)
|
||||
|
||||
// If we fail to acquire the lock, cleanup the session
|
||||
defer func() {
|
||||
if !s.isHeld {
|
||||
close(s.sessionRenew)
|
||||
s.sessionRenew = nil
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// Create the contender entry
|
||||
kv := s.c.KV()
|
||||
made, _, err := kv.Acquire(s.contenderEntry(s.lockSession), nil)
|
||||
if err != nil || !made {
|
||||
return nil, fmt.Errorf("failed to make contender entry: %v", err)
|
||||
}
|
||||
|
||||
// Setup the query options
|
||||
qOpts := &QueryOptions{
|
||||
WaitTime: s.opts.SemaphoreWaitTime,
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
attempts := 0
|
||||
WAIT:
|
||||
// Check if we should quit
|
||||
select {
|
||||
case <-stopCh:
|
||||
return nil, nil
|
||||
default:
|
||||
}
|
||||
|
||||
// Handle the one-shot mode.
|
||||
if s.opts.SemaphoreTryOnce && attempts > 0 {
|
||||
elapsed := time.Now().Sub(start)
|
||||
if elapsed > qOpts.WaitTime {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
qOpts.WaitTime -= elapsed
|
||||
}
|
||||
attempts++
|
||||
|
||||
// Read the prefix
|
||||
pairs, meta, err := kv.List(s.opts.Prefix, qOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read prefix: %v", err)
|
||||
}
|
||||
|
||||
// Decode the lock
|
||||
lockPair := s.findLock(pairs)
|
||||
if lockPair.Flags != SemaphoreFlagValue {
|
||||
return nil, ErrSemaphoreConflict
|
||||
}
|
||||
lock, err := s.decodeLock(lockPair)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Verify we agree with the limit
|
||||
if lock.Limit != s.opts.Limit {
|
||||
return nil, fmt.Errorf("semaphore limit conflict (lock: %d, local: %d)",
|
||||
lock.Limit, s.opts.Limit)
|
||||
}
|
||||
|
||||
// Prune the dead holders
|
||||
s.pruneDeadHolders(lock, pairs)
|
||||
|
||||
// Check if the lock is held
|
||||
if len(lock.Holders) >= lock.Limit {
|
||||
qOpts.WaitIndex = meta.LastIndex
|
||||
goto WAIT
|
||||
}
|
||||
|
||||
// Create a new lock with us as a holder
|
||||
lock.Holders[s.lockSession] = true
|
||||
newLock, err := s.encodeLock(lock, lockPair.ModifyIndex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Attempt the acquisition
|
||||
didSet, _, err := kv.CAS(newLock, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to update lock: %v", err)
|
||||
}
|
||||
if !didSet {
|
||||
// Update failed, could have been a race with another contender,
|
||||
// retry the operation
|
||||
goto WAIT
|
||||
}
|
||||
|
||||
// Watch to ensure we maintain ownership of the slot
|
||||
lockCh := make(chan struct{})
|
||||
go s.monitorLock(s.lockSession, lockCh)
|
||||
|
||||
// Set that we own the lock
|
||||
s.isHeld = true
|
||||
|
||||
// Acquired! All done
|
||||
return lockCh, nil
|
||||
}
|
||||
|
||||
// Release is used to voluntarily give up our semaphore slot. It is
|
||||
// an error to call this if the semaphore has not been acquired.
|
||||
func (s *Semaphore) Release() error {
|
||||
// Hold the lock as we try to release
|
||||
s.l.Lock()
|
||||
defer s.l.Unlock()
|
||||
|
||||
// Ensure the lock is actually held
|
||||
if !s.isHeld {
|
||||
return ErrSemaphoreNotHeld
|
||||
}
|
||||
|
||||
// Set that we no longer own the lock
|
||||
s.isHeld = false
|
||||
|
||||
// Stop the session renew
|
||||
if s.sessionRenew != nil {
|
||||
defer func() {
|
||||
close(s.sessionRenew)
|
||||
s.sessionRenew = nil
|
||||
}()
|
||||
}
|
||||
|
||||
// Get and clear the lock session
|
||||
lockSession := s.lockSession
|
||||
s.lockSession = ""
|
||||
|
||||
// Remove ourselves as a lock holder
|
||||
kv := s.c.KV()
|
||||
key := path.Join(s.opts.Prefix, DefaultSemaphoreKey)
|
||||
READ:
|
||||
pair, _, err := kv.Get(key, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pair == nil {
|
||||
pair = &KVPair{}
|
||||
}
|
||||
lock, err := s.decodeLock(pair)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create a new lock without us as a holder
|
||||
if _, ok := lock.Holders[lockSession]; ok {
|
||||
delete(lock.Holders, lockSession)
|
||||
newLock, err := s.encodeLock(lock, pair.ModifyIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Swap the locks
|
||||
didSet, _, err := kv.CAS(newLock, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update lock: %v", err)
|
||||
}
|
||||
if !didSet {
|
||||
goto READ
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy the contender entry
|
||||
contenderKey := path.Join(s.opts.Prefix, lockSession)
|
||||
if _, err := kv.Delete(contenderKey, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroy is used to cleanup the semaphore entry. It is not necessary
|
||||
// to invoke. It will fail if the semaphore is in use.
|
||||
func (s *Semaphore) Destroy() error {
|
||||
// Hold the lock as we try to acquire
|
||||
s.l.Lock()
|
||||
defer s.l.Unlock()
|
||||
|
||||
// Check if we already hold the semaphore
|
||||
if s.isHeld {
|
||||
return ErrSemaphoreHeld
|
||||
}
|
||||
|
||||
// List for the semaphore
|
||||
kv := s.c.KV()
|
||||
pairs, _, err := kv.List(s.opts.Prefix, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read prefix: %v", err)
|
||||
}
|
||||
|
||||
// Find the lock pair, bail if it doesn't exist
|
||||
lockPair := s.findLock(pairs)
|
||||
if lockPair.ModifyIndex == 0 {
|
||||
return nil
|
||||
}
|
||||
if lockPair.Flags != SemaphoreFlagValue {
|
||||
return ErrSemaphoreConflict
|
||||
}
|
||||
|
||||
// Decode the lock
|
||||
lock, err := s.decodeLock(lockPair)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Prune the dead holders
|
||||
s.pruneDeadHolders(lock, pairs)
|
||||
|
||||
// Check if there are any holders
|
||||
if len(lock.Holders) > 0 {
|
||||
return ErrSemaphoreInUse
|
||||
}
|
||||
|
||||
// Attempt the delete
|
||||
didRemove, _, err := kv.DeleteCAS(lockPair, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to remove semaphore: %v", err)
|
||||
}
|
||||
if !didRemove {
|
||||
return ErrSemaphoreInUse
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// createSession is used to create a new managed session
|
||||
func (s *Semaphore) createSession() (string, error) {
|
||||
session := s.c.Session()
|
||||
se := &SessionEntry{
|
||||
Name: s.opts.SessionName,
|
||||
TTL: s.opts.SessionTTL,
|
||||
Behavior: SessionBehaviorDelete,
|
||||
}
|
||||
id, _, err := session.Create(se, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// contenderEntry returns a formatted KVPair for the contender
|
||||
func (s *Semaphore) contenderEntry(session string) *KVPair {
|
||||
return &KVPair{
|
||||
Key: path.Join(s.opts.Prefix, session),
|
||||
Value: s.opts.Value,
|
||||
Session: session,
|
||||
Flags: SemaphoreFlagValue,
|
||||
}
|
||||
}
|
||||
|
||||
// findLock is used to find the KV Pair which is used for coordination
|
||||
func (s *Semaphore) findLock(pairs KVPairs) *KVPair {
|
||||
key := path.Join(s.opts.Prefix, DefaultSemaphoreKey)
|
||||
for _, pair := range pairs {
|
||||
if pair.Key == key {
|
||||
return pair
|
||||
}
|
||||
}
|
||||
return &KVPair{Flags: SemaphoreFlagValue}
|
||||
}
|
||||
|
||||
// decodeLock is used to decode a semaphoreLock from an
|
||||
// entry in Consul
|
||||
func (s *Semaphore) decodeLock(pair *KVPair) (*semaphoreLock, error) {
|
||||
// Handle if there is no lock
|
||||
if pair == nil || pair.Value == nil {
|
||||
return &semaphoreLock{
|
||||
Limit: s.opts.Limit,
|
||||
Holders: make(map[string]bool),
|
||||
}, nil
|
||||
}
|
||||
|
||||
l := &semaphoreLock{}
|
||||
if err := json.Unmarshal(pair.Value, l); err != nil {
|
||||
return nil, fmt.Errorf("lock decoding failed: %v", err)
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// encodeLock is used to encode a semaphoreLock into a KVPair
|
||||
// that can be PUT
|
||||
func (s *Semaphore) encodeLock(l *semaphoreLock, oldIndex uint64) (*KVPair, error) {
|
||||
enc, err := json.Marshal(l)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("lock encoding failed: %v", err)
|
||||
}
|
||||
pair := &KVPair{
|
||||
Key: path.Join(s.opts.Prefix, DefaultSemaphoreKey),
|
||||
Value: enc,
|
||||
Flags: SemaphoreFlagValue,
|
||||
ModifyIndex: oldIndex,
|
||||
}
|
||||
return pair, nil
|
||||
}
|
||||
|
||||
// pruneDeadHolders is used to remove all the dead lock holders
|
||||
func (s *Semaphore) pruneDeadHolders(lock *semaphoreLock, pairs KVPairs) {
|
||||
// Gather all the live holders
|
||||
alive := make(map[string]struct{}, len(pairs))
|
||||
for _, pair := range pairs {
|
||||
if pair.Session != "" {
|
||||
alive[pair.Session] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any holders that are dead
|
||||
for holder := range lock.Holders {
|
||||
if _, ok := alive[holder]; !ok {
|
||||
delete(lock.Holders, holder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// monitorLock is a long running routine to monitor a semaphore ownership
|
||||
// It closes the stopCh if we lose our slot.
|
||||
func (s *Semaphore) monitorLock(session string, stopCh chan struct{}) {
|
||||
defer close(stopCh)
|
||||
kv := s.c.KV()
|
||||
opts := &QueryOptions{RequireConsistent: true}
|
||||
WAIT:
|
||||
retries := s.opts.MonitorRetries
|
||||
RETRY:
|
||||
pairs, meta, err := kv.List(s.opts.Prefix, opts)
|
||||
if err != nil {
|
||||
// If configured we can try to ride out a brief Consul unavailability
|
||||
// by doing retries. Note that we have to attempt the retry in a non-
|
||||
// blocking fashion so that we have a clean place to reset the retry
|
||||
// counter if service is restored.
|
||||
if retries > 0 && IsServerError(err) {
|
||||
time.Sleep(s.opts.MonitorRetryTime)
|
||||
retries--
|
||||
opts.WaitIndex = 0
|
||||
goto RETRY
|
||||
}
|
||||
return
|
||||
}
|
||||
lockPair := s.findLock(pairs)
|
||||
lock, err := s.decodeLock(lockPair)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.pruneDeadHolders(lock, pairs)
|
||||
if _, ok := lock.Holders[session]; ok {
|
||||
opts.WaitIndex = meta.LastIndex
|
||||
goto WAIT
|
||||
}
|
||||
}
|
217
vendor/github.com/hashicorp/consul/api/session.go
generated
vendored
Normal file
217
vendor/github.com/hashicorp/consul/api/session.go
generated
vendored
Normal file
|
@ -0,0 +1,217 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// SessionBehaviorRelease is the default behavior and causes
|
||||
// all associated locks to be released on session invalidation.
|
||||
SessionBehaviorRelease = "release"
|
||||
|
||||
// SessionBehaviorDelete is new in Consul 0.5 and changes the
|
||||
// behavior to delete all associated locks on session invalidation.
|
||||
// It can be used in a way similar to Ephemeral Nodes in ZooKeeper.
|
||||
SessionBehaviorDelete = "delete"
|
||||
)
|
||||
|
||||
var ErrSessionExpired = errors.New("session expired")
|
||||
|
||||
// SessionEntry represents a session in consul
|
||||
type SessionEntry struct {
|
||||
CreateIndex uint64
|
||||
ID string
|
||||
Name string
|
||||
Node string
|
||||
Checks []string
|
||||
LockDelay time.Duration
|
||||
Behavior string
|
||||
TTL string
|
||||
}
|
||||
|
||||
// Session can be used to query the Session endpoints
|
||||
type Session struct {
|
||||
c *Client
|
||||
}
|
||||
|
||||
// Session returns a handle to the session endpoints
|
||||
func (c *Client) Session() *Session {
|
||||
return &Session{c}
|
||||
}
|
||||
|
||||
// CreateNoChecks is like Create but is used specifically to create
|
||||
// a session with no associated health checks.
|
||||
func (s *Session) CreateNoChecks(se *SessionEntry, q *WriteOptions) (string, *WriteMeta, error) {
|
||||
body := make(map[string]interface{})
|
||||
body["Checks"] = []string{}
|
||||
if se != nil {
|
||||
if se.Name != "" {
|
||||
body["Name"] = se.Name
|
||||
}
|
||||
if se.Node != "" {
|
||||
body["Node"] = se.Node
|
||||
}
|
||||
if se.LockDelay != 0 {
|
||||
body["LockDelay"] = durToMsec(se.LockDelay)
|
||||
}
|
||||
if se.Behavior != "" {
|
||||
body["Behavior"] = se.Behavior
|
||||
}
|
||||
if se.TTL != "" {
|
||||
body["TTL"] = se.TTL
|
||||
}
|
||||
}
|
||||
return s.create(body, q)
|
||||
|
||||
}
|
||||
|
||||
// Create makes a new session. Providing a session entry can
|
||||
// customize the session. It can also be nil to use defaults.
|
||||
func (s *Session) Create(se *SessionEntry, q *WriteOptions) (string, *WriteMeta, error) {
|
||||
var obj interface{}
|
||||
if se != nil {
|
||||
body := make(map[string]interface{})
|
||||
obj = body
|
||||
if se.Name != "" {
|
||||
body["Name"] = se.Name
|
||||
}
|
||||
if se.Node != "" {
|
||||
body["Node"] = se.Node
|
||||
}
|
||||
if se.LockDelay != 0 {
|
||||
body["LockDelay"] = durToMsec(se.LockDelay)
|
||||
}
|
||||
if len(se.Checks) > 0 {
|
||||
body["Checks"] = se.Checks
|
||||
}
|
||||
if se.Behavior != "" {
|
||||
body["Behavior"] = se.Behavior
|
||||
}
|
||||
if se.TTL != "" {
|
||||
body["TTL"] = se.TTL
|
||||
}
|
||||
}
|
||||
return s.create(obj, q)
|
||||
}
|
||||
|
||||
func (s *Session) create(obj interface{}, q *WriteOptions) (string, *WriteMeta, error) {
|
||||
var out struct{ ID string }
|
||||
wm, err := s.c.write("/v1/session/create", obj, &out, q)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return out.ID, wm, nil
|
||||
}
|
||||
|
||||
// Destroy invalidates a given session
|
||||
func (s *Session) Destroy(id string, q *WriteOptions) (*WriteMeta, error) {
|
||||
wm, err := s.c.write("/v1/session/destroy/"+id, nil, nil, q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wm, nil
|
||||
}
|
||||
|
||||
// Renew renews the TTL on a given session
|
||||
func (s *Session) Renew(id string, q *WriteOptions) (*SessionEntry, *WriteMeta, error) {
|
||||
r := s.c.newRequest("PUT", "/v1/session/renew/"+id)
|
||||
r.setWriteOptions(q)
|
||||
rtt, resp, err := s.c.doRequest(r)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
wm := &WriteMeta{RequestTime: rtt}
|
||||
|
||||
if resp.StatusCode == 404 {
|
||||
return nil, wm, nil
|
||||
} else if resp.StatusCode != 200 {
|
||||
return nil, nil, fmt.Errorf("Unexpected response code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var entries []*SessionEntry
|
||||
if err := decodeBody(resp, &entries); err != nil {
|
||||
return nil, nil, fmt.Errorf("Failed to read response: %v", err)
|
||||
}
|
||||
if len(entries) > 0 {
|
||||
return entries[0], wm, nil
|
||||
}
|
||||
return nil, wm, nil
|
||||
}
|
||||
|
||||
// RenewPeriodic is used to periodically invoke Session.Renew on a
|
||||
// session until a doneCh is closed. This is meant to be used in a long running
|
||||
// goroutine to ensure a session stays valid.
|
||||
func (s *Session) RenewPeriodic(initialTTL string, id string, q *WriteOptions, doneCh chan struct{}) error {
|
||||
ttl, err := time.ParseDuration(initialTTL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
waitDur := ttl / 2
|
||||
lastRenewTime := time.Now()
|
||||
var lastErr error
|
||||
for {
|
||||
if time.Since(lastRenewTime) > ttl {
|
||||
return lastErr
|
||||
}
|
||||
select {
|
||||
case <-time.After(waitDur):
|
||||
entry, _, err := s.Renew(id, q)
|
||||
if err != nil {
|
||||
waitDur = time.Second
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
if entry == nil {
|
||||
return ErrSessionExpired
|
||||
}
|
||||
|
||||
// Handle the server updating the TTL
|
||||
ttl, _ = time.ParseDuration(entry.TTL)
|
||||
waitDur = ttl / 2
|
||||
lastRenewTime = time.Now()
|
||||
|
||||
case <-doneCh:
|
||||
// Attempt a session destroy
|
||||
s.Destroy(id, q)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Info looks up a single session
|
||||
func (s *Session) Info(id string, q *QueryOptions) (*SessionEntry, *QueryMeta, error) {
|
||||
var entries []*SessionEntry
|
||||
qm, err := s.c.query("/v1/session/info/"+id, &entries, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(entries) > 0 {
|
||||
return entries[0], qm, nil
|
||||
}
|
||||
return nil, qm, nil
|
||||
}
|
||||
|
||||
// List gets sessions for a node
|
||||
func (s *Session) Node(node string, q *QueryOptions) ([]*SessionEntry, *QueryMeta, error) {
|
||||
var entries []*SessionEntry
|
||||
qm, err := s.c.query("/v1/session/node/"+node, &entries, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return entries, qm, nil
|
||||
}
|
||||
|
||||
// List gets all active sessions
|
||||
func (s *Session) List(q *QueryOptions) ([]*SessionEntry, *QueryMeta, error) {
|
||||
var entries []*SessionEntry
|
||||
qm, err := s.c.query("/v1/session/list", &entries, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return entries, qm, nil
|
||||
}
|
43
vendor/github.com/hashicorp/consul/api/status.go
generated
vendored
Normal file
43
vendor/github.com/hashicorp/consul/api/status.go
generated
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
package api
|
||||
|
||||
// Status can be used to query the Status endpoints
|
||||
type Status struct {
|
||||
c *Client
|
||||
}
|
||||
|
||||
// Status returns a handle to the status endpoints
|
||||
func (c *Client) Status() *Status {
|
||||
return &Status{c}
|
||||
}
|
||||
|
||||
// Leader is used to query for a known leader
|
||||
func (s *Status) Leader() (string, error) {
|
||||
r := s.c.newRequest("GET", "/v1/status/leader")
|
||||
_, resp, err := requireOK(s.c.doRequest(r))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var leader string
|
||||
if err := decodeBody(resp, &leader); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return leader, nil
|
||||
}
|
||||
|
||||
// Peers is used to query for a known raft peers
|
||||
func (s *Status) Peers() ([]string, error) {
|
||||
r := s.c.newRequest("GET", "/v1/status/peers")
|
||||
_, resp, err := requireOK(s.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var peers []string
|
||||
if err := decodeBody(resp, &peers); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return peers, nil
|
||||
}
|
156
vendor/github.com/hashicorp/consul/commands.go
generated
vendored
Normal file
156
vendor/github.com/hashicorp/consul/commands.go
generated
vendored
Normal file
|
@ -0,0 +1,156 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/hashicorp/consul/command"
|
||||
"github.com/hashicorp/consul/command/agent"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
// Commands is the mapping of all the available Consul commands.
|
||||
var Commands map[string]cli.CommandFactory
|
||||
|
||||
func init() {
|
||||
ui := &cli.BasicUi{Writer: os.Stdout}
|
||||
|
||||
Commands = map[string]cli.CommandFactory{
|
||||
"agent": func() (cli.Command, error) {
|
||||
return &agent.Command{
|
||||
Revision: GitCommit,
|
||||
Version: Version,
|
||||
VersionPrerelease: VersionPrerelease,
|
||||
HumanVersion: GetHumanVersion(),
|
||||
Ui: ui,
|
||||
ShutdownCh: make(chan struct{}),
|
||||
}, nil
|
||||
},
|
||||
|
||||
"configtest": func() (cli.Command, error) {
|
||||
return &command.ConfigTestCommand{
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"event": func() (cli.Command, error) {
|
||||
return &command.EventCommand{
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"exec": func() (cli.Command, error) {
|
||||
return &command.ExecCommand{
|
||||
ShutdownCh: makeShutdownCh(),
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"force-leave": func() (cli.Command, error) {
|
||||
return &command.ForceLeaveCommand{
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"join": func() (cli.Command, error) {
|
||||
return &command.JoinCommand{
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"keygen": func() (cli.Command, error) {
|
||||
return &command.KeygenCommand{
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"keyring": func() (cli.Command, error) {
|
||||
return &command.KeyringCommand{
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"leave": func() (cli.Command, error) {
|
||||
return &command.LeaveCommand{
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"lock": func() (cli.Command, error) {
|
||||
return &command.LockCommand{
|
||||
ShutdownCh: makeShutdownCh(),
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"maint": func() (cli.Command, error) {
|
||||
return &command.MaintCommand{
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"members": func() (cli.Command, error) {
|
||||
return &command.MembersCommand{
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"monitor": func() (cli.Command, error) {
|
||||
return &command.MonitorCommand{
|
||||
ShutdownCh: makeShutdownCh(),
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"info": func() (cli.Command, error) {
|
||||
return &command.InfoCommand{
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"reload": func() (cli.Command, error) {
|
||||
return &command.ReloadCommand{
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"rtt": func() (cli.Command, error) {
|
||||
return &command.RTTCommand{
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"version": func() (cli.Command, error) {
|
||||
return &command.VersionCommand{
|
||||
HumanVersion: GetHumanVersion(),
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"watch": func() (cli.Command, error) {
|
||||
return &command.WatchCommand{
|
||||
ShutdownCh: makeShutdownCh(),
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// makeShutdownCh returns a channel that can be used for shutdown
|
||||
// notifications for commands. This channel will send a message for every
|
||||
// interrupt or SIGTERM received.
|
||||
func makeShutdownCh() <-chan struct{} {
|
||||
resultCh := make(chan struct{})
|
||||
|
||||
signalCh := make(chan os.Signal, 4)
|
||||
signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM)
|
||||
go func() {
|
||||
for {
|
||||
<-signalCh
|
||||
resultCh <- struct{}{}
|
||||
}
|
||||
}()
|
||||
|
||||
return resultCh
|
||||
}
|
53
vendor/github.com/hashicorp/consul/main.go
generated
vendored
Normal file
53
vendor/github.com/hashicorp/consul/main.go
generated
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/cli"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/consul/lib"
|
||||
)
|
||||
|
||||
func init() {
|
||||
lib.SeedMathRand()
|
||||
}
|
||||
|
||||
func main() {
|
||||
os.Exit(realMain())
|
||||
}
|
||||
|
||||
func realMain() int {
|
||||
log.SetOutput(ioutil.Discard)
|
||||
|
||||
// Get the command line args. We shortcut "--version" and "-v" to
|
||||
// just show the version.
|
||||
args := os.Args[1:]
|
||||
for _, arg := range args {
|
||||
if arg == "--" {
|
||||
break
|
||||
}
|
||||
if arg == "-v" || arg == "--version" {
|
||||
newArgs := make([]string, len(args)+1)
|
||||
newArgs[0] = "version"
|
||||
copy(newArgs[1:], args)
|
||||
args = newArgs
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
cli := &cli.CLI{
|
||||
Args: args,
|
||||
Commands: Commands,
|
||||
HelpFunc: cli.BasicHelpFunc("consul"),
|
||||
}
|
||||
|
||||
exitCode, err := cli.Run()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
return exitCode
|
||||
}
|
43
vendor/github.com/hashicorp/consul/version.go
generated
vendored
Normal file
43
vendor/github.com/hashicorp/consul/version.go
generated
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// The git commit that was compiled. This will be filled in by the compiler.
|
||||
var (
|
||||
GitCommit string
|
||||
GitDescribe string
|
||||
)
|
||||
|
||||
// The main version number that is being run at the moment.
|
||||
const Version = "0.7.0"
|
||||
|
||||
// A pre-release marker for the version. If this is "" (empty string)
|
||||
// then it means that it is a final release. Otherwise, this is a pre-release
|
||||
// such as "dev" (in development), "beta", "rc1", etc.
|
||||
const VersionPrerelease = "dev"
|
||||
|
||||
// GetHumanVersion composes the parts of the version in a way that's suitable
|
||||
// for displaying to humans.
|
||||
func GetHumanVersion() string {
|
||||
version := Version
|
||||
if GitDescribe != "" {
|
||||
version = GitDescribe
|
||||
}
|
||||
|
||||
release := VersionPrerelease
|
||||
if GitDescribe == "" && release == "" {
|
||||
release = "dev"
|
||||
}
|
||||
if release != "" {
|
||||
version += fmt.Sprintf("-%s", release)
|
||||
if GitCommit != "" {
|
||||
version += fmt.Sprintf(" (%s)", GitCommit)
|
||||
}
|
||||
}
|
||||
|
||||
// Strip off any single quotes added by the git information.
|
||||
return strings.Replace(version, "'", "", -1)
|
||||
}
|
363
vendor/github.com/hashicorp/go-cleanhttp/LICENSE
generated
vendored
Normal file
363
vendor/github.com/hashicorp/go-cleanhttp/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,363 @@
|
|||
Mozilla Public License, version 2.0
|
||||
|
||||
1. Definitions
|
||||
|
||||
1.1. "Contributor"
|
||||
|
||||
means each individual or legal entity that creates, contributes to the
|
||||
creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
|
||||
means the combination of the Contributions of others (if any) used by a
|
||||
Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
|
||||
means Source Code Form to which the initial Contributor has attached the
|
||||
notice in Exhibit A, the Executable Form of such Source Code Form, and
|
||||
Modifications of such Source Code Form, in each case including portions
|
||||
thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
a. that the initial Contributor has attached the notice described in
|
||||
Exhibit B to the Covered Software; or
|
||||
|
||||
b. that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the terms of
|
||||
a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
|
||||
means a work that combines Covered Software with other material, in a
|
||||
separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
|
||||
means having the right to grant, to the maximum extent possible, whether
|
||||
at the time of the initial grant or subsequently, any and all of the
|
||||
rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
|
||||
means any of the following:
|
||||
|
||||
a. any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered Software; or
|
||||
|
||||
b. any new file in Source Code Form that contains any Covered Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the License,
|
||||
by the making, using, selling, offering for sale, having made, import,
|
||||
or transfer of either its Contributions or its Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
|
||||
means either the GNU General Public License, Version 2.0, the GNU Lesser
|
||||
General Public License, Version 2.1, the GNU Affero General Public
|
||||
License, Version 3.0, or any later versions of those licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that controls, is
|
||||
controlled by, or is under common control with You. For purposes of this
|
||||
definition, "control" means (a) the power, direct or indirect, to cause
|
||||
the direction or management of such entity, whether by contract or
|
||||
otherwise, or (b) ownership of more than fifty percent (50%) of the
|
||||
outstanding shares or beneficial ownership of such entity.
|
||||
|
||||
|
||||
2. License Grants and Conditions
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
a. under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
b. under Patent Claims of such Contributor to make, use, sell, offer for
|
||||
sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
a. for any code that a Contributor has removed from Covered Software; or
|
||||
|
||||
b. for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
c. under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights to
|
||||
grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
|
||||
Section 2.1.
|
||||
|
||||
|
||||
3. Responsibilities
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
a. such Covered Software must also be made available in Source Code Form,
|
||||
as described in Section 3.1, and You must inform recipients of the
|
||||
Executable Form how they can obtain a copy of such Source Code Form by
|
||||
reasonable means in a timely manner, at a charge no more than the cost
|
||||
of distribution to the recipient; and
|
||||
|
||||
b. You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter the
|
||||
recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty, or
|
||||
limitations of liability) contained within the Source Code Form of the
|
||||
Covered Software, except that You may alter any license notices to the
|
||||
extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this License
|
||||
with respect to some or all of the Covered Software due to statute,
|
||||
judicial order, or regulation then You must: (a) comply with the terms of
|
||||
this License to the maximum extent possible; and (b) describe the
|
||||
limitations and the code they affect. Such description must be placed in a
|
||||
text file included with all distributions of the Covered Software under
|
||||
this License. Except to the extent prohibited by statute or regulation,
|
||||
such description must be sufficiently detailed for a recipient of ordinary
|
||||
skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically if You
|
||||
fail to comply with any of its terms. However, if You become compliant,
|
||||
then the rights granted under this License from a particular Contributor
|
||||
are reinstated (a) provisionally, unless and until such Contributor
|
||||
explicitly and finally terminates Your grants, and (b) on an ongoing
|
||||
basis, if such Contributor fails to notify You of the non-compliance by
|
||||
some reasonable means prior to 60 days after You have come back into
|
||||
compliance. Moreover, Your grants from a particular Contributor are
|
||||
reinstated on an ongoing basis if such Contributor notifies You of the
|
||||
non-compliance by some reasonable means, this is the first time You have
|
||||
received notice of non-compliance with this License from such
|
||||
Contributor, and You become compliant prior to 30 days after Your receipt
|
||||
of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
|
||||
license agreements (excluding distributors and resellers) which have been
|
||||
validly granted by You or Your distributors under this License prior to
|
||||
termination shall survive termination.
|
||||
|
||||
6. Disclaimer of Warranty
|
||||
|
||||
Covered Software is provided under this License on an "as is" basis,
|
||||
without warranty of any kind, either expressed, implied, or statutory,
|
||||
including, without limitation, warranties that the Covered Software is free
|
||||
of defects, merchantable, fit for a particular purpose or non-infringing.
|
||||
The entire risk as to the quality and performance of the Covered Software
|
||||
is with You. Should any Covered Software prove defective in any respect,
|
||||
You (not any Contributor) assume the cost of any necessary servicing,
|
||||
repair, or correction. This disclaimer of warranty constitutes an essential
|
||||
part of this License. No use of any Covered Software is authorized under
|
||||
this License except under this disclaimer.
|
||||
|
||||
7. Limitation of Liability
|
||||
|
||||
Under no circumstances and under no legal theory, whether tort (including
|
||||
negligence), contract, or otherwise, shall any Contributor, or anyone who
|
||||
distributes Covered Software as permitted above, be liable to You for any
|
||||
direct, indirect, special, incidental, or consequential damages of any
|
||||
character including, without limitation, damages for lost profits, loss of
|
||||
goodwill, work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses, even if such party shall have been
|
||||
informed of the possibility of such damages. This limitation of liability
|
||||
shall not apply to liability for death or personal injury resulting from
|
||||
such party's negligence to the extent applicable law prohibits such
|
||||
limitation. Some jurisdictions do not allow the exclusion or limitation of
|
||||
incidental or consequential damages, so this exclusion and limitation may
|
||||
not apply to You.
|
||||
|
||||
8. Litigation
|
||||
|
||||
Any litigation relating to this License may be brought only in the courts
|
||||
of a jurisdiction where the defendant maintains its principal place of
|
||||
business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions. Nothing
|
||||
in this Section shall prevent a party's ability to bring cross-claims or
|
||||
counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides that
|
||||
the language of a contract shall be construed against the drafter shall not
|
||||
be used to construe this License against a Contributor.
|
||||
|
||||
|
||||
10. Versions of the License
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses If You choose to distribute Source Code Form that is
|
||||
Incompatible With Secondary Licenses under the terms of this version of
|
||||
the License, the notice described in Exhibit B of this License must be
|
||||
attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular file,
|
||||
then You may include the notice in a location (such as a LICENSE file in a
|
||||
relevant directory) where a recipient would be likely to look for such a
|
||||
notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
|
||||
This Source Code Form is "Incompatible
|
||||
With Secondary Licenses", as defined by
|
||||
the Mozilla Public License, v. 2.0.
|
||||
|
53
vendor/github.com/hashicorp/go-cleanhttp/cleanhttp.go
generated
vendored
Normal file
53
vendor/github.com/hashicorp/go-cleanhttp/cleanhttp.go
generated
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
package cleanhttp
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DefaultTransport returns a new http.Transport with the same default values
|
||||
// as http.DefaultTransport, but with idle connections and keepalives disabled.
|
||||
func DefaultTransport() *http.Transport {
|
||||
transport := DefaultPooledTransport()
|
||||
transport.DisableKeepAlives = true
|
||||
transport.MaxIdleConnsPerHost = -1
|
||||
return transport
|
||||
}
|
||||
|
||||
// DefaultPooledTransport returns a new http.Transport with similar default
|
||||
// values to http.DefaultTransport. Do not use this for transient transports as
|
||||
// it can leak file descriptors over time. Only use this for transports that
|
||||
// will be re-used for the same host(s).
|
||||
func DefaultPooledTransport() *http.Transport {
|
||||
transport := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
DisableKeepAlives: false,
|
||||
MaxIdleConnsPerHost: 1,
|
||||
}
|
||||
return transport
|
||||
}
|
||||
|
||||
// DefaultClient returns a new http.Client with similar default values to
|
||||
// http.Client, but with a non-shared Transport, idle connections disabled, and
|
||||
// keepalives disabled.
|
||||
func DefaultClient() *http.Client {
|
||||
return &http.Client{
|
||||
Transport: DefaultTransport(),
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultPooledClient returns a new http.Client with the same default values
|
||||
// as http.Client, but with a non-shared Transport. Do not use this function
|
||||
// for transient clients as it can leak file descriptors over time. Only use
|
||||
// this for clients that will be re-used for the same host(s).
|
||||
func DefaultPooledClient() *http.Client {
|
||||
return &http.Client{
|
||||
Transport: DefaultPooledTransport(),
|
||||
}
|
||||
}
|
20
vendor/github.com/hashicorp/go-cleanhttp/doc.go
generated
vendored
Normal file
20
vendor/github.com/hashicorp/go-cleanhttp/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
// Package cleanhttp offers convenience utilities for acquiring "clean"
|
||||
// http.Transport and http.Client structs.
|
||||
//
|
||||
// Values set on http.DefaultClient and http.DefaultTransport affect all
|
||||
// callers. This can have detrimental effects, esepcially in TLS contexts,
|
||||
// where client or root certificates set to talk to multiple endpoints can end
|
||||
// up displacing each other, leading to hard-to-debug issues. This package
|
||||
// provides non-shared http.Client and http.Transport structs to ensure that
|
||||
// the configuration will not be overwritten by other parts of the application
|
||||
// or dependencies.
|
||||
//
|
||||
// The DefaultClient and DefaultTransport functions disable idle connections
|
||||
// and keepalives. Without ensuring that idle connections are closed before
|
||||
// garbage collection, short-term clients/transports can leak file descriptors,
|
||||
// eventually leading to "too many open files" errors. If you will be
|
||||
// connecting to the same hosts repeatedly from the same client, you can use
|
||||
// DefaultPooledClient to receive a client that has connection pooling
|
||||
// semantics similar to http.DefaultClient.
|
||||
//
|
||||
package cleanhttp
|
354
vendor/github.com/hashicorp/go-version/LICENSE
generated
vendored
Normal file
354
vendor/github.com/hashicorp/go-version/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,354 @@
|
|||
Mozilla Public License, version 2.0
|
||||
|
||||
1. Definitions
|
||||
|
||||
1.1. “Contributor”
|
||||
|
||||
means each individual or legal entity that creates, contributes to the
|
||||
creation of, or owns Covered Software.
|
||||
|
||||
1.2. “Contributor Version”
|
||||
|
||||
means the combination of the Contributions of others (if any) used by a
|
||||
Contributor and that particular Contributor’s Contribution.
|
||||
|
||||
1.3. “Contribution”
|
||||
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. “Covered Software”
|
||||
|
||||
means Source Code Form to which the initial Contributor has attached the
|
||||
notice in Exhibit A, the Executable Form of such Source Code Form, and
|
||||
Modifications of such Source Code Form, in each case including portions
|
||||
thereof.
|
||||
|
||||
1.5. “Incompatible With Secondary Licenses”
|
||||
means
|
||||
|
||||
a. that the initial Contributor has attached the notice described in
|
||||
Exhibit B to the Covered Software; or
|
||||
|
||||
b. that the Covered Software was made available under the terms of version
|
||||
1.1 or earlier of the License, but not also under the terms of a
|
||||
Secondary License.
|
||||
|
||||
1.6. “Executable Form”
|
||||
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. “Larger Work”
|
||||
|
||||
means a work that combines Covered Software with other material, in a separate
|
||||
file or files, that is not Covered Software.
|
||||
|
||||
1.8. “License”
|
||||
|
||||
means this document.
|
||||
|
||||
1.9. “Licensable”
|
||||
|
||||
means having the right to grant, to the maximum extent possible, whether at the
|
||||
time of the initial grant or subsequently, any and all of the rights conveyed by
|
||||
this License.
|
||||
|
||||
1.10. “Modifications”
|
||||
|
||||
means any of the following:
|
||||
|
||||
a. any file in Source Code Form that results from an addition to, deletion
|
||||
from, or modification of the contents of Covered Software; or
|
||||
|
||||
b. any new file in Source Code Form that contains any Covered Software.
|
||||
|
||||
1.11. “Patent Claims” of a Contributor
|
||||
|
||||
means any patent claim(s), including without limitation, method, process,
|
||||
and apparatus claims, in any patent Licensable by such Contributor that
|
||||
would be infringed, but for the grant of the License, by the making,
|
||||
using, selling, offering for sale, having made, import, or transfer of
|
||||
either its Contributions or its Contributor Version.
|
||||
|
||||
1.12. “Secondary License”
|
||||
|
||||
means either the GNU General Public License, Version 2.0, the GNU Lesser
|
||||
General Public License, Version 2.1, the GNU Affero General Public
|
||||
License, Version 3.0, or any later versions of those licenses.
|
||||
|
||||
1.13. “Source Code Form”
|
||||
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. “You” (or “Your”)
|
||||
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, “You” includes any entity that controls, is
|
||||
controlled by, or is under common control with You. For purposes of this
|
||||
definition, “control” means (a) the power, direct or indirect, to cause
|
||||
the direction or management of such entity, whether by contract or
|
||||
otherwise, or (b) ownership of more than fifty percent (50%) of the
|
||||
outstanding shares or beneficial ownership of such entity.
|
||||
|
||||
|
||||
2. License Grants and Conditions
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
a. under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or as
|
||||
part of a Larger Work; and
|
||||
|
||||
b. under Patent Claims of such Contributor to make, use, sell, offer for
|
||||
sale, have made, import, and otherwise transfer either its Contributions
|
||||
or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution become
|
||||
effective for each Contribution on the date the Contributor first distributes
|
||||
such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under this
|
||||
License. No additional rights or licenses will be implied from the distribution
|
||||
or licensing of Covered Software under this License. Notwithstanding Section
|
||||
2.1(b) above, no patent license is granted by a Contributor:
|
||||
|
||||
a. for any code that a Contributor has removed from Covered Software; or
|
||||
|
||||
b. for infringements caused by: (i) Your and any other third party’s
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
c. under Patent Claims infringed by Covered Software in the absence of its
|
||||
Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks, or
|
||||
logos of any Contributor (except as may be necessary to comply with the
|
||||
notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this License
|
||||
(see Section 10.2) or under the terms of a Secondary License (if permitted
|
||||
under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its Contributions
|
||||
are its original creation(s) or it has sufficient rights to grant the
|
||||
rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under applicable
|
||||
copyright doctrines of fair use, fair dealing, or other equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
|
||||
Section 2.1.
|
||||
|
||||
|
||||
3. Responsibilities
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under the
|
||||
terms of this License. You must inform recipients that the Source Code Form
|
||||
of the Covered Software is governed by the terms of this License, and how
|
||||
they can obtain a copy of this License. You may not attempt to alter or
|
||||
restrict the recipients’ rights in the Source Code Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
a. such Covered Software must also be made available in Source Code Form,
|
||||
as described in Section 3.1, and You must inform recipients of the
|
||||
Executable Form how they can obtain a copy of such Source Code Form by
|
||||
reasonable means in a timely manner, at a charge no more than the cost
|
||||
of distribution to the recipient; and
|
||||
|
||||
b. You may distribute such Executable Form under the terms of this License,
|
||||
or sublicense it under different terms, provided that the license for
|
||||
the Executable Form does not attempt to limit or alter the recipients’
|
||||
rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for the
|
||||
Covered Software. If the Larger Work is a combination of Covered Software
|
||||
with a work governed by one or more Secondary Licenses, and the Covered
|
||||
Software is not Incompatible With Secondary Licenses, this License permits
|
||||
You to additionally distribute such Covered Software under the terms of
|
||||
such Secondary License(s), so that the recipient of the Larger Work may, at
|
||||
their option, further distribute the Covered Software under the terms of
|
||||
either this License or such Secondary License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices (including
|
||||
copyright notices, patent notices, disclaimers of warranty, or limitations
|
||||
of liability) contained within the Source Code Form of the Covered
|
||||
Software, except that You may alter any license notices to the extent
|
||||
required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on behalf
|
||||
of any Contributor. You must make it absolutely clear that any such
|
||||
warranty, support, indemnity, or liability obligation is offered by You
|
||||
alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this License
|
||||
with respect to some or all of the Covered Software due to statute, judicial
|
||||
order, or regulation then You must: (a) comply with the terms of this License
|
||||
to the maximum extent possible; and (b) describe the limitations and the code
|
||||
they affect. Such description must be placed in a text file included with all
|
||||
distributions of the Covered Software under this License. Except to the
|
||||
extent prohibited by statute or regulation, such description must be
|
||||
sufficiently detailed for a recipient of ordinary skill to be able to
|
||||
understand it.
|
||||
|
||||
5. Termination
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically if You
|
||||
fail to comply with any of its terms. However, if You become compliant,
|
||||
then the rights granted under this License from a particular Contributor
|
||||
are reinstated (a) provisionally, unless and until such Contributor
|
||||
explicitly and finally terminates Your grants, and (b) on an ongoing basis,
|
||||
if such Contributor fails to notify You of the non-compliance by some
|
||||
reasonable means prior to 60 days after You have come back into compliance.
|
||||
Moreover, Your grants from a particular Contributor are reinstated on an
|
||||
ongoing basis if such Contributor notifies You of the non-compliance by
|
||||
some reasonable means, this is the first time You have received notice of
|
||||
non-compliance with this License from such Contributor, and You become
|
||||
compliant prior to 30 days after Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions, counter-claims,
|
||||
and cross-claims) alleging that a Contributor Version directly or
|
||||
indirectly infringes any patent, then the rights granted to You by any and
|
||||
all Contributors for the Covered Software under Section 2.1 of this License
|
||||
shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
|
||||
license agreements (excluding distributors and resellers) which have been
|
||||
validly granted by You or Your distributors under this License prior to
|
||||
termination shall survive termination.
|
||||
|
||||
6. Disclaimer of Warranty
|
||||
|
||||
Covered Software is provided under this License on an “as is” basis, without
|
||||
warranty of any kind, either expressed, implied, or statutory, including,
|
||||
without limitation, warranties that the Covered Software is free of defects,
|
||||
merchantable, fit for a particular purpose or non-infringing. The entire
|
||||
risk as to the quality and performance of the Covered Software is with You.
|
||||
Should any Covered Software prove defective in any respect, You (not any
|
||||
Contributor) assume the cost of any necessary servicing, repair, or
|
||||
correction. This disclaimer of warranty constitutes an essential part of this
|
||||
License. No use of any Covered Software is authorized under this License
|
||||
except under this disclaimer.
|
||||
|
||||
7. Limitation of Liability
|
||||
|
||||
Under no circumstances and under no legal theory, whether tort (including
|
||||
negligence), contract, or otherwise, shall any Contributor, or anyone who
|
||||
distributes Covered Software as permitted above, be liable to You for any
|
||||
direct, indirect, special, incidental, or consequential damages of any
|
||||
character including, without limitation, damages for lost profits, loss of
|
||||
goodwill, work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses, even if such party shall have been
|
||||
informed of the possibility of such damages. This limitation of liability
|
||||
shall not apply to liability for death or personal injury resulting from such
|
||||
party’s negligence to the extent applicable law prohibits such limitation.
|
||||
Some jurisdictions do not allow the exclusion or limitation of incidental or
|
||||
consequential damages, so this exclusion and limitation may not apply to You.
|
||||
|
||||
8. Litigation
|
||||
|
||||
Any litigation relating to this License may be brought only in the courts of
|
||||
a jurisdiction where the defendant maintains its principal place of business
|
||||
and such litigation shall be governed by laws of that jurisdiction, without
|
||||
reference to its conflict-of-law provisions. Nothing in this Section shall
|
||||
prevent a party’s ability to bring cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
|
||||
This License represents the complete agreement concerning the subject matter
|
||||
hereof. If any provision of this License is held to be unenforceable, such
|
||||
provision shall be reformed only to the extent necessary to make it
|
||||
enforceable. Any law or regulation which provides that the language of a
|
||||
contract shall be construed against the drafter shall not be used to construe
|
||||
this License against a Contributor.
|
||||
|
||||
|
||||
10. Versions of the License
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version of
|
||||
the License under which You originally received the Covered Software, or
|
||||
under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a modified
|
||||
version of this License if you rename the license and remove any
|
||||
references to the name of the license steward (except to note that such
|
||||
modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular file, then
|
||||
You may include the notice in a location (such as a LICENSE file in a relevant
|
||||
directory) where a recipient would be likely to look for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - “Incompatible With Secondary Licenses” Notice
|
||||
|
||||
This Source Code Form is “Incompatible
|
||||
With Secondary Licenses”, as defined by
|
||||
the Mozilla Public License, v. 2.0.
|
||||
|
178
vendor/github.com/hashicorp/go-version/constraint.go
generated
vendored
Normal file
178
vendor/github.com/hashicorp/go-version/constraint.go
generated
vendored
Normal file
|
@ -0,0 +1,178 @@
|
|||
package version
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Constraint represents a single constraint for a version, such as
|
||||
// ">= 1.0".
|
||||
type Constraint struct {
|
||||
f constraintFunc
|
||||
check *Version
|
||||
original string
|
||||
}
|
||||
|
||||
// Constraints is a slice of constraints. We make a custom type so that
|
||||
// we can add methods to it.
|
||||
type Constraints []*Constraint
|
||||
|
||||
type constraintFunc func(v, c *Version) bool
|
||||
|
||||
var constraintOperators map[string]constraintFunc
|
||||
|
||||
var constraintRegexp *regexp.Regexp
|
||||
|
||||
func init() {
|
||||
constraintOperators = map[string]constraintFunc{
|
||||
"": constraintEqual,
|
||||
"=": constraintEqual,
|
||||
"!=": constraintNotEqual,
|
||||
">": constraintGreaterThan,
|
||||
"<": constraintLessThan,
|
||||
">=": constraintGreaterThanEqual,
|
||||
"<=": constraintLessThanEqual,
|
||||
"~>": constraintPessimistic,
|
||||
}
|
||||
|
||||
ops := make([]string, 0, len(constraintOperators))
|
||||
for k := range constraintOperators {
|
||||
ops = append(ops, regexp.QuoteMeta(k))
|
||||
}
|
||||
|
||||
constraintRegexp = regexp.MustCompile(fmt.Sprintf(
|
||||
`^\s*(%s)\s*(%s)\s*$`,
|
||||
strings.Join(ops, "|"),
|
||||
VersionRegexpRaw))
|
||||
}
|
||||
|
||||
// NewConstraint will parse one or more constraints from the given
|
||||
// constraint string. The string must be a comma-separated list of
|
||||
// constraints.
|
||||
func NewConstraint(v string) (Constraints, error) {
|
||||
vs := strings.Split(v, ",")
|
||||
result := make([]*Constraint, len(vs))
|
||||
for i, single := range vs {
|
||||
c, err := parseSingle(single)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result[i] = c
|
||||
}
|
||||
|
||||
return Constraints(result), nil
|
||||
}
|
||||
|
||||
// Check tests if a version satisfies all the constraints.
|
||||
func (cs Constraints) Check(v *Version) bool {
|
||||
for _, c := range cs {
|
||||
if !c.Check(v) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Returns the string format of the constraints
|
||||
func (cs Constraints) String() string {
|
||||
csStr := make([]string, len(cs))
|
||||
for i, c := range cs {
|
||||
csStr[i] = c.String()
|
||||
}
|
||||
|
||||
return strings.Join(csStr, ",")
|
||||
}
|
||||
|
||||
// Check tests if a constraint is validated by the given version.
|
||||
func (c *Constraint) Check(v *Version) bool {
|
||||
return c.f(v, c.check)
|
||||
}
|
||||
|
||||
func (c *Constraint) String() string {
|
||||
return c.original
|
||||
}
|
||||
|
||||
func parseSingle(v string) (*Constraint, error) {
|
||||
matches := constraintRegexp.FindStringSubmatch(v)
|
||||
if matches == nil {
|
||||
return nil, fmt.Errorf("Malformed constraint: %s", v)
|
||||
}
|
||||
|
||||
check, err := NewVersion(matches[2])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Constraint{
|
||||
f: constraintOperators[matches[1]],
|
||||
check: check,
|
||||
original: v,
|
||||
}, nil
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Constraint functions
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
func constraintEqual(v, c *Version) bool {
|
||||
return v.Equal(c)
|
||||
}
|
||||
|
||||
func constraintNotEqual(v, c *Version) bool {
|
||||
return !v.Equal(c)
|
||||
}
|
||||
|
||||
func constraintGreaterThan(v, c *Version) bool {
|
||||
return v.Compare(c) == 1
|
||||
}
|
||||
|
||||
func constraintLessThan(v, c *Version) bool {
|
||||
return v.Compare(c) == -1
|
||||
}
|
||||
|
||||
func constraintGreaterThanEqual(v, c *Version) bool {
|
||||
return v.Compare(c) >= 0
|
||||
}
|
||||
|
||||
func constraintLessThanEqual(v, c *Version) bool {
|
||||
return v.Compare(c) <= 0
|
||||
}
|
||||
|
||||
func constraintPessimistic(v, c *Version) bool {
|
||||
// If the version being checked is naturally less than the constraint, then there
|
||||
// is no way for the version to be valid against the constraint
|
||||
if v.LessThan(c) {
|
||||
return false
|
||||
}
|
||||
// We'll use this more than once, so grab the length now so it's a little cleaner
|
||||
// to write the later checks
|
||||
cs := len(c.segments)
|
||||
|
||||
// If the version being checked has less specificity than the constraint, then there
|
||||
// is no way for the version to be valid against the constraint
|
||||
if cs > len(v.segments) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check the segments in the constraint against those in the version. If the version
|
||||
// being checked, at any point, does not have the same values in each index of the
|
||||
// constraints segments, then it cannot be valid against the constraint.
|
||||
for i := 0; i < c.si-1; i++ {
|
||||
if v.segments[i] != c.segments[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Check the last part of the segment in the constraint. If the version segment at
|
||||
// this index is less than the constraints segment at this index, then it cannot
|
||||
// be valid against the constraint
|
||||
if c.segments[cs-1] > v.segments[cs-1] {
|
||||
return false
|
||||
}
|
||||
|
||||
// If nothing has rejected the version by now, it's valid
|
||||
return true
|
||||
}
|
308
vendor/github.com/hashicorp/go-version/version.go
generated
vendored
Normal file
308
vendor/github.com/hashicorp/go-version/version.go
generated
vendored
Normal file
|
@ -0,0 +1,308 @@
|
|||
package version
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// The compiled regular expression used to test the validity of a version.
|
||||
var versionRegexp *regexp.Regexp
|
||||
|
||||
// The raw regular expression string used for testing the validity
|
||||
// of a version.
|
||||
const VersionRegexpRaw string = `v?([0-9]+(\.[0-9]+)*?)` +
|
||||
`(-?([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
|
||||
`(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
|
||||
`?`
|
||||
|
||||
// Version represents a single version.
|
||||
type Version struct {
|
||||
metadata string
|
||||
pre string
|
||||
segments []int64
|
||||
si int
|
||||
}
|
||||
|
||||
func init() {
|
||||
versionRegexp = regexp.MustCompile("^" + VersionRegexpRaw + "$")
|
||||
}
|
||||
|
||||
// NewVersion parses the given version and returns a new
|
||||
// Version.
|
||||
func NewVersion(v string) (*Version, error) {
|
||||
matches := versionRegexp.FindStringSubmatch(v)
|
||||
if matches == nil {
|
||||
return nil, fmt.Errorf("Malformed version: %s", v)
|
||||
}
|
||||
segmentsStr := strings.Split(matches[1], ".")
|
||||
segments := make([]int64, len(segmentsStr))
|
||||
si := 0
|
||||
for i, str := range segmentsStr {
|
||||
val, err := strconv.ParseInt(str, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"Error parsing version: %s", err)
|
||||
}
|
||||
|
||||
segments[i] = int64(val)
|
||||
si++
|
||||
}
|
||||
|
||||
// Even though we could support more than three segments, if we
|
||||
// got less than three, pad it with 0s. This is to cover the basic
|
||||
// default usecase of semver, which is MAJOR.MINOR.PATCH at the minimum
|
||||
for i := len(segments); i < 3; i++ {
|
||||
segments = append(segments, 0)
|
||||
}
|
||||
|
||||
return &Version{
|
||||
metadata: matches[7],
|
||||
pre: matches[4],
|
||||
segments: segments,
|
||||
si: si,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Must is a helper that wraps a call to a function returning (*Version, error)
|
||||
// and panics if error is non-nil.
|
||||
func Must(v *Version, err error) *Version {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Compare compares this version to another version. This
|
||||
// returns -1, 0, or 1 if this version is smaller, equal,
|
||||
// or larger than the other version, respectively.
|
||||
//
|
||||
// If you want boolean results, use the LessThan, Equal,
|
||||
// or GreaterThan methods.
|
||||
func (v *Version) Compare(other *Version) int {
|
||||
// A quick, efficient equality check
|
||||
if v.String() == other.String() {
|
||||
return 0
|
||||
}
|
||||
|
||||
segmentsSelf := v.Segments64()
|
||||
segmentsOther := other.Segments64()
|
||||
|
||||
// If the segments are the same, we must compare on prerelease info
|
||||
if reflect.DeepEqual(segmentsSelf, segmentsOther) {
|
||||
preSelf := v.Prerelease()
|
||||
preOther := other.Prerelease()
|
||||
if preSelf == "" && preOther == "" {
|
||||
return 0
|
||||
}
|
||||
if preSelf == "" {
|
||||
return 1
|
||||
}
|
||||
if preOther == "" {
|
||||
return -1
|
||||
}
|
||||
|
||||
return comparePrereleases(preSelf, preOther)
|
||||
}
|
||||
|
||||
// Get the highest specificity (hS), or if they're equal, just use segmentSelf length
|
||||
lenSelf := len(segmentsSelf)
|
||||
lenOther := len(segmentsOther)
|
||||
hS := lenSelf
|
||||
if lenSelf < lenOther {
|
||||
hS = lenOther
|
||||
}
|
||||
// Compare the segments
|
||||
// Because a constraint could have more/less specificity than the version it's
|
||||
// checking, we need to account for a lopsided or jagged comparison
|
||||
for i := 0; i < hS; i++ {
|
||||
if i > lenSelf-1 {
|
||||
// This means Self had the lower specificity
|
||||
// Check to see if the remaining segments in Other are all zeros
|
||||
if !allZero(segmentsOther[i:]) {
|
||||
// if not, it means that Other has to be greater than Self
|
||||
return -1
|
||||
}
|
||||
break
|
||||
} else if i > lenOther-1 {
|
||||
// this means Other had the lower specificity
|
||||
// Check to see if the remaining segments in Self are all zeros -
|
||||
if !allZero(segmentsSelf[i:]) {
|
||||
//if not, it means that Self has to be greater than Other
|
||||
return 1
|
||||
}
|
||||
break
|
||||
}
|
||||
lhs := segmentsSelf[i]
|
||||
rhs := segmentsOther[i]
|
||||
if lhs == rhs {
|
||||
continue
|
||||
} else if lhs < rhs {
|
||||
return -1
|
||||
}
|
||||
// Otherwis, rhs was > lhs, they're not equal
|
||||
return 1
|
||||
}
|
||||
|
||||
// if we got this far, they're equal
|
||||
return 0
|
||||
}
|
||||
|
||||
func allZero(segs []int64) bool {
|
||||
for _, s := range segs {
|
||||
if s != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func comparePart(preSelf string, preOther string) int {
|
||||
if preSelf == preOther {
|
||||
return 0
|
||||
}
|
||||
|
||||
// if a part is empty, we use the other to decide
|
||||
if preSelf == "" {
|
||||
_, notIsNumeric := strconv.ParseInt(preOther, 10, 64)
|
||||
if notIsNumeric == nil {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
if preOther == "" {
|
||||
_, notIsNumeric := strconv.ParseInt(preSelf, 10, 64)
|
||||
if notIsNumeric == nil {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
if preSelf > preOther {
|
||||
return 1
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
func comparePrereleases(v string, other string) int {
|
||||
// the same pre release!
|
||||
if v == other {
|
||||
return 0
|
||||
}
|
||||
|
||||
// split both pre releases for analyse their parts
|
||||
selfPreReleaseMeta := strings.Split(v, ".")
|
||||
otherPreReleaseMeta := strings.Split(other, ".")
|
||||
|
||||
selfPreReleaseLen := len(selfPreReleaseMeta)
|
||||
otherPreReleaseLen := len(otherPreReleaseMeta)
|
||||
|
||||
biggestLen := otherPreReleaseLen
|
||||
if selfPreReleaseLen > otherPreReleaseLen {
|
||||
biggestLen = selfPreReleaseLen
|
||||
}
|
||||
|
||||
// loop for parts to find the first difference
|
||||
for i := 0; i < biggestLen; i = i + 1 {
|
||||
partSelfPre := ""
|
||||
if i < selfPreReleaseLen {
|
||||
partSelfPre = selfPreReleaseMeta[i]
|
||||
}
|
||||
|
||||
partOtherPre := ""
|
||||
if i < otherPreReleaseLen {
|
||||
partOtherPre = otherPreReleaseMeta[i]
|
||||
}
|
||||
|
||||
compare := comparePart(partSelfPre, partOtherPre)
|
||||
// if parts are equals, continue the loop
|
||||
if compare != 0 {
|
||||
return compare
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// Equal tests if two versions are equal.
|
||||
func (v *Version) Equal(o *Version) bool {
|
||||
return v.Compare(o) == 0
|
||||
}
|
||||
|
||||
// GreaterThan tests if this version is greater than another version.
|
||||
func (v *Version) GreaterThan(o *Version) bool {
|
||||
return v.Compare(o) > 0
|
||||
}
|
||||
|
||||
// LessThan tests if this version is less than another version.
|
||||
func (v *Version) LessThan(o *Version) bool {
|
||||
return v.Compare(o) < 0
|
||||
}
|
||||
|
||||
// Metadata returns any metadata that was part of the version
|
||||
// string.
|
||||
//
|
||||
// Metadata is anything that comes after the "+" in the version.
|
||||
// For example, with "1.2.3+beta", the metadata is "beta".
|
||||
func (v *Version) Metadata() string {
|
||||
return v.metadata
|
||||
}
|
||||
|
||||
// Prerelease returns any prerelease data that is part of the version,
|
||||
// or blank if there is no prerelease data.
|
||||
//
|
||||
// Prerelease information is anything that comes after the "-" in the
|
||||
// version (but before any metadata). For example, with "1.2.3-beta",
|
||||
// the prerelease information is "beta".
|
||||
func (v *Version) Prerelease() string {
|
||||
return v.pre
|
||||
}
|
||||
|
||||
// Segments returns the numeric segments of the version as a slice of ints.
|
||||
//
|
||||
// This excludes any metadata or pre-release information. For example,
|
||||
// for a version "1.2.3-beta", segments will return a slice of
|
||||
// 1, 2, 3.
|
||||
func (v *Version) Segments() []int {
|
||||
segmentSlice := make([]int, len(v.segments))
|
||||
for i, v := range v.segments {
|
||||
segmentSlice[i] = int(v)
|
||||
}
|
||||
return segmentSlice
|
||||
}
|
||||
|
||||
// Segments64 returns the numeric segments of the version as a slice of int64s.
|
||||
//
|
||||
// This excludes any metadata or pre-release information. For example,
|
||||
// for a version "1.2.3-beta", segments will return a slice of
|
||||
// 1, 2, 3.
|
||||
func (v *Version) Segments64() []int64 {
|
||||
return v.segments
|
||||
}
|
||||
|
||||
// String returns the full version string included pre-release
|
||||
// and metadata information.
|
||||
func (v *Version) String() string {
|
||||
var buf bytes.Buffer
|
||||
fmtParts := make([]string, len(v.segments))
|
||||
for i, s := range v.segments {
|
||||
// We can ignore err here since we've pre-parsed the values in segments
|
||||
str := strconv.FormatInt(s, 10)
|
||||
fmtParts[i] = str
|
||||
}
|
||||
fmt.Fprintf(&buf, strings.Join(fmtParts, "."))
|
||||
if v.pre != "" {
|
||||
fmt.Fprintf(&buf, "-%s", v.pre)
|
||||
}
|
||||
if v.metadata != "" {
|
||||
fmt.Fprintf(&buf, "+%s", v.metadata)
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
17
vendor/github.com/hashicorp/go-version/version_collection.go
generated
vendored
Normal file
17
vendor/github.com/hashicorp/go-version/version_collection.go
generated
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
package version
|
||||
|
||||
// Collection is a type that implements the sort.Interface interface
|
||||
// so that versions can be sorted.
|
||||
type Collection []*Version
|
||||
|
||||
func (v Collection) Len() int {
|
||||
return len(v)
|
||||
}
|
||||
|
||||
func (v Collection) Less(i, j int) bool {
|
||||
return v[i].LessThan(v[j])
|
||||
}
|
||||
|
||||
func (v Collection) Swap(i, j int) {
|
||||
v[i], v[j] = v[j], v[i]
|
||||
}
|
354
vendor/github.com/hashicorp/serf/LICENSE
generated
vendored
Normal file
354
vendor/github.com/hashicorp/serf/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,354 @@
|
|||
Mozilla Public License, version 2.0
|
||||
|
||||
1. Definitions
|
||||
|
||||
1.1. “Contributor”
|
||||
|
||||
means each individual or legal entity that creates, contributes to the
|
||||
creation of, or owns Covered Software.
|
||||
|
||||
1.2. “Contributor Version”
|
||||
|
||||
means the combination of the Contributions of others (if any) used by a
|
||||
Contributor and that particular Contributor’s Contribution.
|
||||
|
||||
1.3. “Contribution”
|
||||
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. “Covered Software”
|
||||
|
||||
means Source Code Form to which the initial Contributor has attached the
|
||||
notice in Exhibit A, the Executable Form of such Source Code Form, and
|
||||
Modifications of such Source Code Form, in each case including portions
|
||||
thereof.
|
||||
|
||||
1.5. “Incompatible With Secondary Licenses”
|
||||
means
|
||||
|
||||
a. that the initial Contributor has attached the notice described in
|
||||
Exhibit B to the Covered Software; or
|
||||
|
||||
b. that the Covered Software was made available under the terms of version
|
||||
1.1 or earlier of the License, but not also under the terms of a
|
||||
Secondary License.
|
||||
|
||||
1.6. “Executable Form”
|
||||
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. “Larger Work”
|
||||
|
||||
means a work that combines Covered Software with other material, in a separate
|
||||
file or files, that is not Covered Software.
|
||||
|
||||
1.8. “License”
|
||||
|
||||
means this document.
|
||||
|
||||
1.9. “Licensable”
|
||||
|
||||
means having the right to grant, to the maximum extent possible, whether at the
|
||||
time of the initial grant or subsequently, any and all of the rights conveyed by
|
||||
this License.
|
||||
|
||||
1.10. “Modifications”
|
||||
|
||||
means any of the following:
|
||||
|
||||
a. any file in Source Code Form that results from an addition to, deletion
|
||||
from, or modification of the contents of Covered Software; or
|
||||
|
||||
b. any new file in Source Code Form that contains any Covered Software.
|
||||
|
||||
1.11. “Patent Claims” of a Contributor
|
||||
|
||||
means any patent claim(s), including without limitation, method, process,
|
||||
and apparatus claims, in any patent Licensable by such Contributor that
|
||||
would be infringed, but for the grant of the License, by the making,
|
||||
using, selling, offering for sale, having made, import, or transfer of
|
||||
either its Contributions or its Contributor Version.
|
||||
|
||||
1.12. “Secondary License”
|
||||
|
||||
means either the GNU General Public License, Version 2.0, the GNU Lesser
|
||||
General Public License, Version 2.1, the GNU Affero General Public
|
||||
License, Version 3.0, or any later versions of those licenses.
|
||||
|
||||
1.13. “Source Code Form”
|
||||
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. “You” (or “Your”)
|
||||
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, “You” includes any entity that controls, is
|
||||
controlled by, or is under common control with You. For purposes of this
|
||||
definition, “control” means (a) the power, direct or indirect, to cause
|
||||
the direction or management of such entity, whether by contract or
|
||||
otherwise, or (b) ownership of more than fifty percent (50%) of the
|
||||
outstanding shares or beneficial ownership of such entity.
|
||||
|
||||
|
||||
2. License Grants and Conditions
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
a. under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or as
|
||||
part of a Larger Work; and
|
||||
|
||||
b. under Patent Claims of such Contributor to make, use, sell, offer for
|
||||
sale, have made, import, and otherwise transfer either its Contributions
|
||||
or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution become
|
||||
effective for each Contribution on the date the Contributor first distributes
|
||||
such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under this
|
||||
License. No additional rights or licenses will be implied from the distribution
|
||||
or licensing of Covered Software under this License. Notwithstanding Section
|
||||
2.1(b) above, no patent license is granted by a Contributor:
|
||||
|
||||
a. for any code that a Contributor has removed from Covered Software; or
|
||||
|
||||
b. for infringements caused by: (i) Your and any other third party’s
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
c. under Patent Claims infringed by Covered Software in the absence of its
|
||||
Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks, or
|
||||
logos of any Contributor (except as may be necessary to comply with the
|
||||
notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this License
|
||||
(see Section 10.2) or under the terms of a Secondary License (if permitted
|
||||
under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its Contributions
|
||||
are its original creation(s) or it has sufficient rights to grant the
|
||||
rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under applicable
|
||||
copyright doctrines of fair use, fair dealing, or other equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
|
||||
Section 2.1.
|
||||
|
||||
|
||||
3. Responsibilities
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under the
|
||||
terms of this License. You must inform recipients that the Source Code Form
|
||||
of the Covered Software is governed by the terms of this License, and how
|
||||
they can obtain a copy of this License. You may not attempt to alter or
|
||||
restrict the recipients’ rights in the Source Code Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
a. such Covered Software must also be made available in Source Code Form,
|
||||
as described in Section 3.1, and You must inform recipients of the
|
||||
Executable Form how they can obtain a copy of such Source Code Form by
|
||||
reasonable means in a timely manner, at a charge no more than the cost
|
||||
of distribution to the recipient; and
|
||||
|
||||
b. You may distribute such Executable Form under the terms of this License,
|
||||
or sublicense it under different terms, provided that the license for
|
||||
the Executable Form does not attempt to limit or alter the recipients’
|
||||
rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for the
|
||||
Covered Software. If the Larger Work is a combination of Covered Software
|
||||
with a work governed by one or more Secondary Licenses, and the Covered
|
||||
Software is not Incompatible With Secondary Licenses, this License permits
|
||||
You to additionally distribute such Covered Software under the terms of
|
||||
such Secondary License(s), so that the recipient of the Larger Work may, at
|
||||
their option, further distribute the Covered Software under the terms of
|
||||
either this License or such Secondary License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices (including
|
||||
copyright notices, patent notices, disclaimers of warranty, or limitations
|
||||
of liability) contained within the Source Code Form of the Covered
|
||||
Software, except that You may alter any license notices to the extent
|
||||
required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on behalf
|
||||
of any Contributor. You must make it absolutely clear that any such
|
||||
warranty, support, indemnity, or liability obligation is offered by You
|
||||
alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this License
|
||||
with respect to some or all of the Covered Software due to statute, judicial
|
||||
order, or regulation then You must: (a) comply with the terms of this License
|
||||
to the maximum extent possible; and (b) describe the limitations and the code
|
||||
they affect. Such description must be placed in a text file included with all
|
||||
distributions of the Covered Software under this License. Except to the
|
||||
extent prohibited by statute or regulation, such description must be
|
||||
sufficiently detailed for a recipient of ordinary skill to be able to
|
||||
understand it.
|
||||
|
||||
5. Termination
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically if You
|
||||
fail to comply with any of its terms. However, if You become compliant,
|
||||
then the rights granted under this License from a particular Contributor
|
||||
are reinstated (a) provisionally, unless and until such Contributor
|
||||
explicitly and finally terminates Your grants, and (b) on an ongoing basis,
|
||||
if such Contributor fails to notify You of the non-compliance by some
|
||||
reasonable means prior to 60 days after You have come back into compliance.
|
||||
Moreover, Your grants from a particular Contributor are reinstated on an
|
||||
ongoing basis if such Contributor notifies You of the non-compliance by
|
||||
some reasonable means, this is the first time You have received notice of
|
||||
non-compliance with this License from such Contributor, and You become
|
||||
compliant prior to 30 days after Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions, counter-claims,
|
||||
and cross-claims) alleging that a Contributor Version directly or
|
||||
indirectly infringes any patent, then the rights granted to You by any and
|
||||
all Contributors for the Covered Software under Section 2.1 of this License
|
||||
shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
|
||||
license agreements (excluding distributors and resellers) which have been
|
||||
validly granted by You or Your distributors under this License prior to
|
||||
termination shall survive termination.
|
||||
|
||||
6. Disclaimer of Warranty
|
||||
|
||||
Covered Software is provided under this License on an “as is” basis, without
|
||||
warranty of any kind, either expressed, implied, or statutory, including,
|
||||
without limitation, warranties that the Covered Software is free of defects,
|
||||
merchantable, fit for a particular purpose or non-infringing. The entire
|
||||
risk as to the quality and performance of the Covered Software is with You.
|
||||
Should any Covered Software prove defective in any respect, You (not any
|
||||
Contributor) assume the cost of any necessary servicing, repair, or
|
||||
correction. This disclaimer of warranty constitutes an essential part of this
|
||||
License. No use of any Covered Software is authorized under this License
|
||||
except under this disclaimer.
|
||||
|
||||
7. Limitation of Liability
|
||||
|
||||
Under no circumstances and under no legal theory, whether tort (including
|
||||
negligence), contract, or otherwise, shall any Contributor, or anyone who
|
||||
distributes Covered Software as permitted above, be liable to You for any
|
||||
direct, indirect, special, incidental, or consequential damages of any
|
||||
character including, without limitation, damages for lost profits, loss of
|
||||
goodwill, work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses, even if such party shall have been
|
||||
informed of the possibility of such damages. This limitation of liability
|
||||
shall not apply to liability for death or personal injury resulting from such
|
||||
party’s negligence to the extent applicable law prohibits such limitation.
|
||||
Some jurisdictions do not allow the exclusion or limitation of incidental or
|
||||
consequential damages, so this exclusion and limitation may not apply to You.
|
||||
|
||||
8. Litigation
|
||||
|
||||
Any litigation relating to this License may be brought only in the courts of
|
||||
a jurisdiction where the defendant maintains its principal place of business
|
||||
and such litigation shall be governed by laws of that jurisdiction, without
|
||||
reference to its conflict-of-law provisions. Nothing in this Section shall
|
||||
prevent a party’s ability to bring cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
|
||||
This License represents the complete agreement concerning the subject matter
|
||||
hereof. If any provision of this License is held to be unenforceable, such
|
||||
provision shall be reformed only to the extent necessary to make it
|
||||
enforceable. Any law or regulation which provides that the language of a
|
||||
contract shall be construed against the drafter shall not be used to construe
|
||||
this License against a Contributor.
|
||||
|
||||
|
||||
10. Versions of the License
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version of
|
||||
the License under which You originally received the Covered Software, or
|
||||
under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a modified
|
||||
version of this License if you rename the license and remove any
|
||||
references to the name of the license steward (except to note that such
|
||||
modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular file, then
|
||||
You may include the notice in a location (such as a LICENSE file in a relevant
|
||||
directory) where a recipient would be likely to look for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - “Incompatible With Secondary Licenses” Notice
|
||||
|
||||
This Source Code Form is “Incompatible
|
||||
With Secondary Licenses”, as defined by
|
||||
the Mozilla Public License, v. 2.0.
|
||||
|
133
vendor/github.com/hashicorp/serf/commands.go
generated
vendored
Normal file
133
vendor/github.com/hashicorp/serf/commands.go
generated
vendored
Normal file
|
@ -0,0 +1,133 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/serf/command"
|
||||
"github.com/hashicorp/serf/command/agent"
|
||||
"github.com/mitchellh/cli"
|
||||
"os"
|
||||
"os/signal"
|
||||
)
|
||||
|
||||
// Commands is the mapping of all the available Serf commands.
|
||||
var Commands map[string]cli.CommandFactory
|
||||
|
||||
func init() {
|
||||
ui := &cli.BasicUi{Writer: os.Stdout}
|
||||
|
||||
Commands = map[string]cli.CommandFactory{
|
||||
"agent": func() (cli.Command, error) {
|
||||
return &agent.Command{
|
||||
Ui: ui,
|
||||
ShutdownCh: make(chan struct{}),
|
||||
}, nil
|
||||
},
|
||||
|
||||
"event": func() (cli.Command, error) {
|
||||
return &command.EventCommand{
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"query": func() (cli.Command, error) {
|
||||
return &command.QueryCommand{
|
||||
ShutdownCh: makeShutdownCh(),
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"force-leave": func() (cli.Command, error) {
|
||||
return &command.ForceLeaveCommand{
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"join": func() (cli.Command, error) {
|
||||
return &command.JoinCommand{
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"keygen": func() (cli.Command, error) {
|
||||
return &command.KeygenCommand{
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"keys": func() (cli.Command, error) {
|
||||
return &command.KeysCommand{
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"leave": func() (cli.Command, error) {
|
||||
return &command.LeaveCommand{
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"members": func() (cli.Command, error) {
|
||||
return &command.MembersCommand{
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"monitor": func() (cli.Command, error) {
|
||||
return &command.MonitorCommand{
|
||||
ShutdownCh: makeShutdownCh(),
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"tags": func() (cli.Command, error) {
|
||||
return &command.TagsCommand{
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"reachability": func() (cli.Command, error) {
|
||||
return &command.ReachabilityCommand{
|
||||
ShutdownCh: makeShutdownCh(),
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"rtt": func() (cli.Command, error) {
|
||||
return &command.RTTCommand{
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"info": func() (cli.Command, error) {
|
||||
return &command.InfoCommand{
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"version": func() (cli.Command, error) {
|
||||
return &command.VersionCommand{
|
||||
Revision: GitCommit,
|
||||
Version: Version,
|
||||
VersionPrerelease: VersionPrerelease,
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// makeShutdownCh returns a channel that can be used for shutdown
|
||||
// notifications for commands. This channel will send a message for every
|
||||
// interrupt received.
|
||||
func makeShutdownCh() <-chan struct{} {
|
||||
resultCh := make(chan struct{})
|
||||
|
||||
signalCh := make(chan os.Signal, 4)
|
||||
signal.Notify(signalCh, os.Interrupt)
|
||||
go func() {
|
||||
for {
|
||||
<-signalCh
|
||||
resultCh <- struct{}{}
|
||||
}
|
||||
}()
|
||||
|
||||
return resultCh
|
||||
}
|
180
vendor/github.com/hashicorp/serf/coordinate/client.go
generated
vendored
Normal file
180
vendor/github.com/hashicorp/serf/coordinate/client.go
generated
vendored
Normal file
|
@ -0,0 +1,180 @@
|
|||
package coordinate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Client manages the estimated network coordinate for a given node, and adjusts
|
||||
// it as the node observes round trip times and estimated coordinates from other
|
||||
// nodes. The core algorithm is based on Vivaldi, see the documentation for Config
|
||||
// for more details.
|
||||
type Client struct {
|
||||
// coord is the current estimate of the client's network coordinate.
|
||||
coord *Coordinate
|
||||
|
||||
// origin is a coordinate sitting at the origin.
|
||||
origin *Coordinate
|
||||
|
||||
// config contains the tuning parameters that govern the performance of
|
||||
// the algorithm.
|
||||
config *Config
|
||||
|
||||
// adjustmentIndex is the current index into the adjustmentSamples slice.
|
||||
adjustmentIndex uint
|
||||
|
||||
// adjustment is used to store samples for the adjustment calculation.
|
||||
adjustmentSamples []float64
|
||||
|
||||
// latencyFilterSamples is used to store the last several RTT samples,
|
||||
// keyed by node name. We will use the config's LatencyFilterSamples
|
||||
// value to determine how many samples we keep, per node.
|
||||
latencyFilterSamples map[string][]float64
|
||||
|
||||
// mutex enables safe concurrent access to the client.
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
// NewClient creates a new Client and verifies the configuration is valid.
|
||||
func NewClient(config *Config) (*Client, error) {
|
||||
if !(config.Dimensionality > 0) {
|
||||
return nil, fmt.Errorf("dimensionality must be >0")
|
||||
}
|
||||
|
||||
return &Client{
|
||||
coord: NewCoordinate(config),
|
||||
origin: NewCoordinate(config),
|
||||
config: config,
|
||||
adjustmentIndex: 0,
|
||||
adjustmentSamples: make([]float64, config.AdjustmentWindowSize),
|
||||
latencyFilterSamples: make(map[string][]float64),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetCoordinate returns a copy of the coordinate for this client.
|
||||
func (c *Client) GetCoordinate() *Coordinate {
|
||||
c.mutex.RLock()
|
||||
defer c.mutex.RUnlock()
|
||||
|
||||
return c.coord.Clone()
|
||||
}
|
||||
|
||||
// SetCoordinate forces the client's coordinate to a known state.
|
||||
func (c *Client) SetCoordinate(coord *Coordinate) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
c.coord = coord.Clone()
|
||||
}
|
||||
|
||||
// ForgetNode removes any client state for the given node.
|
||||
func (c *Client) ForgetNode(node string) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
delete(c.latencyFilterSamples, node)
|
||||
}
|
||||
|
||||
// latencyFilter applies a simple moving median filter with a new sample for
|
||||
// a node. This assumes that the mutex has been locked already.
|
||||
func (c *Client) latencyFilter(node string, rttSeconds float64) float64 {
|
||||
samples, ok := c.latencyFilterSamples[node]
|
||||
if !ok {
|
||||
samples = make([]float64, 0, c.config.LatencyFilterSize)
|
||||
}
|
||||
|
||||
// Add the new sample and trim the list, if needed.
|
||||
samples = append(samples, rttSeconds)
|
||||
if len(samples) > int(c.config.LatencyFilterSize) {
|
||||
samples = samples[1:]
|
||||
}
|
||||
c.latencyFilterSamples[node] = samples
|
||||
|
||||
// Sort a copy of the samples and return the median.
|
||||
sorted := make([]float64, len(samples))
|
||||
copy(sorted, samples)
|
||||
sort.Float64s(sorted)
|
||||
return sorted[len(sorted)/2]
|
||||
}
|
||||
|
||||
// updateVivialdi updates the Vivaldi portion of the client's coordinate. This
|
||||
// assumes that the mutex has been locked already.
|
||||
func (c *Client) updateVivaldi(other *Coordinate, rttSeconds float64) {
|
||||
const zeroThreshold = 1.0e-6
|
||||
|
||||
dist := c.coord.DistanceTo(other).Seconds()
|
||||
if rttSeconds < zeroThreshold {
|
||||
rttSeconds = zeroThreshold
|
||||
}
|
||||
wrongness := math.Abs(dist-rttSeconds) / rttSeconds
|
||||
|
||||
totalError := c.coord.Error + other.Error
|
||||
if totalError < zeroThreshold {
|
||||
totalError = zeroThreshold
|
||||
}
|
||||
weight := c.coord.Error / totalError
|
||||
|
||||
c.coord.Error = c.config.VivaldiCE*weight*wrongness + c.coord.Error*(1.0-c.config.VivaldiCE*weight)
|
||||
if c.coord.Error > c.config.VivaldiErrorMax {
|
||||
c.coord.Error = c.config.VivaldiErrorMax
|
||||
}
|
||||
|
||||
delta := c.config.VivaldiCC * weight
|
||||
force := delta * (rttSeconds - dist)
|
||||
c.coord = c.coord.ApplyForce(c.config, force, other)
|
||||
}
|
||||
|
||||
// updateAdjustment updates the adjustment portion of the client's coordinate, if
|
||||
// the feature is enabled. This assumes that the mutex has been locked already.
|
||||
func (c *Client) updateAdjustment(other *Coordinate, rttSeconds float64) {
|
||||
if c.config.AdjustmentWindowSize == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Note that the existing adjustment factors don't figure in to this
|
||||
// calculation so we use the raw distance here.
|
||||
dist := c.coord.rawDistanceTo(other)
|
||||
c.adjustmentSamples[c.adjustmentIndex] = rttSeconds - dist
|
||||
c.adjustmentIndex = (c.adjustmentIndex + 1) % c.config.AdjustmentWindowSize
|
||||
|
||||
sum := 0.0
|
||||
for _, sample := range c.adjustmentSamples {
|
||||
sum += sample
|
||||
}
|
||||
c.coord.Adjustment = sum / (2.0 * float64(c.config.AdjustmentWindowSize))
|
||||
}
|
||||
|
||||
// updateGravity applies a small amount of gravity to pull coordinates towards
|
||||
// the center of the coordinate system to combat drift. This assumes that the
|
||||
// mutex is locked already.
|
||||
func (c *Client) updateGravity() {
|
||||
dist := c.origin.DistanceTo(c.coord).Seconds()
|
||||
force := -1.0 * math.Pow(dist/c.config.GravityRho, 2.0)
|
||||
c.coord = c.coord.ApplyForce(c.config, force, c.origin)
|
||||
}
|
||||
|
||||
// Update takes other, a coordinate for another node, and rtt, a round trip
|
||||
// time observation for a ping to that node, and updates the estimated position of
|
||||
// the client's coordinate. Returns the updated coordinate.
|
||||
func (c *Client) Update(node string, other *Coordinate, rtt time.Duration) *Coordinate {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
rttSeconds := c.latencyFilter(node, rtt.Seconds())
|
||||
c.updateVivaldi(other, rttSeconds)
|
||||
c.updateAdjustment(other, rttSeconds)
|
||||
c.updateGravity()
|
||||
return c.coord.Clone()
|
||||
}
|
||||
|
||||
// DistanceTo returns the estimated RTT from the client's coordinate to other, the
|
||||
// coordinate for another node.
|
||||
func (c *Client) DistanceTo(other *Coordinate) time.Duration {
|
||||
c.mutex.RLock()
|
||||
defer c.mutex.RUnlock()
|
||||
|
||||
return c.coord.DistanceTo(other)
|
||||
}
|
70
vendor/github.com/hashicorp/serf/coordinate/config.go
generated
vendored
Normal file
70
vendor/github.com/hashicorp/serf/coordinate/config.go
generated
vendored
Normal file
|
@ -0,0 +1,70 @@
|
|||
package coordinate
|
||||
|
||||
// Config is used to set the parameters of the Vivaldi-based coordinate mapping
|
||||
// algorithm.
|
||||
//
|
||||
// The following references are called out at various points in the documentation
|
||||
// here:
|
||||
//
|
||||
// [1] Dabek, Frank, et al. "Vivaldi: A decentralized network coordinate system."
|
||||
// ACM SIGCOMM Computer Communication Review. Vol. 34. No. 4. ACM, 2004.
|
||||
// [2] Ledlie, Jonathan, Paul Gardner, and Margo I. Seltzer. "Network Coordinates
|
||||
// in the Wild." NSDI. Vol. 7. 2007.
|
||||
// [3] Lee, Sanghwan, et al. "On suitability of Euclidean embedding for
|
||||
// host-based network coordinate systems." Networking, IEEE/ACM Transactions
|
||||
// on 18.1 (2010): 27-40.
|
||||
type Config struct {
|
||||
// The dimensionality of the coordinate system. As discussed in [2], more
|
||||
// dimensions improves the accuracy of the estimates up to a point. Per [2]
|
||||
// we chose 8 dimensions plus a non-Euclidean height.
|
||||
Dimensionality uint
|
||||
|
||||
// VivaldiErrorMax is the default error value when a node hasn't yet made
|
||||
// any observations. It also serves as an upper limit on the error value in
|
||||
// case observations cause the error value to increase without bound.
|
||||
VivaldiErrorMax float64
|
||||
|
||||
// VivaldiCE is a tuning factor that controls the maximum impact an
|
||||
// observation can have on a node's confidence. See [1] for more details.
|
||||
VivaldiCE float64
|
||||
|
||||
// VivaldiCC is a tuning factor that controls the maximum impact an
|
||||
// observation can have on a node's coordinate. See [1] for more details.
|
||||
VivaldiCC float64
|
||||
|
||||
// AdjustmentWindowSize is a tuning factor that determines how many samples
|
||||
// we retain to calculate the adjustment factor as discussed in [3]. Setting
|
||||
// this to zero disables this feature.
|
||||
AdjustmentWindowSize uint
|
||||
|
||||
// HeightMin is the minimum value of the height parameter. Since this
|
||||
// always must be positive, it will introduce a small amount error, so
|
||||
// the chosen value should be relatively small compared to "normal"
|
||||
// coordinates.
|
||||
HeightMin float64
|
||||
|
||||
// LatencyFilterSamples is the maximum number of samples that are retained
|
||||
// per node, in order to compute a median. The intent is to ride out blips
|
||||
// but still keep the delay low, since our time to probe any given node is
|
||||
// pretty infrequent. See [2] for more details.
|
||||
LatencyFilterSize uint
|
||||
|
||||
// GravityRho is a tuning factor that sets how much gravity has an effect
|
||||
// to try to re-center coordinates. See [2] for more details.
|
||||
GravityRho float64
|
||||
}
|
||||
|
||||
// DefaultConfig returns a Config that has some default values suitable for
|
||||
// basic testing of the algorithm, but not tuned to any particular type of cluster.
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
Dimensionality: 8,
|
||||
VivaldiErrorMax: 1.5,
|
||||
VivaldiCE: 0.25,
|
||||
VivaldiCC: 0.25,
|
||||
AdjustmentWindowSize: 20,
|
||||
HeightMin: 10.0e-6,
|
||||
LatencyFilterSize: 3,
|
||||
GravityRho: 150.0,
|
||||
}
|
||||
}
|
183
vendor/github.com/hashicorp/serf/coordinate/coordinate.go
generated
vendored
Normal file
183
vendor/github.com/hashicorp/serf/coordinate/coordinate.go
generated
vendored
Normal file
|
@ -0,0 +1,183 @@
|
|||
package coordinate
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Coordinate is a specialized structure for holding network coordinates for the
|
||||
// Vivaldi-based coordinate mapping algorithm. All of the fields should be public
|
||||
// to enable this to be serialized. All values in here are in units of seconds.
|
||||
type Coordinate struct {
|
||||
// Vec is the Euclidean portion of the coordinate. This is used along
|
||||
// with the other fields to provide an overall distance estimate. The
|
||||
// units here are seconds.
|
||||
Vec []float64
|
||||
|
||||
// Err reflects the confidence in the given coordinate and is updated
|
||||
// dynamically by the Vivaldi Client. This is dimensionless.
|
||||
Error float64
|
||||
|
||||
// Adjustment is a distance offset computed based on a calculation over
|
||||
// observations from all other nodes over a fixed window and is updated
|
||||
// dynamically by the Vivaldi Client. The units here are seconds.
|
||||
Adjustment float64
|
||||
|
||||
// Height is a distance offset that accounts for non-Euclidean effects
|
||||
// which model the access links from nodes to the core Internet. The access
|
||||
// links are usually set by bandwidth and congestion, and the core links
|
||||
// usually follow distance based on geography.
|
||||
Height float64
|
||||
}
|
||||
|
||||
const (
|
||||
// secondsToNanoseconds is used to convert float seconds to nanoseconds.
|
||||
secondsToNanoseconds = 1.0e9
|
||||
|
||||
// zeroThreshold is used to decide if two coordinates are on top of each
|
||||
// other.
|
||||
zeroThreshold = 1.0e-6
|
||||
)
|
||||
|
||||
// ErrDimensionalityConflict will be panic-d if you try to perform operations
|
||||
// with incompatible dimensions.
|
||||
type DimensionalityConflictError struct{}
|
||||
|
||||
// Adds the error interface.
|
||||
func (e DimensionalityConflictError) Error() string {
|
||||
return "coordinate dimensionality does not match"
|
||||
}
|
||||
|
||||
// NewCoordinate creates a new coordinate at the origin, using the given config
|
||||
// to supply key initial values.
|
||||
func NewCoordinate(config *Config) *Coordinate {
|
||||
return &Coordinate{
|
||||
Vec: make([]float64, config.Dimensionality),
|
||||
Error: config.VivaldiErrorMax,
|
||||
Adjustment: 0.0,
|
||||
Height: config.HeightMin,
|
||||
}
|
||||
}
|
||||
|
||||
// Clone creates an independent copy of this coordinate.
|
||||
func (c *Coordinate) Clone() *Coordinate {
|
||||
vec := make([]float64, len(c.Vec))
|
||||
copy(vec, c.Vec)
|
||||
return &Coordinate{
|
||||
Vec: vec,
|
||||
Error: c.Error,
|
||||
Adjustment: c.Adjustment,
|
||||
Height: c.Height,
|
||||
}
|
||||
}
|
||||
|
||||
// IsCompatibleWith checks to see if the two coordinates are compatible
|
||||
// dimensionally. If this returns true then you are guaranteed to not get
|
||||
// any runtime errors operating on them.
|
||||
func (c *Coordinate) IsCompatibleWith(other *Coordinate) bool {
|
||||
return len(c.Vec) == len(other.Vec)
|
||||
}
|
||||
|
||||
// ApplyForce returns the result of applying the force from the direction of the
|
||||
// other coordinate.
|
||||
func (c *Coordinate) ApplyForce(config *Config, force float64, other *Coordinate) *Coordinate {
|
||||
if !c.IsCompatibleWith(other) {
|
||||
panic(DimensionalityConflictError{})
|
||||
}
|
||||
|
||||
ret := c.Clone()
|
||||
unit, mag := unitVectorAt(c.Vec, other.Vec)
|
||||
ret.Vec = add(ret.Vec, mul(unit, force))
|
||||
if mag > zeroThreshold {
|
||||
ret.Height = (ret.Height+other.Height)*force/mag + ret.Height
|
||||
ret.Height = math.Max(ret.Height, config.HeightMin)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// DistanceTo returns the distance between this coordinate and the other
|
||||
// coordinate, including adjustments.
|
||||
func (c *Coordinate) DistanceTo(other *Coordinate) time.Duration {
|
||||
if !c.IsCompatibleWith(other) {
|
||||
panic(DimensionalityConflictError{})
|
||||
}
|
||||
|
||||
dist := c.rawDistanceTo(other)
|
||||
adjustedDist := dist + c.Adjustment + other.Adjustment
|
||||
if adjustedDist > 0.0 {
|
||||
dist = adjustedDist
|
||||
}
|
||||
return time.Duration(dist * secondsToNanoseconds)
|
||||
}
|
||||
|
||||
// rawDistanceTo returns the Vivaldi distance between this coordinate and the
|
||||
// other coordinate in seconds, not including adjustments. This assumes the
|
||||
// dimensions have already been checked to be compatible.
|
||||
func (c *Coordinate) rawDistanceTo(other *Coordinate) float64 {
|
||||
return magnitude(diff(c.Vec, other.Vec)) + c.Height + other.Height
|
||||
}
|
||||
|
||||
// add returns the sum of vec1 and vec2. This assumes the dimensions have
|
||||
// already been checked to be compatible.
|
||||
func add(vec1 []float64, vec2 []float64) []float64 {
|
||||
ret := make([]float64, len(vec1))
|
||||
for i, _ := range ret {
|
||||
ret[i] = vec1[i] + vec2[i]
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// diff returns the difference between the vec1 and vec2. This assumes the
|
||||
// dimensions have already been checked to be compatible.
|
||||
func diff(vec1 []float64, vec2 []float64) []float64 {
|
||||
ret := make([]float64, len(vec1))
|
||||
for i, _ := range ret {
|
||||
ret[i] = vec1[i] - vec2[i]
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// mul returns vec multiplied by a scalar factor.
|
||||
func mul(vec []float64, factor float64) []float64 {
|
||||
ret := make([]float64, len(vec))
|
||||
for i, _ := range vec {
|
||||
ret[i] = vec[i] * factor
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// magnitude computes the magnitude of the vec.
|
||||
func magnitude(vec []float64) float64 {
|
||||
sum := 0.0
|
||||
for i, _ := range vec {
|
||||
sum += vec[i] * vec[i]
|
||||
}
|
||||
return math.Sqrt(sum)
|
||||
}
|
||||
|
||||
// unitVectorAt returns a unit vector pointing at vec1 from vec2. If the two
|
||||
// positions are the same then a random unit vector is returned. We also return
|
||||
// the distance between the points for use in the later height calculation.
|
||||
func unitVectorAt(vec1 []float64, vec2 []float64) ([]float64, float64) {
|
||||
ret := diff(vec1, vec2)
|
||||
|
||||
// If the coordinates aren't on top of each other we can normalize.
|
||||
if mag := magnitude(ret); mag > zeroThreshold {
|
||||
return mul(ret, 1.0/mag), mag
|
||||
}
|
||||
|
||||
// Otherwise, just return a random unit vector.
|
||||
for i, _ := range ret {
|
||||
ret[i] = rand.Float64() - 0.5
|
||||
}
|
||||
if mag := magnitude(ret); mag > zeroThreshold {
|
||||
return mul(ret, 1.0/mag), 0.0
|
||||
}
|
||||
|
||||
// And finally just give up and make a unit vector along the first
|
||||
// dimension. This should be exceedingly rare.
|
||||
ret = make([]float64, len(ret))
|
||||
ret[0] = 1.0
|
||||
return ret, 0.0
|
||||
}
|
187
vendor/github.com/hashicorp/serf/coordinate/phantom.go
generated
vendored
Normal file
187
vendor/github.com/hashicorp/serf/coordinate/phantom.go
generated
vendored
Normal file
|
@ -0,0 +1,187 @@
|
|||
package coordinate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GenerateClients returns a slice with nodes number of clients, all with the
|
||||
// given config.
|
||||
func GenerateClients(nodes int, config *Config) ([]*Client, error) {
|
||||
clients := make([]*Client, nodes)
|
||||
for i, _ := range clients {
|
||||
client, err := NewClient(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clients[i] = client
|
||||
}
|
||||
return clients, nil
|
||||
}
|
||||
|
||||
// GenerateLine returns a truth matrix as if all the nodes are in a straight linke
|
||||
// with the given spacing between them.
|
||||
func GenerateLine(nodes int, spacing time.Duration) [][]time.Duration {
|
||||
truth := make([][]time.Duration, nodes)
|
||||
for i := range truth {
|
||||
truth[i] = make([]time.Duration, nodes)
|
||||
}
|
||||
|
||||
for i := 0; i < nodes; i++ {
|
||||
for j := i + 1; j < nodes; j++ {
|
||||
rtt := time.Duration(j-i) * spacing
|
||||
truth[i][j], truth[j][i] = rtt, rtt
|
||||
}
|
||||
}
|
||||
return truth
|
||||
}
|
||||
|
||||
// GenerateGrid returns a truth matrix as if all the nodes are in a two dimensional
|
||||
// grid with the given spacing between them.
|
||||
func GenerateGrid(nodes int, spacing time.Duration) [][]time.Duration {
|
||||
truth := make([][]time.Duration, nodes)
|
||||
for i := range truth {
|
||||
truth[i] = make([]time.Duration, nodes)
|
||||
}
|
||||
|
||||
n := int(math.Sqrt(float64(nodes)))
|
||||
for i := 0; i < nodes; i++ {
|
||||
for j := i + 1; j < nodes; j++ {
|
||||
x1, y1 := float64(i%n), float64(i/n)
|
||||
x2, y2 := float64(j%n), float64(j/n)
|
||||
dx, dy := x2-x1, y2-y1
|
||||
dist := math.Sqrt(dx*dx + dy*dy)
|
||||
rtt := time.Duration(dist * float64(spacing))
|
||||
truth[i][j], truth[j][i] = rtt, rtt
|
||||
}
|
||||
}
|
||||
return truth
|
||||
}
|
||||
|
||||
// GenerateSplit returns a truth matrix as if half the nodes are close together in
|
||||
// one location and half the nodes are close together in another. The lan factor
|
||||
// is used to separate the nodes locally and the wan factor represents the split
|
||||
// between the two sides.
|
||||
func GenerateSplit(nodes int, lan time.Duration, wan time.Duration) [][]time.Duration {
|
||||
truth := make([][]time.Duration, nodes)
|
||||
for i := range truth {
|
||||
truth[i] = make([]time.Duration, nodes)
|
||||
}
|
||||
|
||||
split := nodes / 2
|
||||
for i := 0; i < nodes; i++ {
|
||||
for j := i + 1; j < nodes; j++ {
|
||||
rtt := lan
|
||||
if (i <= split && j > split) || (i > split && j <= split) {
|
||||
rtt += wan
|
||||
}
|
||||
truth[i][j], truth[j][i] = rtt, rtt
|
||||
}
|
||||
}
|
||||
return truth
|
||||
}
|
||||
|
||||
// GenerateCircle returns a truth matrix for a set of nodes, evenly distributed
|
||||
// around a circle with the given radius. The first node is at the "center" of the
|
||||
// circle because it's equidistant from all the other nodes, but we place it at
|
||||
// double the radius, so it should show up above all the other nodes in height.
|
||||
func GenerateCircle(nodes int, radius time.Duration) [][]time.Duration {
|
||||
truth := make([][]time.Duration, nodes)
|
||||
for i := range truth {
|
||||
truth[i] = make([]time.Duration, nodes)
|
||||
}
|
||||
|
||||
for i := 0; i < nodes; i++ {
|
||||
for j := i + 1; j < nodes; j++ {
|
||||
var rtt time.Duration
|
||||
if i == 0 {
|
||||
rtt = 2 * radius
|
||||
} else {
|
||||
t1 := 2.0 * math.Pi * float64(i) / float64(nodes)
|
||||
x1, y1 := math.Cos(t1), math.Sin(t1)
|
||||
t2 := 2.0 * math.Pi * float64(j) / float64(nodes)
|
||||
x2, y2 := math.Cos(t2), math.Sin(t2)
|
||||
dx, dy := x2-x1, y2-y1
|
||||
dist := math.Sqrt(dx*dx + dy*dy)
|
||||
rtt = time.Duration(dist * float64(radius))
|
||||
}
|
||||
truth[i][j], truth[j][i] = rtt, rtt
|
||||
}
|
||||
}
|
||||
return truth
|
||||
}
|
||||
|
||||
// GenerateRandom returns a truth matrix for a set of nodes with normally
|
||||
// distributed delays, with the given mean and deviation. The RNG is re-seeded
|
||||
// so you always get the same matrix for a given size.
|
||||
func GenerateRandom(nodes int, mean time.Duration, deviation time.Duration) [][]time.Duration {
|
||||
rand.Seed(1)
|
||||
|
||||
truth := make([][]time.Duration, nodes)
|
||||
for i := range truth {
|
||||
truth[i] = make([]time.Duration, nodes)
|
||||
}
|
||||
|
||||
for i := 0; i < nodes; i++ {
|
||||
for j := i + 1; j < nodes; j++ {
|
||||
rttSeconds := rand.NormFloat64()*deviation.Seconds() + mean.Seconds()
|
||||
rtt := time.Duration(rttSeconds * secondsToNanoseconds)
|
||||
truth[i][j], truth[j][i] = rtt, rtt
|
||||
}
|
||||
}
|
||||
return truth
|
||||
}
|
||||
|
||||
// Simulate runs the given number of cycles using the given list of clients and
|
||||
// truth matrix. On each cycle, each client will pick a random node and observe
|
||||
// the truth RTT, updating its coordinate estimate. The RNG is re-seeded for
|
||||
// each simulation run to get deterministic results (for this algorithm and the
|
||||
// underlying algorithm which will use random numbers for position vectors when
|
||||
// starting out with everything at the origin).
|
||||
func Simulate(clients []*Client, truth [][]time.Duration, cycles int) {
|
||||
rand.Seed(1)
|
||||
|
||||
nodes := len(clients)
|
||||
for cycle := 0; cycle < cycles; cycle++ {
|
||||
for i, _ := range clients {
|
||||
if j := rand.Intn(nodes); j != i {
|
||||
c := clients[j].GetCoordinate()
|
||||
rtt := truth[i][j]
|
||||
node := fmt.Sprintf("node_%d", j)
|
||||
clients[i].Update(node, c, rtt)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stats is returned from the Evaluate function with a summary of the algorithm
|
||||
// performance.
|
||||
type Stats struct {
|
||||
ErrorMax float64
|
||||
ErrorAvg float64
|
||||
}
|
||||
|
||||
// Evaluate uses the coordinates of the given clients to calculate estimated
|
||||
// distances and compares them with the given truth matrix, returning summary
|
||||
// stats.
|
||||
func Evaluate(clients []*Client, truth [][]time.Duration) (stats Stats) {
|
||||
nodes := len(clients)
|
||||
count := 0
|
||||
for i := 0; i < nodes; i++ {
|
||||
for j := i + 1; j < nodes; j++ {
|
||||
est := clients[i].DistanceTo(clients[j].GetCoordinate()).Seconds()
|
||||
actual := truth[i][j].Seconds()
|
||||
error := math.Abs(est-actual) / actual
|
||||
stats.ErrorMax = math.Max(stats.ErrorMax, error)
|
||||
stats.ErrorAvg += error
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
|
||||
stats.ErrorAvg /= float64(count)
|
||||
fmt.Printf("Error avg=%9.6f max=%9.6f\n", stats.ErrorAvg, stats.ErrorMax)
|
||||
return
|
||||
}
|
44
vendor/github.com/hashicorp/serf/main.go
generated
vendored
Normal file
44
vendor/github.com/hashicorp/serf/main.go
generated
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/cli"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
os.Exit(realMain())
|
||||
}
|
||||
|
||||
func realMain() int {
|
||||
log.SetOutput(ioutil.Discard)
|
||||
|
||||
// Get the command line args. We shortcut "--version" and "-v" to
|
||||
// just show the version.
|
||||
args := os.Args[1:]
|
||||
for _, arg := range args {
|
||||
if arg == "-v" || arg == "--version" {
|
||||
newArgs := make([]string, len(args)+1)
|
||||
newArgs[0] = "version"
|
||||
copy(newArgs[1:], args)
|
||||
args = newArgs
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
cli := &cli.CLI{
|
||||
Args: args,
|
||||
Commands: Commands,
|
||||
HelpFunc: cli.BasicHelpFunc("serf"),
|
||||
}
|
||||
|
||||
exitCode, err := cli.Run()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
return exitCode
|
||||
}
|
27
vendor/github.com/hashicorp/serf/serf/broadcast.go
generated
vendored
Normal file
27
vendor/github.com/hashicorp/serf/serf/broadcast.go
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
package serf
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/memberlist"
|
||||
)
|
||||
|
||||
// broadcast is an implementation of memberlist.Broadcast and is used
|
||||
// to manage broadcasts across the memberlist channel that are related
|
||||
// only to Serf.
|
||||
type broadcast struct {
|
||||
msg []byte
|
||||
notify chan<- struct{}
|
||||
}
|
||||
|
||||
func (b *broadcast) Invalidates(other memberlist.Broadcast) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *broadcast) Message() []byte {
|
||||
return b.msg
|
||||
}
|
||||
|
||||
func (b *broadcast) Finished() {
|
||||
if b.notify != nil {
|
||||
close(b.notify)
|
||||
}
|
||||
}
|
80
vendor/github.com/hashicorp/serf/serf/coalesce.go
generated
vendored
Normal file
80
vendor/github.com/hashicorp/serf/serf/coalesce.go
generated
vendored
Normal file
|
@ -0,0 +1,80 @@
|
|||
package serf
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// coalescer is a simple interface that must be implemented to be
|
||||
// used inside of a coalesceLoop
|
||||
type coalescer interface {
|
||||
// Can the coalescer handle this event, if not it is
|
||||
// directly passed through to the destination channel
|
||||
Handle(Event) bool
|
||||
|
||||
// Invoked to coalesce the given event
|
||||
Coalesce(Event)
|
||||
|
||||
// Invoked to flush the coalesced events
|
||||
Flush(outChan chan<- Event)
|
||||
}
|
||||
|
||||
// coalescedEventCh returns an event channel where the events are coalesced
|
||||
// using the given coalescer.
|
||||
func coalescedEventCh(outCh chan<- Event, shutdownCh <-chan struct{},
|
||||
cPeriod time.Duration, qPeriod time.Duration, c coalescer) chan<- Event {
|
||||
inCh := make(chan Event, 1024)
|
||||
go coalesceLoop(inCh, outCh, shutdownCh, cPeriod, qPeriod, c)
|
||||
return inCh
|
||||
}
|
||||
|
||||
// coalesceLoop is a simple long-running routine that manages the high-level
|
||||
// flow of coalescing based on quiescence and a maximum quantum period.
|
||||
func coalesceLoop(inCh <-chan Event, outCh chan<- Event, shutdownCh <-chan struct{},
|
||||
coalescePeriod time.Duration, quiescentPeriod time.Duration, c coalescer) {
|
||||
var quiescent <-chan time.Time
|
||||
var quantum <-chan time.Time
|
||||
shutdown := false
|
||||
|
||||
INGEST:
|
||||
// Reset the timers
|
||||
quantum = nil
|
||||
quiescent = nil
|
||||
|
||||
for {
|
||||
select {
|
||||
case e := <-inCh:
|
||||
// Ignore any non handled events
|
||||
if !c.Handle(e) {
|
||||
outCh <- e
|
||||
continue
|
||||
}
|
||||
|
||||
// Start a new quantum if we need to
|
||||
// and restart the quiescent timer
|
||||
if quantum == nil {
|
||||
quantum = time.After(coalescePeriod)
|
||||
}
|
||||
quiescent = time.After(quiescentPeriod)
|
||||
|
||||
// Coalesce the event
|
||||
c.Coalesce(e)
|
||||
|
||||
case <-quantum:
|
||||
goto FLUSH
|
||||
case <-quiescent:
|
||||
goto FLUSH
|
||||
case <-shutdownCh:
|
||||
shutdown = true
|
||||
goto FLUSH
|
||||
}
|
||||
}
|
||||
|
||||
FLUSH:
|
||||
// Flush the coalesced events
|
||||
c.Flush(outCh)
|
||||
|
||||
// Restart ingestion if we are not done
|
||||
if !shutdown {
|
||||
goto INGEST
|
||||
}
|
||||
}
|
68
vendor/github.com/hashicorp/serf/serf/coalesce_member.go
generated
vendored
Normal file
68
vendor/github.com/hashicorp/serf/serf/coalesce_member.go
generated
vendored
Normal file
|
@ -0,0 +1,68 @@
|
|||
package serf
|
||||
|
||||
type coalesceEvent struct {
|
||||
Type EventType
|
||||
Member *Member
|
||||
}
|
||||
|
||||
type memberEventCoalescer struct {
|
||||
lastEvents map[string]EventType
|
||||
latestEvents map[string]coalesceEvent
|
||||
}
|
||||
|
||||
func (c *memberEventCoalescer) Handle(e Event) bool {
|
||||
switch e.EventType() {
|
||||
case EventMemberJoin:
|
||||
return true
|
||||
case EventMemberLeave:
|
||||
return true
|
||||
case EventMemberFailed:
|
||||
return true
|
||||
case EventMemberUpdate:
|
||||
return true
|
||||
case EventMemberReap:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (c *memberEventCoalescer) Coalesce(raw Event) {
|
||||
e := raw.(MemberEvent)
|
||||
for _, m := range e.Members {
|
||||
c.latestEvents[m.Name] = coalesceEvent{
|
||||
Type: e.Type,
|
||||
Member: &m,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *memberEventCoalescer) Flush(outCh chan<- Event) {
|
||||
// Coalesce the various events we got into a single set of events.
|
||||
events := make(map[EventType]*MemberEvent)
|
||||
for name, cevent := range c.latestEvents {
|
||||
previous, ok := c.lastEvents[name]
|
||||
|
||||
// If we sent the same event before, then ignore
|
||||
// unless it is a MemberUpdate
|
||||
if ok && previous == cevent.Type && cevent.Type != EventMemberUpdate {
|
||||
continue
|
||||
}
|
||||
|
||||
// Update our last event
|
||||
c.lastEvents[name] = cevent.Type
|
||||
|
||||
// Add it to our event
|
||||
newEvent, ok := events[cevent.Type]
|
||||
if !ok {
|
||||
newEvent = &MemberEvent{Type: cevent.Type}
|
||||
events[cevent.Type] = newEvent
|
||||
}
|
||||
newEvent.Members = append(newEvent.Members, *cevent.Member)
|
||||
}
|
||||
|
||||
// Send out those events
|
||||
for _, event := range events {
|
||||
outCh <- *event
|
||||
}
|
||||
}
|
52
vendor/github.com/hashicorp/serf/serf/coalesce_user.go
generated
vendored
Normal file
52
vendor/github.com/hashicorp/serf/serf/coalesce_user.go
generated
vendored
Normal file
|
@ -0,0 +1,52 @@
|
|||
package serf
|
||||
|
||||
type latestUserEvents struct {
|
||||
LTime LamportTime
|
||||
Events []Event
|
||||
}
|
||||
|
||||
type userEventCoalescer struct {
|
||||
// Maps an event name into the latest versions
|
||||
events map[string]*latestUserEvents
|
||||
}
|
||||
|
||||
func (c *userEventCoalescer) Handle(e Event) bool {
|
||||
// Only handle EventUser messages
|
||||
if e.EventType() != EventUser {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if coalescing is enabled
|
||||
user := e.(UserEvent)
|
||||
return user.Coalesce
|
||||
}
|
||||
|
||||
func (c *userEventCoalescer) Coalesce(e Event) {
|
||||
user := e.(UserEvent)
|
||||
latest, ok := c.events[user.Name]
|
||||
|
||||
// Create a new entry if there are none, or
|
||||
// if this message has the newest LTime
|
||||
if !ok || latest.LTime < user.LTime {
|
||||
latest = &latestUserEvents{
|
||||
LTime: user.LTime,
|
||||
Events: []Event{e},
|
||||
}
|
||||
c.events[user.Name] = latest
|
||||
return
|
||||
}
|
||||
|
||||
// If the the same age, save it
|
||||
if latest.LTime == user.LTime {
|
||||
latest.Events = append(latest.Events, e)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *userEventCoalescer) Flush(outChan chan<- Event) {
|
||||
for _, latest := range c.events {
|
||||
for _, e := range latest.Events {
|
||||
outChan <- e
|
||||
}
|
||||
}
|
||||
c.events = make(map[string]*latestUserEvents)
|
||||
}
|
259
vendor/github.com/hashicorp/serf/serf/config.go
generated
vendored
Normal file
259
vendor/github.com/hashicorp/serf/serf/config.go
generated
vendored
Normal file
|
@ -0,0 +1,259 @@
|
|||
package serf
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/memberlist"
|
||||
)
|
||||
|
||||
// ProtocolVersionMap is the mapping of Serf delegate protocol versions
|
||||
// to memberlist protocol versions. We mask the memberlist protocols using
|
||||
// our own protocol version.
|
||||
var ProtocolVersionMap map[uint8]uint8
|
||||
|
||||
func init() {
|
||||
ProtocolVersionMap = map[uint8]uint8{
|
||||
4: 2,
|
||||
3: 2,
|
||||
2: 2,
|
||||
}
|
||||
}
|
||||
|
||||
// Config is the configuration for creating a Serf instance.
|
||||
type Config struct {
|
||||
// The name of this node. This must be unique in the cluster. If this
|
||||
// is not set, Serf will set it to the hostname of the running machine.
|
||||
NodeName string
|
||||
|
||||
// The tags for this role, if any. This is used to provide arbitrary
|
||||
// key/value metadata per-node. For example, a "role" tag may be used to
|
||||
// differentiate "load-balancer" from a "web" role as parts of the same cluster.
|
||||
// Tags are deprecating 'Role', and instead it acts as a special key in this
|
||||
// map.
|
||||
Tags map[string]string
|
||||
|
||||
// EventCh is a channel that receives all the Serf events. The events
|
||||
// are sent on this channel in proper ordering. Care must be taken that
|
||||
// this channel doesn't block, either by processing the events quick
|
||||
// enough or buffering the channel, otherwise it can block state updates
|
||||
// within Serf itself. If no EventCh is specified, no events will be fired,
|
||||
// but point-in-time snapshots of members can still be retrieved by
|
||||
// calling Members on Serf.
|
||||
EventCh chan<- Event
|
||||
|
||||
// ProtocolVersion is the protocol version to speak. This must be between
|
||||
// ProtocolVersionMin and ProtocolVersionMax.
|
||||
ProtocolVersion uint8
|
||||
|
||||
// BroadcastTimeout is the amount of time to wait for a broadcast
|
||||
// message to be sent to the cluster. Broadcast messages are used for
|
||||
// things like leave messages and force remove messages. If this is not
|
||||
// set, a timeout of 5 seconds will be set.
|
||||
BroadcastTimeout time.Duration
|
||||
|
||||
// The settings below relate to Serf's event coalescence feature. Serf
|
||||
// is able to coalesce multiple events into single events in order to
|
||||
// reduce the amount of noise that is sent along the EventCh. For example
|
||||
// if five nodes quickly join, the EventCh will be sent one EventMemberJoin
|
||||
// containing the five nodes rather than five individual EventMemberJoin
|
||||
// events. Coalescence can mitigate potential flapping behavior.
|
||||
//
|
||||
// Coalescence is disabled by default and can be enabled by setting
|
||||
// CoalescePeriod.
|
||||
//
|
||||
// CoalescePeriod specifies the time duration to coalesce events.
|
||||
// For example, if this is set to 5 seconds, then all events received
|
||||
// within 5 seconds that can be coalesced will be.
|
||||
//
|
||||
// QuiescentPeriod specifies the duration of time where if no events
|
||||
// are received, coalescence immediately happens. For example, if
|
||||
// CoalscePeriod is set to 10 seconds but QuiscentPeriod is set to 2
|
||||
// seconds, then the events will be coalesced and dispatched if no
|
||||
// new events are received within 2 seconds of the last event. Otherwise,
|
||||
// every event will always be delayed by at least 10 seconds.
|
||||
CoalescePeriod time.Duration
|
||||
QuiescentPeriod time.Duration
|
||||
|
||||
// The settings below relate to Serf's user event coalescing feature.
|
||||
// The settings operate like above but only affect user messages and
|
||||
// not the Member* messages that Serf generates.
|
||||
UserCoalescePeriod time.Duration
|
||||
UserQuiescentPeriod time.Duration
|
||||
|
||||
// The settings below relate to Serf keeping track of recently
|
||||
// failed/left nodes and attempting reconnects.
|
||||
//
|
||||
// ReapInterval is the interval when the reaper runs. If this is not
|
||||
// set (it is zero), it will be set to a reasonable default.
|
||||
//
|
||||
// ReconnectInterval is the interval when we attempt to reconnect
|
||||
// to failed nodes. If this is not set (it is zero), it will be set
|
||||
// to a reasonable default.
|
||||
//
|
||||
// ReconnectTimeout is the amount of time to attempt to reconnect to
|
||||
// a failed node before giving up and considering it completely gone.
|
||||
//
|
||||
// TombstoneTimeout is the amount of time to keep around nodes
|
||||
// that gracefully left as tombstones for syncing state with other
|
||||
// Serf nodes.
|
||||
ReapInterval time.Duration
|
||||
ReconnectInterval time.Duration
|
||||
ReconnectTimeout time.Duration
|
||||
TombstoneTimeout time.Duration
|
||||
|
||||
// FlapTimeout is the amount of time less than which we consider a node
|
||||
// being failed and rejoining looks like a flap for telemetry purposes.
|
||||
// This should be set less than a typical reboot time, but large enough
|
||||
// to see actual events, given our expected detection times for a failed
|
||||
// node.
|
||||
FlapTimeout time.Duration
|
||||
|
||||
// QueueDepthWarning is used to generate warning message if the
|
||||
// number of queued messages to broadcast exceeds this number. This
|
||||
// is to provide the user feedback if events are being triggered
|
||||
// faster than they can be disseminated
|
||||
QueueDepthWarning int
|
||||
|
||||
// MaxQueueDepth is used to start dropping messages if the number
|
||||
// of queued messages to broadcast exceeds this number. This is to
|
||||
// prevent an unbounded growth of memory utilization
|
||||
MaxQueueDepth int
|
||||
|
||||
// RecentIntentBuffer is used to set the size of recent join and leave intent
|
||||
// messages that will be buffered. This is used to guard against
|
||||
// the case where Serf broadcasts an intent that arrives before the
|
||||
// Memberlist event. It is important that this not be too small to avoid
|
||||
// continuous rebroadcasting of dead events.
|
||||
RecentIntentBuffer int
|
||||
|
||||
// EventBuffer is used to control how many events are buffered.
|
||||
// This is used to prevent re-delivery of events to a client. The buffer
|
||||
// must be large enough to handle all "recent" events, since Serf will
|
||||
// not deliver messages that are older than the oldest entry in the buffer.
|
||||
// Thus if a client is generating too many events, it's possible that the
|
||||
// buffer gets overrun and messages are not delivered.
|
||||
EventBuffer int
|
||||
|
||||
// QueryBuffer is used to control how many queries are buffered.
|
||||
// This is used to prevent re-delivery of queries to a client. The buffer
|
||||
// must be large enough to handle all "recent" events, since Serf will not
|
||||
// deliver queries older than the oldest entry in the buffer.
|
||||
// Thus if a client is generating too many queries, it's possible that the
|
||||
// buffer gets overrun and messages are not delivered.
|
||||
QueryBuffer int
|
||||
|
||||
// QueryTimeoutMult configures the default timeout multipler for a query to run if no
|
||||
// specific value is provided. Queries are real-time by nature, where the
|
||||
// reply is time sensitive. As a result, results are collected in an async
|
||||
// fashion, however the query must have a bounded duration. We want the timeout
|
||||
// to be long enough that all nodes have time to receive the message, run a handler,
|
||||
// and generate a reply. Once the timeout is exceeded, any further replies are ignored.
|
||||
// The default value is
|
||||
//
|
||||
// Timeout = GossipInterval * QueryTimeoutMult * log(N+1)
|
||||
//
|
||||
QueryTimeoutMult int
|
||||
|
||||
// QueryResponseSizeLimit and QuerySizeLimit limit the inbound and
|
||||
// outbound payload sizes for queries, respectively. These must fit
|
||||
// in a UDP packet with some additional overhead, so tuning these
|
||||
// past the default values of 1024 will depend on your network
|
||||
// configuration.
|
||||
QueryResponseSizeLimit int
|
||||
QuerySizeLimit int
|
||||
|
||||
// MemberlistConfig is the memberlist configuration that Serf will
|
||||
// use to do the underlying membership management and gossip. Some
|
||||
// fields in the MemberlistConfig will be overwritten by Serf no
|
||||
// matter what:
|
||||
//
|
||||
// * Name - This will always be set to the same as the NodeName
|
||||
// in this configuration.
|
||||
//
|
||||
// * Events - Serf uses a custom event delegate.
|
||||
//
|
||||
// * Delegate - Serf uses a custom delegate.
|
||||
//
|
||||
MemberlistConfig *memberlist.Config
|
||||
|
||||
// LogOutput is the location to write logs to. If this is not set,
|
||||
// logs will go to stderr.
|
||||
LogOutput io.Writer
|
||||
|
||||
// SnapshotPath if provided is used to snapshot live nodes as well
|
||||
// as lamport clock values. When Serf is started with a snapshot,
|
||||
// it will attempt to join all the previously known nodes until one
|
||||
// succeeds and will also avoid replaying old user events.
|
||||
SnapshotPath string
|
||||
|
||||
// RejoinAfterLeave controls our interaction with the snapshot file.
|
||||
// When set to false (default), a leave causes a Serf to not rejoin
|
||||
// the cluster until an explicit join is received. If this is set to
|
||||
// true, we ignore the leave, and rejoin the cluster on start.
|
||||
RejoinAfterLeave bool
|
||||
|
||||
// EnableNameConflictResolution controls if Serf will actively attempt
|
||||
// to resolve a name conflict. Since each Serf member must have a unique
|
||||
// name, a cluster can run into issues if multiple nodes claim the same
|
||||
// name. Without automatic resolution, Serf merely logs some warnings, but
|
||||
// otherwise does not take any action. Automatic resolution detects the
|
||||
// conflict and issues a special query which asks the cluster for the
|
||||
// Name -> IP:Port mapping. If there is a simple majority of votes, that
|
||||
// node stays while the other node will leave the cluster and exit.
|
||||
EnableNameConflictResolution bool
|
||||
|
||||
// DisableCoordinates controls if Serf will maintain an estimate of this
|
||||
// node's network coordinate internally. A network coordinate is useful
|
||||
// for estimating the network distance (i.e. round trip time) between
|
||||
// two nodes. Enabling this option adds some overhead to ping messages.
|
||||
DisableCoordinates bool
|
||||
|
||||
// KeyringFile provides the location of a writable file where Serf can
|
||||
// persist changes to the encryption keyring.
|
||||
KeyringFile string
|
||||
|
||||
// Merge can be optionally provided to intercept a cluster merge
|
||||
// and conditionally abort the merge.
|
||||
Merge MergeDelegate
|
||||
}
|
||||
|
||||
// Init allocates the subdata structures
|
||||
func (c *Config) Init() {
|
||||
if c.Tags == nil {
|
||||
c.Tags = make(map[string]string)
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultConfig returns a Config struct that contains reasonable defaults
|
||||
// for most of the configurations.
|
||||
func DefaultConfig() *Config {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &Config{
|
||||
NodeName: hostname,
|
||||
BroadcastTimeout: 5 * time.Second,
|
||||
EventBuffer: 512,
|
||||
QueryBuffer: 512,
|
||||
LogOutput: os.Stderr,
|
||||
ProtocolVersion: ProtocolVersionMax,
|
||||
ReapInterval: 15 * time.Second,
|
||||
RecentIntentBuffer: 128,
|
||||
ReconnectInterval: 30 * time.Second,
|
||||
ReconnectTimeout: 24 * time.Hour,
|
||||
QueueDepthWarning: 128,
|
||||
MaxQueueDepth: 4096,
|
||||
TombstoneTimeout: 24 * time.Hour,
|
||||
FlapTimeout: 60 * time.Second,
|
||||
MemberlistConfig: memberlist.DefaultLANConfig(),
|
||||
QueryTimeoutMult: 16,
|
||||
QueryResponseSizeLimit: 1024,
|
||||
QuerySizeLimit: 1024,
|
||||
EnableNameConflictResolution: true,
|
||||
DisableCoordinates: false,
|
||||
}
|
||||
}
|
13
vendor/github.com/hashicorp/serf/serf/conflict_delegate.go
generated
vendored
Normal file
13
vendor/github.com/hashicorp/serf/serf/conflict_delegate.go
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
package serf
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/memberlist"
|
||||
)
|
||||
|
||||
type conflictDelegate struct {
|
||||
serf *Serf
|
||||
}
|
||||
|
||||
func (c *conflictDelegate) NotifyConflict(existing, other *memberlist.Node) {
|
||||
c.serf.handleNodeConflict(existing, other)
|
||||
}
|
254
vendor/github.com/hashicorp/serf/serf/delegate.go
generated
vendored
Normal file
254
vendor/github.com/hashicorp/serf/serf/delegate.go
generated
vendored
Normal file
|
@ -0,0 +1,254 @@
|
|||
package serf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/armon/go-metrics"
|
||||
)
|
||||
|
||||
// delegate is the memberlist.Delegate implementation that Serf uses.
|
||||
type delegate struct {
|
||||
serf *Serf
|
||||
}
|
||||
|
||||
func (d *delegate) NodeMeta(limit int) []byte {
|
||||
roleBytes := d.serf.encodeTags(d.serf.config.Tags)
|
||||
if len(roleBytes) > limit {
|
||||
panic(fmt.Errorf("Node tags '%v' exceeds length limit of %d bytes", d.serf.config.Tags, limit))
|
||||
}
|
||||
|
||||
return roleBytes
|
||||
}
|
||||
|
||||
func (d *delegate) NotifyMsg(buf []byte) {
|
||||
// If we didn't actually receive any data, then ignore it.
|
||||
if len(buf) == 0 {
|
||||
return
|
||||
}
|
||||
metrics.AddSample([]string{"serf", "msgs", "received"}, float32(len(buf)))
|
||||
|
||||
rebroadcast := false
|
||||
rebroadcastQueue := d.serf.broadcasts
|
||||
t := messageType(buf[0])
|
||||
switch t {
|
||||
case messageLeaveType:
|
||||
var leave messageLeave
|
||||
if err := decodeMessage(buf[1:], &leave); err != nil {
|
||||
d.serf.logger.Printf("[ERR] serf: Error decoding leave message: %s", err)
|
||||
break
|
||||
}
|
||||
|
||||
d.serf.logger.Printf("[DEBUG] serf: messageLeaveType: %s", leave.Node)
|
||||
rebroadcast = d.serf.handleNodeLeaveIntent(&leave)
|
||||
|
||||
case messageJoinType:
|
||||
var join messageJoin
|
||||
if err := decodeMessage(buf[1:], &join); err != nil {
|
||||
d.serf.logger.Printf("[ERR] serf: Error decoding join message: %s", err)
|
||||
break
|
||||
}
|
||||
|
||||
d.serf.logger.Printf("[DEBUG] serf: messageJoinType: %s", join.Node)
|
||||
rebroadcast = d.serf.handleNodeJoinIntent(&join)
|
||||
|
||||
case messageUserEventType:
|
||||
var event messageUserEvent
|
||||
if err := decodeMessage(buf[1:], &event); err != nil {
|
||||
d.serf.logger.Printf("[ERR] serf: Error decoding user event message: %s", err)
|
||||
break
|
||||
}
|
||||
|
||||
d.serf.logger.Printf("[DEBUG] serf: messageUserEventType: %s", event.Name)
|
||||
rebroadcast = d.serf.handleUserEvent(&event)
|
||||
rebroadcastQueue = d.serf.eventBroadcasts
|
||||
|
||||
case messageQueryType:
|
||||
var query messageQuery
|
||||
if err := decodeMessage(buf[1:], &query); err != nil {
|
||||
d.serf.logger.Printf("[ERR] serf: Error decoding query message: %s", err)
|
||||
break
|
||||
}
|
||||
|
||||
d.serf.logger.Printf("[DEBUG] serf: messageQueryType: %s", query.Name)
|
||||
rebroadcast = d.serf.handleQuery(&query)
|
||||
rebroadcastQueue = d.serf.queryBroadcasts
|
||||
|
||||
case messageQueryResponseType:
|
||||
var resp messageQueryResponse
|
||||
if err := decodeMessage(buf[1:], &resp); err != nil {
|
||||
d.serf.logger.Printf("[ERR] serf: Error decoding query response message: %s", err)
|
||||
break
|
||||
}
|
||||
|
||||
d.serf.logger.Printf("[DEBUG] serf: messageQueryResponseType: %v", resp.From)
|
||||
d.serf.handleQueryResponse(&resp)
|
||||
|
||||
default:
|
||||
d.serf.logger.Printf("[WARN] serf: Received message of unknown type: %d", t)
|
||||
}
|
||||
|
||||
if rebroadcast {
|
||||
// Copy the buffer since it we cannot rely on the slice not changing
|
||||
newBuf := make([]byte, len(buf))
|
||||
copy(newBuf, buf)
|
||||
|
||||
rebroadcastQueue.QueueBroadcast(&broadcast{
|
||||
msg: newBuf,
|
||||
notify: nil,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (d *delegate) GetBroadcasts(overhead, limit int) [][]byte {
|
||||
msgs := d.serf.broadcasts.GetBroadcasts(overhead, limit)
|
||||
|
||||
// Determine the bytes used already
|
||||
bytesUsed := 0
|
||||
for _, msg := range msgs {
|
||||
lm := len(msg)
|
||||
bytesUsed += lm + overhead
|
||||
metrics.AddSample([]string{"serf", "msgs", "sent"}, float32(lm))
|
||||
}
|
||||
|
||||
// Get any additional query broadcasts
|
||||
queryMsgs := d.serf.queryBroadcasts.GetBroadcasts(overhead, limit-bytesUsed)
|
||||
if queryMsgs != nil {
|
||||
for _, m := range queryMsgs {
|
||||
lm := len(m)
|
||||
bytesUsed += lm + overhead
|
||||
metrics.AddSample([]string{"serf", "msgs", "sent"}, float32(lm))
|
||||
}
|
||||
msgs = append(msgs, queryMsgs...)
|
||||
}
|
||||
|
||||
// Get any additional event broadcasts
|
||||
eventMsgs := d.serf.eventBroadcasts.GetBroadcasts(overhead, limit-bytesUsed)
|
||||
if eventMsgs != nil {
|
||||
for _, m := range eventMsgs {
|
||||
lm := len(m)
|
||||
bytesUsed += lm + overhead
|
||||
metrics.AddSample([]string{"serf", "msgs", "sent"}, float32(lm))
|
||||
}
|
||||
msgs = append(msgs, eventMsgs...)
|
||||
}
|
||||
|
||||
return msgs
|
||||
}
|
||||
|
||||
func (d *delegate) LocalState(join bool) []byte {
|
||||
d.serf.memberLock.RLock()
|
||||
defer d.serf.memberLock.RUnlock()
|
||||
d.serf.eventLock.RLock()
|
||||
defer d.serf.eventLock.RUnlock()
|
||||
|
||||
// Create the message to send
|
||||
pp := messagePushPull{
|
||||
LTime: d.serf.clock.Time(),
|
||||
StatusLTimes: make(map[string]LamportTime, len(d.serf.members)),
|
||||
LeftMembers: make([]string, 0, len(d.serf.leftMembers)),
|
||||
EventLTime: d.serf.eventClock.Time(),
|
||||
Events: d.serf.eventBuffer,
|
||||
QueryLTime: d.serf.queryClock.Time(),
|
||||
}
|
||||
|
||||
// Add all the join LTimes
|
||||
for name, member := range d.serf.members {
|
||||
pp.StatusLTimes[name] = member.statusLTime
|
||||
}
|
||||
|
||||
// Add all the left nodes
|
||||
for _, member := range d.serf.leftMembers {
|
||||
pp.LeftMembers = append(pp.LeftMembers, member.Name)
|
||||
}
|
||||
|
||||
// Encode the push pull state
|
||||
buf, err := encodeMessage(messagePushPullType, &pp)
|
||||
if err != nil {
|
||||
d.serf.logger.Printf("[ERR] serf: Failed to encode local state: %v", err)
|
||||
return nil
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
func (d *delegate) MergeRemoteState(buf []byte, isJoin bool) {
|
||||
// Ensure we have a message
|
||||
if len(buf) == 0 {
|
||||
d.serf.logger.Printf("[ERR] serf: Remote state is zero bytes")
|
||||
return
|
||||
}
|
||||
|
||||
// Check the message type
|
||||
if messageType(buf[0]) != messagePushPullType {
|
||||
d.serf.logger.Printf("[ERR] serf: Remote state has bad type prefix: %v", buf[0])
|
||||
return
|
||||
}
|
||||
|
||||
// Attempt a decode
|
||||
pp := messagePushPull{}
|
||||
if err := decodeMessage(buf[1:], &pp); err != nil {
|
||||
d.serf.logger.Printf("[ERR] serf: Failed to decode remote state: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Witness the Lamport clocks first.
|
||||
// We subtract 1 since no message with that clock has been sent yet
|
||||
if pp.LTime > 0 {
|
||||
d.serf.clock.Witness(pp.LTime - 1)
|
||||
}
|
||||
if pp.EventLTime > 0 {
|
||||
d.serf.eventClock.Witness(pp.EventLTime - 1)
|
||||
}
|
||||
if pp.QueryLTime > 0 {
|
||||
d.serf.queryClock.Witness(pp.QueryLTime - 1)
|
||||
}
|
||||
|
||||
// Process the left nodes first to avoid the LTimes from being increment
|
||||
// in the wrong order
|
||||
leftMap := make(map[string]struct{}, len(pp.LeftMembers))
|
||||
leave := messageLeave{}
|
||||
for _, name := range pp.LeftMembers {
|
||||
leftMap[name] = struct{}{}
|
||||
leave.LTime = pp.StatusLTimes[name]
|
||||
leave.Node = name
|
||||
d.serf.handleNodeLeaveIntent(&leave)
|
||||
}
|
||||
|
||||
// Update any other LTimes
|
||||
join := messageJoin{}
|
||||
for name, statusLTime := range pp.StatusLTimes {
|
||||
// Skip the left nodes
|
||||
if _, ok := leftMap[name]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Create an artificial join message
|
||||
join.LTime = statusLTime
|
||||
join.Node = name
|
||||
d.serf.handleNodeJoinIntent(&join)
|
||||
}
|
||||
|
||||
// If we are doing a join, and eventJoinIgnore is set
|
||||
// then we set the eventMinTime to the EventLTime. This
|
||||
// prevents any of the incoming events from being processed
|
||||
if isJoin && d.serf.eventJoinIgnore {
|
||||
d.serf.eventLock.Lock()
|
||||
if pp.EventLTime > d.serf.eventMinTime {
|
||||
d.serf.eventMinTime = pp.EventLTime
|
||||
}
|
||||
d.serf.eventLock.Unlock()
|
||||
}
|
||||
|
||||
// Process all the events
|
||||
userEvent := messageUserEvent{}
|
||||
for _, events := range pp.Events {
|
||||
if events == nil {
|
||||
continue
|
||||
}
|
||||
userEvent.LTime = events.LTime
|
||||
for _, e := range events.Events {
|
||||
userEvent.Name = e.Name
|
||||
userEvent.Payload = e.Payload
|
||||
d.serf.handleUserEvent(&userEvent)
|
||||
}
|
||||
}
|
||||
}
|
168
vendor/github.com/hashicorp/serf/serf/event.go
generated
vendored
Normal file
168
vendor/github.com/hashicorp/serf/serf/event.go
generated
vendored
Normal file
|
@ -0,0 +1,168 @@
|
|||
package serf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// EventType are all the types of events that may occur and be sent
|
||||
// along the Serf channel.
|
||||
type EventType int
|
||||
|
||||
const (
|
||||
EventMemberJoin EventType = iota
|
||||
EventMemberLeave
|
||||
EventMemberFailed
|
||||
EventMemberUpdate
|
||||
EventMemberReap
|
||||
EventUser
|
||||
EventQuery
|
||||
)
|
||||
|
||||
func (t EventType) String() string {
|
||||
switch t {
|
||||
case EventMemberJoin:
|
||||
return "member-join"
|
||||
case EventMemberLeave:
|
||||
return "member-leave"
|
||||
case EventMemberFailed:
|
||||
return "member-failed"
|
||||
case EventMemberUpdate:
|
||||
return "member-update"
|
||||
case EventMemberReap:
|
||||
return "member-reap"
|
||||
case EventUser:
|
||||
return "user"
|
||||
case EventQuery:
|
||||
return "query"
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown event type: %d", t))
|
||||
}
|
||||
}
|
||||
|
||||
// Event is a generic interface for exposing Serf events
|
||||
// Clients will usually need to use a type switches to get
|
||||
// to a more useful type
|
||||
type Event interface {
|
||||
EventType() EventType
|
||||
String() string
|
||||
}
|
||||
|
||||
// MemberEvent is the struct used for member related events
|
||||
// Because Serf coalesces events, an event may contain multiple members.
|
||||
type MemberEvent struct {
|
||||
Type EventType
|
||||
Members []Member
|
||||
}
|
||||
|
||||
func (m MemberEvent) EventType() EventType {
|
||||
return m.Type
|
||||
}
|
||||
|
||||
func (m MemberEvent) String() string {
|
||||
switch m.Type {
|
||||
case EventMemberJoin:
|
||||
return "member-join"
|
||||
case EventMemberLeave:
|
||||
return "member-leave"
|
||||
case EventMemberFailed:
|
||||
return "member-failed"
|
||||
case EventMemberUpdate:
|
||||
return "member-update"
|
||||
case EventMemberReap:
|
||||
return "member-reap"
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown event type: %d", m.Type))
|
||||
}
|
||||
}
|
||||
|
||||
// UserEvent is the struct used for events that are triggered
|
||||
// by the user and are not related to members
|
||||
type UserEvent struct {
|
||||
LTime LamportTime
|
||||
Name string
|
||||
Payload []byte
|
||||
Coalesce bool
|
||||
}
|
||||
|
||||
func (u UserEvent) EventType() EventType {
|
||||
return EventUser
|
||||
}
|
||||
|
||||
func (u UserEvent) String() string {
|
||||
return fmt.Sprintf("user-event: %s", u.Name)
|
||||
}
|
||||
|
||||
// Query is the struct used EventQuery type events
|
||||
type Query struct {
|
||||
LTime LamportTime
|
||||
Name string
|
||||
Payload []byte
|
||||
|
||||
serf *Serf
|
||||
id uint32 // ID is not exported, since it may change
|
||||
addr []byte // Address to respond to
|
||||
port uint16 // Port to respond to
|
||||
deadline time.Time // Must respond by this deadline
|
||||
respLock sync.Mutex
|
||||
}
|
||||
|
||||
func (q *Query) EventType() EventType {
|
||||
return EventQuery
|
||||
}
|
||||
|
||||
func (q *Query) String() string {
|
||||
return fmt.Sprintf("query: %s", q.Name)
|
||||
}
|
||||
|
||||
// Deadline returns the time by which a response must be sent
|
||||
func (q *Query) Deadline() time.Time {
|
||||
return q.deadline
|
||||
}
|
||||
|
||||
// Respond is used to send a response to the user query
|
||||
func (q *Query) Respond(buf []byte) error {
|
||||
q.respLock.Lock()
|
||||
defer q.respLock.Unlock()
|
||||
|
||||
// Check if we've already responded
|
||||
if q.deadline.IsZero() {
|
||||
return fmt.Errorf("Response already sent")
|
||||
}
|
||||
|
||||
// Ensure we aren't past our response deadline
|
||||
if time.Now().After(q.deadline) {
|
||||
return fmt.Errorf("Response is past the deadline")
|
||||
}
|
||||
|
||||
// Create response
|
||||
resp := messageQueryResponse{
|
||||
LTime: q.LTime,
|
||||
ID: q.id,
|
||||
From: q.serf.config.NodeName,
|
||||
Payload: buf,
|
||||
}
|
||||
|
||||
// Format the response
|
||||
raw, err := encodeMessage(messageQueryResponseType, &resp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to format response: %v", err)
|
||||
}
|
||||
|
||||
// Check the size limit
|
||||
if len(raw) > q.serf.config.QueryResponseSizeLimit {
|
||||
return fmt.Errorf("response exceeds limit of %d bytes", q.serf.config.QueryResponseSizeLimit)
|
||||
}
|
||||
|
||||
// Send the response
|
||||
addr := net.UDPAddr{IP: q.addr, Port: int(q.port)}
|
||||
if err := q.serf.memberlist.SendTo(&addr, raw); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Clera the deadline, response sent
|
||||
q.deadline = time.Time{}
|
||||
return nil
|
||||
}
|
21
vendor/github.com/hashicorp/serf/serf/event_delegate.go
generated
vendored
Normal file
21
vendor/github.com/hashicorp/serf/serf/event_delegate.go
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
package serf
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/memberlist"
|
||||
)
|
||||
|
||||
type eventDelegate struct {
|
||||
serf *Serf
|
||||
}
|
||||
|
||||
func (e *eventDelegate) NotifyJoin(n *memberlist.Node) {
|
||||
e.serf.handleNodeJoin(n)
|
||||
}
|
||||
|
||||
func (e *eventDelegate) NotifyLeave(n *memberlist.Node) {
|
||||
e.serf.handleNodeLeave(n)
|
||||
}
|
||||
|
||||
func (e *eventDelegate) NotifyUpdate(n *memberlist.Node) {
|
||||
e.serf.handleNodeUpdate(n)
|
||||
}
|
312
vendor/github.com/hashicorp/serf/serf/internal_query.go
generated
vendored
Normal file
312
vendor/github.com/hashicorp/serf/serf/internal_query.go
generated
vendored
Normal file
|
@ -0,0 +1,312 @@
|
|||
package serf
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// This is the prefix we use for queries that are internal to Serf.
|
||||
// They are handled internally, and not forwarded to a client.
|
||||
InternalQueryPrefix = "_serf_"
|
||||
|
||||
// pingQuery is run to check for reachability
|
||||
pingQuery = "ping"
|
||||
|
||||
// conflictQuery is run to resolve a name conflict
|
||||
conflictQuery = "conflict"
|
||||
|
||||
// installKeyQuery is used to install a new key
|
||||
installKeyQuery = "install-key"
|
||||
|
||||
// useKeyQuery is used to change the primary encryption key
|
||||
useKeyQuery = "use-key"
|
||||
|
||||
// removeKeyQuery is used to remove a key from the keyring
|
||||
removeKeyQuery = "remove-key"
|
||||
|
||||
// listKeysQuery is used to list all known keys in the cluster
|
||||
listKeysQuery = "list-keys"
|
||||
)
|
||||
|
||||
// internalQueryName is used to generate a query name for an internal query
|
||||
func internalQueryName(name string) string {
|
||||
return InternalQueryPrefix + name
|
||||
}
|
||||
|
||||
// serfQueries is used to listen for queries that start with
|
||||
// _serf and respond to them as appropriate.
|
||||
type serfQueries struct {
|
||||
inCh chan Event
|
||||
logger *log.Logger
|
||||
outCh chan<- Event
|
||||
serf *Serf
|
||||
shutdownCh <-chan struct{}
|
||||
}
|
||||
|
||||
// nodeKeyResponse is used to store the result from an individual node while
|
||||
// replying to key modification queries
|
||||
type nodeKeyResponse struct {
|
||||
// Result indicates true/false if there were errors or not
|
||||
Result bool
|
||||
|
||||
// Message contains error messages or other information
|
||||
Message string
|
||||
|
||||
// Keys is used in listing queries to relay a list of installed keys
|
||||
Keys []string
|
||||
}
|
||||
|
||||
// newSerfQueries is used to create a new serfQueries. We return an event
|
||||
// channel that is ingested and forwarded to an outCh. Any Queries that
|
||||
// have the InternalQueryPrefix are handled instead of forwarded.
|
||||
func newSerfQueries(serf *Serf, logger *log.Logger, outCh chan<- Event, shutdownCh <-chan struct{}) (chan<- Event, error) {
|
||||
inCh := make(chan Event, 1024)
|
||||
q := &serfQueries{
|
||||
inCh: inCh,
|
||||
logger: logger,
|
||||
outCh: outCh,
|
||||
serf: serf,
|
||||
shutdownCh: shutdownCh,
|
||||
}
|
||||
go q.stream()
|
||||
return inCh, nil
|
||||
}
|
||||
|
||||
// stream is a long running routine to ingest the event stream
|
||||
func (s *serfQueries) stream() {
|
||||
for {
|
||||
select {
|
||||
case e := <-s.inCh:
|
||||
// Check if this is a query we should process
|
||||
if q, ok := e.(*Query); ok && strings.HasPrefix(q.Name, InternalQueryPrefix) {
|
||||
go s.handleQuery(q)
|
||||
|
||||
} else if s.outCh != nil {
|
||||
s.outCh <- e
|
||||
}
|
||||
|
||||
case <-s.shutdownCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handleQuery is invoked when we get an internal query
|
||||
func (s *serfQueries) handleQuery(q *Query) {
|
||||
// Get the queryName after the initial prefix
|
||||
queryName := q.Name[len(InternalQueryPrefix):]
|
||||
switch queryName {
|
||||
case pingQuery:
|
||||
// Nothing to do, we will ack the query
|
||||
case conflictQuery:
|
||||
s.handleConflict(q)
|
||||
case installKeyQuery:
|
||||
s.handleInstallKey(q)
|
||||
case useKeyQuery:
|
||||
s.handleUseKey(q)
|
||||
case removeKeyQuery:
|
||||
s.handleRemoveKey(q)
|
||||
case listKeysQuery:
|
||||
s.handleListKeys(q)
|
||||
default:
|
||||
s.logger.Printf("[WARN] serf: Unhandled internal query '%s'", queryName)
|
||||
}
|
||||
}
|
||||
|
||||
// handleConflict is invoked when we get a query that is attempting to
|
||||
// disambiguate a name conflict. They payload is a node name, and the response
|
||||
// should the address we believe that node is at, if any.
|
||||
func (s *serfQueries) handleConflict(q *Query) {
|
||||
// The target node name is the payload
|
||||
node := string(q.Payload)
|
||||
|
||||
// Do not respond to the query if it is about us
|
||||
if node == s.serf.config.NodeName {
|
||||
return
|
||||
}
|
||||
s.logger.Printf("[DEBUG] serf: Got conflict resolution query for '%s'", node)
|
||||
|
||||
// Look for the member info
|
||||
var out *Member
|
||||
s.serf.memberLock.Lock()
|
||||
if member, ok := s.serf.members[node]; ok {
|
||||
out = &member.Member
|
||||
}
|
||||
s.serf.memberLock.Unlock()
|
||||
|
||||
// Encode the response
|
||||
buf, err := encodeMessage(messageConflictResponseType, out)
|
||||
if err != nil {
|
||||
s.logger.Printf("[ERR] serf: Failed to encode conflict query response: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Send our answer
|
||||
if err := q.Respond(buf); err != nil {
|
||||
s.logger.Printf("[ERR] serf: Failed to respond to conflict query: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// sendKeyResponse handles responding to key-related queries.
|
||||
func (s *serfQueries) sendKeyResponse(q *Query, resp *nodeKeyResponse) {
|
||||
buf, err := encodeMessage(messageKeyResponseType, resp)
|
||||
if err != nil {
|
||||
s.logger.Printf("[ERR] serf: Failed to encode key response: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := q.Respond(buf); err != nil {
|
||||
s.logger.Printf("[ERR] serf: Failed to respond to key query: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// handleInstallKey is invoked whenever a new encryption key is received from
|
||||
// another member in the cluster, and handles the process of installing it onto
|
||||
// the memberlist keyring. This type of query may fail if the provided key does
|
||||
// not fit the constraints that memberlist enforces. If the query fails, the
|
||||
// response will contain the error message so that it may be relayed.
|
||||
func (s *serfQueries) handleInstallKey(q *Query) {
|
||||
response := nodeKeyResponse{Result: false}
|
||||
keyring := s.serf.config.MemberlistConfig.Keyring
|
||||
req := keyRequest{}
|
||||
|
||||
err := decodeMessage(q.Payload[1:], &req)
|
||||
if err != nil {
|
||||
s.logger.Printf("[ERR] serf: Failed to decode key request: %v", err)
|
||||
goto SEND
|
||||
}
|
||||
|
||||
if !s.serf.EncryptionEnabled() {
|
||||
response.Message = "No keyring to modify (encryption not enabled)"
|
||||
s.logger.Printf("[ERR] serf: No keyring to modify (encryption not enabled)")
|
||||
goto SEND
|
||||
}
|
||||
|
||||
s.logger.Printf("[INFO] serf: Received install-key query")
|
||||
if err := keyring.AddKey(req.Key); err != nil {
|
||||
response.Message = err.Error()
|
||||
s.logger.Printf("[ERR] serf: Failed to install key: %s", err)
|
||||
goto SEND
|
||||
}
|
||||
|
||||
if err := s.serf.writeKeyringFile(); err != nil {
|
||||
response.Message = err.Error()
|
||||
s.logger.Printf("[ERR] serf: Failed to write keyring file: %s", err)
|
||||
goto SEND
|
||||
}
|
||||
|
||||
response.Result = true
|
||||
|
||||
SEND:
|
||||
s.sendKeyResponse(q, &response)
|
||||
}
|
||||
|
||||
// handleUseKey is invoked whenever a query is received to mark a different key
|
||||
// in the internal keyring as the primary key. This type of query may fail due
|
||||
// to operator error (requested key not in ring), and thus sends error messages
|
||||
// back in the response.
|
||||
func (s *serfQueries) handleUseKey(q *Query) {
|
||||
response := nodeKeyResponse{Result: false}
|
||||
keyring := s.serf.config.MemberlistConfig.Keyring
|
||||
req := keyRequest{}
|
||||
|
||||
err := decodeMessage(q.Payload[1:], &req)
|
||||
if err != nil {
|
||||
s.logger.Printf("[ERR] serf: Failed to decode key request: %v", err)
|
||||
goto SEND
|
||||
}
|
||||
|
||||
if !s.serf.EncryptionEnabled() {
|
||||
response.Message = "No keyring to modify (encryption not enabled)"
|
||||
s.logger.Printf("[ERR] serf: No keyring to modify (encryption not enabled)")
|
||||
goto SEND
|
||||
}
|
||||
|
||||
s.logger.Printf("[INFO] serf: Received use-key query")
|
||||
if err := keyring.UseKey(req.Key); err != nil {
|
||||
response.Message = err.Error()
|
||||
s.logger.Printf("[ERR] serf: Failed to change primary key: %s", err)
|
||||
goto SEND
|
||||
}
|
||||
|
||||
if err := s.serf.writeKeyringFile(); err != nil {
|
||||
response.Message = err.Error()
|
||||
s.logger.Printf("[ERR] serf: Failed to write keyring file: %s", err)
|
||||
goto SEND
|
||||
}
|
||||
|
||||
response.Result = true
|
||||
|
||||
SEND:
|
||||
s.sendKeyResponse(q, &response)
|
||||
}
|
||||
|
||||
// handleRemoveKey is invoked when a query is received to remove a particular
|
||||
// key from the keyring. This type of query can fail if the key requested for
|
||||
// deletion is currently the primary key in the keyring, so therefore it will
|
||||
// reply to the query with any relevant errors from the operation.
|
||||
func (s *serfQueries) handleRemoveKey(q *Query) {
|
||||
response := nodeKeyResponse{Result: false}
|
||||
keyring := s.serf.config.MemberlistConfig.Keyring
|
||||
req := keyRequest{}
|
||||
|
||||
err := decodeMessage(q.Payload[1:], &req)
|
||||
if err != nil {
|
||||
s.logger.Printf("[ERR] serf: Failed to decode key request: %v", err)
|
||||
goto SEND
|
||||
}
|
||||
|
||||
if !s.serf.EncryptionEnabled() {
|
||||
response.Message = "No keyring to modify (encryption not enabled)"
|
||||
s.logger.Printf("[ERR] serf: No keyring to modify (encryption not enabled)")
|
||||
goto SEND
|
||||
}
|
||||
|
||||
s.logger.Printf("[INFO] serf: Received remove-key query")
|
||||
if err := keyring.RemoveKey(req.Key); err != nil {
|
||||
response.Message = err.Error()
|
||||
s.logger.Printf("[ERR] serf: Failed to remove key: %s", err)
|
||||
goto SEND
|
||||
}
|
||||
|
||||
if err := s.serf.writeKeyringFile(); err != nil {
|
||||
response.Message = err.Error()
|
||||
s.logger.Printf("[ERR] serf: Failed to write keyring file: %s", err)
|
||||
goto SEND
|
||||
}
|
||||
|
||||
response.Result = true
|
||||
|
||||
SEND:
|
||||
s.sendKeyResponse(q, &response)
|
||||
}
|
||||
|
||||
// handleListKeys is invoked when a query is received to return a list of all
|
||||
// installed keys the Serf instance knows of. For performance, the keys are
|
||||
// encoded to base64 on each of the members to remove this burden from the
|
||||
// node asking for the results.
|
||||
func (s *serfQueries) handleListKeys(q *Query) {
|
||||
response := nodeKeyResponse{Result: false}
|
||||
keyring := s.serf.config.MemberlistConfig.Keyring
|
||||
|
||||
if !s.serf.EncryptionEnabled() {
|
||||
response.Message = "Keyring is empty (encryption not enabled)"
|
||||
s.logger.Printf("[ERR] serf: Keyring is empty (encryption not enabled)")
|
||||
goto SEND
|
||||
}
|
||||
|
||||
s.logger.Printf("[INFO] serf: Received list-keys query")
|
||||
for _, keyBytes := range keyring.GetKeys() {
|
||||
// Encode the keys before sending the response. This should help take
|
||||
// some the burden of doing this off of the asking member.
|
||||
key := base64.StdEncoding.EncodeToString(keyBytes)
|
||||
response.Keys = append(response.Keys, key)
|
||||
}
|
||||
response.Result = true
|
||||
|
||||
SEND:
|
||||
s.sendKeyResponse(q, &response)
|
||||
}
|
166
vendor/github.com/hashicorp/serf/serf/keymanager.go
generated
vendored
Normal file
166
vendor/github.com/hashicorp/serf/serf/keymanager.go
generated
vendored
Normal file
|
@ -0,0 +1,166 @@
|
|||
package serf
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// KeyManager encapsulates all functionality within Serf for handling
|
||||
// encryption keyring changes across a cluster.
|
||||
type KeyManager struct {
|
||||
serf *Serf
|
||||
|
||||
// Lock to protect read and write operations
|
||||
l sync.RWMutex
|
||||
}
|
||||
|
||||
// keyRequest is used to contain input parameters which get broadcasted to all
|
||||
// nodes as part of a key query operation.
|
||||
type keyRequest struct {
|
||||
Key []byte
|
||||
}
|
||||
|
||||
// KeyResponse is used to relay a query for a list of all keys in use.
|
||||
type KeyResponse struct {
|
||||
Messages map[string]string // Map of node name to response message
|
||||
NumNodes int // Total nodes memberlist knows of
|
||||
NumResp int // Total responses received
|
||||
NumErr int // Total errors from request
|
||||
|
||||
// Keys is a mapping of the base64-encoded value of the key bytes to the
|
||||
// number of nodes that have the key installed.
|
||||
Keys map[string]int
|
||||
}
|
||||
|
||||
// streamKeyResp takes care of reading responses from a channel and composing
|
||||
// them into a KeyResponse. It will update a KeyResponse *in place* and
|
||||
// therefore has nothing to return.
|
||||
func (k *KeyManager) streamKeyResp(resp *KeyResponse, ch <-chan NodeResponse) {
|
||||
for r := range ch {
|
||||
var nodeResponse nodeKeyResponse
|
||||
|
||||
resp.NumResp++
|
||||
|
||||
// Decode the response
|
||||
if len(r.Payload) < 1 || messageType(r.Payload[0]) != messageKeyResponseType {
|
||||
resp.Messages[r.From] = fmt.Sprintf(
|
||||
"Invalid key query response type: %v", r.Payload)
|
||||
resp.NumErr++
|
||||
goto NEXT
|
||||
}
|
||||
if err := decodeMessage(r.Payload[1:], &nodeResponse); err != nil {
|
||||
resp.Messages[r.From] = fmt.Sprintf(
|
||||
"Failed to decode key query response: %v", r.Payload)
|
||||
resp.NumErr++
|
||||
goto NEXT
|
||||
}
|
||||
|
||||
if !nodeResponse.Result {
|
||||
resp.Messages[r.From] = nodeResponse.Message
|
||||
resp.NumErr++
|
||||
}
|
||||
|
||||
// Currently only used for key list queries, this adds keys to a counter
|
||||
// and increments them for each node response which contains them.
|
||||
for _, key := range nodeResponse.Keys {
|
||||
if _, ok := resp.Keys[key]; !ok {
|
||||
resp.Keys[key] = 1
|
||||
} else {
|
||||
resp.Keys[key]++
|
||||
}
|
||||
}
|
||||
|
||||
NEXT:
|
||||
// Return early if all nodes have responded. This allows us to avoid
|
||||
// waiting for the full timeout when there is nothing left to do.
|
||||
if resp.NumResp == resp.NumNodes {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handleKeyRequest performs query broadcasting to all members for any type of
|
||||
// key operation and manages gathering responses and packing them up into a
|
||||
// KeyResponse for uniform response handling.
|
||||
func (k *KeyManager) handleKeyRequest(key, query string) (*KeyResponse, error) {
|
||||
resp := &KeyResponse{
|
||||
Messages: make(map[string]string),
|
||||
Keys: make(map[string]int),
|
||||
}
|
||||
qName := internalQueryName(query)
|
||||
|
||||
// Decode the new key into raw bytes
|
||||
rawKey, err := base64.StdEncoding.DecodeString(key)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// Encode the query request
|
||||
req, err := encodeMessage(messageKeyRequestType, keyRequest{Key: rawKey})
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
qParam := k.serf.DefaultQueryParams()
|
||||
queryResp, err := k.serf.Query(qName, req, qParam)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// Handle the response stream and populate the KeyResponse
|
||||
resp.NumNodes = k.serf.memberlist.NumMembers()
|
||||
k.streamKeyResp(resp, queryResp.respCh)
|
||||
|
||||
// Check the response for any reported failure conditions
|
||||
if resp.NumErr != 0 {
|
||||
return resp, fmt.Errorf("%d/%d nodes reported failure", resp.NumErr, resp.NumNodes)
|
||||
}
|
||||
if resp.NumResp != resp.NumNodes {
|
||||
return resp, fmt.Errorf("%d/%d nodes reported success", resp.NumResp, resp.NumNodes)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// InstallKey handles broadcasting a query to all members and gathering
|
||||
// responses from each of them, returning a list of messages from each node
|
||||
// and any applicable error conditions.
|
||||
func (k *KeyManager) InstallKey(key string) (*KeyResponse, error) {
|
||||
k.l.Lock()
|
||||
defer k.l.Unlock()
|
||||
|
||||
return k.handleKeyRequest(key, installKeyQuery)
|
||||
}
|
||||
|
||||
// UseKey handles broadcasting a primary key change to all members in the
|
||||
// cluster, and gathering any response messages. If successful, there should
|
||||
// be an empty KeyResponse returned.
|
||||
func (k *KeyManager) UseKey(key string) (*KeyResponse, error) {
|
||||
k.l.Lock()
|
||||
defer k.l.Unlock()
|
||||
|
||||
return k.handleKeyRequest(key, useKeyQuery)
|
||||
}
|
||||
|
||||
// RemoveKey handles broadcasting a key to the cluster for removal. Each member
|
||||
// will receive this event, and if they have the key in their keyring, remove
|
||||
// it. If any errors are encountered, RemoveKey will collect and relay them.
|
||||
func (k *KeyManager) RemoveKey(key string) (*KeyResponse, error) {
|
||||
k.l.Lock()
|
||||
defer k.l.Unlock()
|
||||
|
||||
return k.handleKeyRequest(key, removeKeyQuery)
|
||||
}
|
||||
|
||||
// ListKeys is used to collect installed keys from members in a Serf cluster
|
||||
// and return an aggregated list of all installed keys. This is useful to
|
||||
// operators to ensure that there are no lingering keys installed on any agents.
|
||||
// Since having multiple keys installed can cause performance penalties in some
|
||||
// cases, it's important to verify this information and remove unneeded keys.
|
||||
func (k *KeyManager) ListKeys() (*KeyResponse, error) {
|
||||
k.l.RLock()
|
||||
defer k.l.RUnlock()
|
||||
|
||||
return k.handleKeyRequest("", listKeysQuery)
|
||||
}
|
45
vendor/github.com/hashicorp/serf/serf/lamport.go
generated
vendored
Normal file
45
vendor/github.com/hashicorp/serf/serf/lamport.go
generated
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
package serf
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// LamportClock is a thread safe implementation of a lamport clock. It
|
||||
// uses efficient atomic operations for all of its functions, falling back
|
||||
// to a heavy lock only if there are enough CAS failures.
|
||||
type LamportClock struct {
|
||||
counter uint64
|
||||
}
|
||||
|
||||
// LamportTime is the value of a LamportClock.
|
||||
type LamportTime uint64
|
||||
|
||||
// Time is used to return the current value of the lamport clock
|
||||
func (l *LamportClock) Time() LamportTime {
|
||||
return LamportTime(atomic.LoadUint64(&l.counter))
|
||||
}
|
||||
|
||||
// Increment is used to increment and return the value of the lamport clock
|
||||
func (l *LamportClock) Increment() LamportTime {
|
||||
return LamportTime(atomic.AddUint64(&l.counter, 1))
|
||||
}
|
||||
|
||||
// Witness is called to update our local clock if necessary after
|
||||
// witnessing a clock value received from another process
|
||||
func (l *LamportClock) Witness(v LamportTime) {
|
||||
WITNESS:
|
||||
// If the other value is old, we do not need to do anything
|
||||
cur := atomic.LoadUint64(&l.counter)
|
||||
other := uint64(v)
|
||||
if other < cur {
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure that our local clock is at least one ahead.
|
||||
if !atomic.CompareAndSwapUint64(&l.counter, cur, other+1) {
|
||||
// The CAS failed, so we just retry. Eventually our CAS should
|
||||
// succeed or a future witness will pass us by and our witness
|
||||
// will end.
|
||||
goto WITNESS
|
||||
}
|
||||
}
|
44
vendor/github.com/hashicorp/serf/serf/merge_delegate.go
generated
vendored
Normal file
44
vendor/github.com/hashicorp/serf/serf/merge_delegate.go
generated
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
package serf
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/hashicorp/memberlist"
|
||||
)
|
||||
|
||||
type MergeDelegate interface {
|
||||
NotifyMerge([]*Member) error
|
||||
}
|
||||
|
||||
type mergeDelegate struct {
|
||||
serf *Serf
|
||||
}
|
||||
|
||||
func (m *mergeDelegate) NotifyMerge(nodes []*memberlist.Node) error {
|
||||
members := make([]*Member, len(nodes))
|
||||
for idx, n := range nodes {
|
||||
members[idx] = m.nodeToMember(n)
|
||||
}
|
||||
return m.serf.config.Merge.NotifyMerge(members)
|
||||
}
|
||||
|
||||
func (m *mergeDelegate) NotifyAlive(peer *memberlist.Node) error {
|
||||
member := m.nodeToMember(peer)
|
||||
return m.serf.config.Merge.NotifyMerge([]*Member{member})
|
||||
}
|
||||
|
||||
func (m *mergeDelegate) nodeToMember(n *memberlist.Node) *Member {
|
||||
return &Member{
|
||||
Name: n.Name,
|
||||
Addr: net.IP(n.Addr),
|
||||
Port: n.Port,
|
||||
Tags: m.serf.decodeTags(n.Meta),
|
||||
Status: StatusNone,
|
||||
ProtocolMin: n.PMin,
|
||||
ProtocolMax: n.PMax,
|
||||
ProtocolCur: n.PCur,
|
||||
DelegateMin: n.DMin,
|
||||
DelegateMax: n.DMax,
|
||||
DelegateCur: n.DCur,
|
||||
}
|
||||
}
|
147
vendor/github.com/hashicorp/serf/serf/messages.go
generated
vendored
Normal file
147
vendor/github.com/hashicorp/serf/serf/messages.go
generated
vendored
Normal file
|
@ -0,0 +1,147 @@
|
|||
package serf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/hashicorp/go-msgpack/codec"
|
||||
"time"
|
||||
)
|
||||
|
||||
// messageType are the types of gossip messages Serf will send along
|
||||
// memberlist.
|
||||
type messageType uint8
|
||||
|
||||
const (
|
||||
messageLeaveType messageType = iota
|
||||
messageJoinType
|
||||
messagePushPullType
|
||||
messageUserEventType
|
||||
messageQueryType
|
||||
messageQueryResponseType
|
||||
messageConflictResponseType
|
||||
messageKeyRequestType
|
||||
messageKeyResponseType
|
||||
)
|
||||
|
||||
const (
|
||||
// Ack flag is used to force receiver to send an ack back
|
||||
queryFlagAck uint32 = 1 << iota
|
||||
|
||||
// NoBroadcast is used to prevent re-broadcast of a query.
|
||||
// this can be used to selectively send queries to individual members
|
||||
queryFlagNoBroadcast
|
||||
)
|
||||
|
||||
// filterType is used with a queryFilter to specify the type of
|
||||
// filter we are sending
|
||||
type filterType uint8
|
||||
|
||||
const (
|
||||
filterNodeType filterType = iota
|
||||
filterTagType
|
||||
)
|
||||
|
||||
// messageJoin is the message broadcasted after we join to
|
||||
// associated the node with a lamport clock
|
||||
type messageJoin struct {
|
||||
LTime LamportTime
|
||||
Node string
|
||||
}
|
||||
|
||||
// messageLeave is the message broadcasted to signal the intentional to
|
||||
// leave.
|
||||
type messageLeave struct {
|
||||
LTime LamportTime
|
||||
Node string
|
||||
}
|
||||
|
||||
// messagePushPullType is used when doing a state exchange. This
|
||||
// is a relatively large message, but is sent infrequently
|
||||
type messagePushPull struct {
|
||||
LTime LamportTime // Current node lamport time
|
||||
StatusLTimes map[string]LamportTime // Maps the node to its status time
|
||||
LeftMembers []string // List of left nodes
|
||||
EventLTime LamportTime // Lamport time for event clock
|
||||
Events []*userEvents // Recent events
|
||||
QueryLTime LamportTime // Lamport time for query clock
|
||||
}
|
||||
|
||||
// messageUserEvent is used for user-generated events
|
||||
type messageUserEvent struct {
|
||||
LTime LamportTime
|
||||
Name string
|
||||
Payload []byte
|
||||
CC bool // "Can Coalesce". Zero value is compatible with Serf 0.1
|
||||
}
|
||||
|
||||
// messageQuery is used for query events
|
||||
type messageQuery struct {
|
||||
LTime LamportTime // Event lamport time
|
||||
ID uint32 // Query ID, randomly generated
|
||||
Addr []byte // Source address, used for a direct reply
|
||||
Port uint16 // Source port, used for a direct reply
|
||||
Filters [][]byte // Potential query filters
|
||||
Flags uint32 // Used to provide various flags
|
||||
Timeout time.Duration // Maximum time between delivery and response
|
||||
Name string // Query name
|
||||
Payload []byte // Query payload
|
||||
}
|
||||
|
||||
// Ack checks if the ack flag is set
|
||||
func (m *messageQuery) Ack() bool {
|
||||
return (m.Flags & queryFlagAck) != 0
|
||||
}
|
||||
|
||||
// NoBroadcast checks if the no broadcast flag is set
|
||||
func (m *messageQuery) NoBroadcast() bool {
|
||||
return (m.Flags & queryFlagNoBroadcast) != 0
|
||||
}
|
||||
|
||||
// filterNode is used with the filterNodeType, and is a list
|
||||
// of node names
|
||||
type filterNode []string
|
||||
|
||||
// filterTag is used with the filterTagType and is a regular
|
||||
// expression to apply to a tag
|
||||
type filterTag struct {
|
||||
Tag string
|
||||
Expr string
|
||||
}
|
||||
|
||||
// messageQueryResponse is used to respond to a query
|
||||
type messageQueryResponse struct {
|
||||
LTime LamportTime // Event lamport time
|
||||
ID uint32 // Query ID
|
||||
From string // Node name
|
||||
Flags uint32 // Used to provide various flags
|
||||
Payload []byte // Optional response payload
|
||||
}
|
||||
|
||||
// Ack checks if the ack flag is set
|
||||
func (m *messageQueryResponse) Ack() bool {
|
||||
return (m.Flags & queryFlagAck) != 0
|
||||
}
|
||||
|
||||
func decodeMessage(buf []byte, out interface{}) error {
|
||||
var handle codec.MsgpackHandle
|
||||
return codec.NewDecoder(bytes.NewReader(buf), &handle).Decode(out)
|
||||
}
|
||||
|
||||
func encodeMessage(t messageType, msg interface{}) ([]byte, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
buf.WriteByte(uint8(t))
|
||||
|
||||
handle := codec.MsgpackHandle{}
|
||||
encoder := codec.NewEncoder(buf, &handle)
|
||||
err := encoder.Encode(msg)
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
func encodeFilter(f filterType, filt interface{}) ([]byte, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
buf.WriteByte(uint8(f))
|
||||
|
||||
handle := codec.MsgpackHandle{}
|
||||
encoder := codec.NewEncoder(buf, &handle)
|
||||
err := encoder.Encode(filt)
|
||||
return buf.Bytes(), err
|
||||
}
|
89
vendor/github.com/hashicorp/serf/serf/ping_delegate.go
generated
vendored
Normal file
89
vendor/github.com/hashicorp/serf/serf/ping_delegate.go
generated
vendored
Normal file
|
@ -0,0 +1,89 @@
|
|||
package serf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/armon/go-metrics"
|
||||
"github.com/hashicorp/go-msgpack/codec"
|
||||
"github.com/hashicorp/memberlist"
|
||||
"github.com/hashicorp/serf/coordinate"
|
||||
)
|
||||
|
||||
// pingDelegate is notified when memberlist successfully completes a direct ping
|
||||
// of a peer node. We use this to update our estimated network coordinate, as
|
||||
// well as cache the coordinate of the peer.
|
||||
type pingDelegate struct {
|
||||
serf *Serf
|
||||
}
|
||||
|
||||
const (
|
||||
// PingVersion is an internal version for the ping message, above the normal
|
||||
// versioning we get from the protocol version. This enables small updates
|
||||
// to the ping message without a full protocol bump.
|
||||
PingVersion = 1
|
||||
)
|
||||
|
||||
// AckPayload is called to produce a payload to send back in response to a ping
|
||||
// request.
|
||||
func (p *pingDelegate) AckPayload() []byte {
|
||||
var buf bytes.Buffer
|
||||
|
||||
// The first byte is the version number, forming a simple header.
|
||||
version := []byte{PingVersion}
|
||||
buf.Write(version)
|
||||
|
||||
// The rest of the message is the serialized coordinate.
|
||||
enc := codec.NewEncoder(&buf, &codec.MsgpackHandle{})
|
||||
if err := enc.Encode(p.serf.coordClient.GetCoordinate()); err != nil {
|
||||
log.Printf("[ERR] serf: Failed to encode coordinate: %v\n", err)
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// NotifyPingComplete is called when this node successfully completes a direct ping
|
||||
// of a peer node.
|
||||
func (p *pingDelegate) NotifyPingComplete(other *memberlist.Node, rtt time.Duration, payload []byte) {
|
||||
if payload == nil || len(payload) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Verify ping version in the header.
|
||||
version := payload[0]
|
||||
if version != PingVersion {
|
||||
log.Printf("[ERR] serf: Unsupported ping version: %v", version)
|
||||
return
|
||||
}
|
||||
|
||||
// Process the remainder of the message as a coordinate.
|
||||
r := bytes.NewReader(payload[1:])
|
||||
dec := codec.NewDecoder(r, &codec.MsgpackHandle{})
|
||||
var coord coordinate.Coordinate
|
||||
if err := dec.Decode(&coord); err != nil {
|
||||
log.Printf("[ERR] serf: Failed to decode coordinate from ping: %v", err)
|
||||
}
|
||||
|
||||
// Apply the update. Since this is a coordinate coming from some place
|
||||
// else we harden this and look for dimensionality problems proactively.
|
||||
before := p.serf.coordClient.GetCoordinate()
|
||||
if before.IsCompatibleWith(&coord) {
|
||||
after := p.serf.coordClient.Update(other.Name, &coord, rtt)
|
||||
|
||||
// Publish some metrics to give us an idea of how much we are
|
||||
// adjusting each time we update.
|
||||
d := float32(before.DistanceTo(after).Seconds() * 1.0e3)
|
||||
metrics.AddSample([]string{"serf", "coordinate", "adjustment-ms"}, d)
|
||||
|
||||
// Cache the coordinate for the other node, and add our own
|
||||
// to the cache as well since it just got updated. This lets
|
||||
// users call GetCachedCoordinate with our node name, which is
|
||||
// more friendly.
|
||||
p.serf.coordCacheLock.Lock()
|
||||
p.serf.coordCache[other.Name] = &coord
|
||||
p.serf.coordCache[p.serf.config.NodeName] = p.serf.coordClient.GetCoordinate()
|
||||
p.serf.coordCacheLock.Unlock()
|
||||
} else {
|
||||
log.Printf("[ERR] serf: Rejected bad coordinate: %v\n", coord)
|
||||
}
|
||||
}
|
210
vendor/github.com/hashicorp/serf/serf/query.go
generated
vendored
Normal file
210
vendor/github.com/hashicorp/serf/serf/query.go
generated
vendored
Normal file
|
@ -0,0 +1,210 @@
|
|||
package serf
|
||||
|
||||
import (
|
||||
"math"
|
||||
"regexp"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// QueryParam is provided to Query() to configure the parameters of the
|
||||
// query. If not provided, sane defaults will be used.
|
||||
type QueryParam struct {
|
||||
// If provided, we restrict the nodes that should respond to those
|
||||
// with names in this list
|
||||
FilterNodes []string
|
||||
|
||||
// FilterTags maps a tag name to a regular expression that is applied
|
||||
// to restrict the nodes that should respond
|
||||
FilterTags map[string]string
|
||||
|
||||
// If true, we are requesting an delivery acknowledgement from
|
||||
// every node that meets the filter requirement. This means nodes
|
||||
// the receive the message but do not pass the filters, will not
|
||||
// send an ack.
|
||||
RequestAck bool
|
||||
|
||||
// The timeout limits how long the query is left open. If not provided,
|
||||
// then a default timeout is used based on the configuration of Serf
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// DefaultQueryTimeout returns the default timeout value for a query
|
||||
// Computed as GossipInterval * QueryTimeoutMult * log(N+1)
|
||||
func (s *Serf) DefaultQueryTimeout() time.Duration {
|
||||
n := s.memberlist.NumMembers()
|
||||
timeout := s.config.MemberlistConfig.GossipInterval
|
||||
timeout *= time.Duration(s.config.QueryTimeoutMult)
|
||||
timeout *= time.Duration(math.Ceil(math.Log10(float64(n + 1))))
|
||||
return timeout
|
||||
}
|
||||
|
||||
// DefaultQueryParam is used to return the default query parameters
|
||||
func (s *Serf) DefaultQueryParams() *QueryParam {
|
||||
return &QueryParam{
|
||||
FilterNodes: nil,
|
||||
FilterTags: nil,
|
||||
RequestAck: false,
|
||||
Timeout: s.DefaultQueryTimeout(),
|
||||
}
|
||||
}
|
||||
|
||||
// encodeFilters is used to convert the filters into the wire format
|
||||
func (q *QueryParam) encodeFilters() ([][]byte, error) {
|
||||
var filters [][]byte
|
||||
|
||||
// Add the node filter
|
||||
if len(q.FilterNodes) > 0 {
|
||||
if buf, err := encodeFilter(filterNodeType, q.FilterNodes); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
filters = append(filters, buf)
|
||||
}
|
||||
}
|
||||
|
||||
// Add the tag filters
|
||||
for tag, expr := range q.FilterTags {
|
||||
filt := filterTag{tag, expr}
|
||||
if buf, err := encodeFilter(filterTagType, &filt); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
filters = append(filters, buf)
|
||||
}
|
||||
}
|
||||
|
||||
return filters, nil
|
||||
}
|
||||
|
||||
// QueryResponse is returned for each new Query. It is used to collect
|
||||
// Ack's as well as responses and to provide those back to a client.
|
||||
type QueryResponse struct {
|
||||
// ackCh is used to send the name of a node for which we've received an ack
|
||||
ackCh chan string
|
||||
|
||||
// deadline is the query end time (start + query timeout)
|
||||
deadline time.Time
|
||||
|
||||
// Query ID
|
||||
id uint32
|
||||
|
||||
// Stores the LTime of the query
|
||||
lTime LamportTime
|
||||
|
||||
// respCh is used to send a response from a node
|
||||
respCh chan NodeResponse
|
||||
|
||||
closed bool
|
||||
closeLock sync.Mutex
|
||||
}
|
||||
|
||||
// newQueryResponse is used to construct a new query response
|
||||
func newQueryResponse(n int, q *messageQuery) *QueryResponse {
|
||||
resp := &QueryResponse{
|
||||
deadline: time.Now().Add(q.Timeout),
|
||||
id: q.ID,
|
||||
lTime: q.LTime,
|
||||
respCh: make(chan NodeResponse, n),
|
||||
}
|
||||
if q.Ack() {
|
||||
resp.ackCh = make(chan string, n)
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
// Close is used to close the query, which will close the underlying
|
||||
// channels and prevent further deliveries
|
||||
func (r *QueryResponse) Close() {
|
||||
r.closeLock.Lock()
|
||||
defer r.closeLock.Unlock()
|
||||
if r.closed {
|
||||
return
|
||||
}
|
||||
r.closed = true
|
||||
if r.ackCh != nil {
|
||||
close(r.ackCh)
|
||||
}
|
||||
if r.respCh != nil {
|
||||
close(r.respCh)
|
||||
}
|
||||
}
|
||||
|
||||
// Deadline returns the ending deadline of the query
|
||||
func (r *QueryResponse) Deadline() time.Time {
|
||||
return r.deadline
|
||||
}
|
||||
|
||||
// Finished returns if the query is finished running
|
||||
func (r *QueryResponse) Finished() bool {
|
||||
return r.closed || time.Now().After(r.deadline)
|
||||
}
|
||||
|
||||
// AckCh returns a channel that can be used to listen for acks
|
||||
// Channel will be closed when the query is finished. This is nil,
|
||||
// if the query did not specify RequestAck.
|
||||
func (r *QueryResponse) AckCh() <-chan string {
|
||||
return r.ackCh
|
||||
}
|
||||
|
||||
// ResponseCh returns a channel that can be used to listen for responses.
|
||||
// Channel will be closed when the query is finished.
|
||||
func (r *QueryResponse) ResponseCh() <-chan NodeResponse {
|
||||
return r.respCh
|
||||
}
|
||||
|
||||
// NodeResponse is used to represent a single response from a node
|
||||
type NodeResponse struct {
|
||||
From string
|
||||
Payload []byte
|
||||
}
|
||||
|
||||
// shouldProcessQuery checks if a query should be proceeded given
|
||||
// a set of filers.
|
||||
func (s *Serf) shouldProcessQuery(filters [][]byte) bool {
|
||||
for _, filter := range filters {
|
||||
switch filterType(filter[0]) {
|
||||
case filterNodeType:
|
||||
// Decode the filter
|
||||
var nodes filterNode
|
||||
if err := decodeMessage(filter[1:], &nodes); err != nil {
|
||||
s.logger.Printf("[WARN] serf: failed to decode filterNodeType: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if we are being targeted
|
||||
found := false
|
||||
for _, n := range nodes {
|
||||
if n == s.config.NodeName {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
|
||||
case filterTagType:
|
||||
// Decode the filter
|
||||
var filt filterTag
|
||||
if err := decodeMessage(filter[1:], &filt); err != nil {
|
||||
s.logger.Printf("[WARN] serf: failed to decode filterTagType: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if we match this regex
|
||||
tags := s.config.Tags
|
||||
matched, err := regexp.MatchString(filt.Expr, tags[filt.Tag])
|
||||
if err != nil {
|
||||
s.logger.Printf("[WARN] serf: failed to compile filter regex (%s): %v", filt.Expr, err)
|
||||
return false
|
||||
}
|
||||
if !matched {
|
||||
return false
|
||||
}
|
||||
|
||||
default:
|
||||
s.logger.Printf("[WARN] serf: query has unrecognized filter type: %d", filter[0])
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
1698
vendor/github.com/hashicorp/serf/serf/serf.go
generated
vendored
Normal file
1698
vendor/github.com/hashicorp/serf/serf/serf.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
560
vendor/github.com/hashicorp/serf/serf/snapshot.go
generated
vendored
Normal file
560
vendor/github.com/hashicorp/serf/serf/snapshot.go
generated
vendored
Normal file
|
@ -0,0 +1,560 @@
|
|||
package serf
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/armon/go-metrics"
|
||||
"github.com/hashicorp/serf/coordinate"
|
||||
)
|
||||
|
||||
/*
|
||||
Serf supports using a "snapshot" file that contains various
|
||||
transactional data that is used to help Serf recover quickly
|
||||
and gracefully from a failure. We append member events, as well
|
||||
as the latest clock values to the file during normal operation,
|
||||
and periodically checkpoint and roll over the file. During a restore,
|
||||
we can replay the various member events to recall a list of known
|
||||
nodes to re-join, as well as restore our clock values to avoid replaying
|
||||
old events.
|
||||
*/
|
||||
|
||||
const flushInterval = 500 * time.Millisecond
|
||||
const clockUpdateInterval = 500 * time.Millisecond
|
||||
const coordinateUpdateInterval = 60 * time.Second
|
||||
const tmpExt = ".compact"
|
||||
|
||||
// Snapshotter is responsible for ingesting events and persisting
|
||||
// them to disk, and providing a recovery mechanism at start time.
|
||||
type Snapshotter struct {
|
||||
aliveNodes map[string]string
|
||||
clock *LamportClock
|
||||
coordClient *coordinate.Client
|
||||
fh *os.File
|
||||
buffered *bufio.Writer
|
||||
inCh <-chan Event
|
||||
lastFlush time.Time
|
||||
lastClock LamportTime
|
||||
lastEventClock LamportTime
|
||||
lastQueryClock LamportTime
|
||||
leaveCh chan struct{}
|
||||
leaving bool
|
||||
logger *log.Logger
|
||||
maxSize int64
|
||||
path string
|
||||
offset int64
|
||||
outCh chan<- Event
|
||||
rejoinAfterLeave bool
|
||||
shutdownCh <-chan struct{}
|
||||
waitCh chan struct{}
|
||||
}
|
||||
|
||||
// PreviousNode is used to represent the previously known alive nodes
|
||||
type PreviousNode struct {
|
||||
Name string
|
||||
Addr string
|
||||
}
|
||||
|
||||
func (p PreviousNode) String() string {
|
||||
return fmt.Sprintf("%s: %s", p.Name, p.Addr)
|
||||
}
|
||||
|
||||
// NewSnapshotter creates a new Snapshotter that records events up to a
|
||||
// max byte size before rotating the file. It can also be used to
|
||||
// recover old state. Snapshotter works by reading an event channel it returns,
|
||||
// passing through to an output channel, and persisting relevant events to disk.
|
||||
// Setting rejoinAfterLeave makes leave not clear the state, and can be used
|
||||
// if you intend to rejoin the same cluster after a leave.
|
||||
func NewSnapshotter(path string,
|
||||
maxSize int,
|
||||
rejoinAfterLeave bool,
|
||||
logger *log.Logger,
|
||||
clock *LamportClock,
|
||||
coordClient *coordinate.Client,
|
||||
outCh chan<- Event,
|
||||
shutdownCh <-chan struct{}) (chan<- Event, *Snapshotter, error) {
|
||||
inCh := make(chan Event, 1024)
|
||||
|
||||
// Try to open the file
|
||||
fh, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0755)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to open snapshot: %v", err)
|
||||
}
|
||||
|
||||
// Determine the offset
|
||||
info, err := fh.Stat()
|
||||
if err != nil {
|
||||
fh.Close()
|
||||
return nil, nil, fmt.Errorf("failed to stat snapshot: %v", err)
|
||||
}
|
||||
offset := info.Size()
|
||||
|
||||
// Create the snapshotter
|
||||
snap := &Snapshotter{
|
||||
aliveNodes: make(map[string]string),
|
||||
clock: clock,
|
||||
coordClient: coordClient,
|
||||
fh: fh,
|
||||
buffered: bufio.NewWriter(fh),
|
||||
inCh: inCh,
|
||||
lastClock: 0,
|
||||
lastEventClock: 0,
|
||||
lastQueryClock: 0,
|
||||
leaveCh: make(chan struct{}),
|
||||
logger: logger,
|
||||
maxSize: int64(maxSize),
|
||||
path: path,
|
||||
offset: offset,
|
||||
outCh: outCh,
|
||||
rejoinAfterLeave: rejoinAfterLeave,
|
||||
shutdownCh: shutdownCh,
|
||||
waitCh: make(chan struct{}),
|
||||
}
|
||||
|
||||
// Recover the last known state
|
||||
if err := snap.replay(); err != nil {
|
||||
fh.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Start handling new commands
|
||||
go snap.stream()
|
||||
return inCh, snap, nil
|
||||
}
|
||||
|
||||
// LastClock returns the last known clock time
|
||||
func (s *Snapshotter) LastClock() LamportTime {
|
||||
return s.lastClock
|
||||
}
|
||||
|
||||
// LastEventClock returns the last known event clock time
|
||||
func (s *Snapshotter) LastEventClock() LamportTime {
|
||||
return s.lastEventClock
|
||||
}
|
||||
|
||||
// LastQueryClock returns the last known query clock time
|
||||
func (s *Snapshotter) LastQueryClock() LamportTime {
|
||||
return s.lastQueryClock
|
||||
}
|
||||
|
||||
// AliveNodes returns the last known alive nodes
|
||||
func (s *Snapshotter) AliveNodes() []*PreviousNode {
|
||||
// Copy the previously known
|
||||
previous := make([]*PreviousNode, 0, len(s.aliveNodes))
|
||||
for name, addr := range s.aliveNodes {
|
||||
previous = append(previous, &PreviousNode{name, addr})
|
||||
}
|
||||
|
||||
// Randomize the order, prevents hot shards
|
||||
for i := range previous {
|
||||
j := rand.Intn(i + 1)
|
||||
previous[i], previous[j] = previous[j], previous[i]
|
||||
}
|
||||
return previous
|
||||
}
|
||||
|
||||
// Wait is used to wait until the snapshotter finishes shut down
|
||||
func (s *Snapshotter) Wait() {
|
||||
<-s.waitCh
|
||||
}
|
||||
|
||||
// Leave is used to remove known nodes to prevent a restart from
|
||||
// causing a join. Otherwise nodes will re-join after leaving!
|
||||
func (s *Snapshotter) Leave() {
|
||||
select {
|
||||
case s.leaveCh <- struct{}{}:
|
||||
case <-s.shutdownCh:
|
||||
}
|
||||
}
|
||||
|
||||
// stream is a long running routine that is used to handle events
|
||||
func (s *Snapshotter) stream() {
|
||||
clockTicker := time.NewTicker(clockUpdateInterval)
|
||||
defer clockTicker.Stop()
|
||||
|
||||
coordinateTicker := time.NewTicker(coordinateUpdateInterval)
|
||||
defer coordinateTicker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-s.leaveCh:
|
||||
s.leaving = true
|
||||
|
||||
// If we plan to re-join, keep our state
|
||||
if !s.rejoinAfterLeave {
|
||||
s.aliveNodes = make(map[string]string)
|
||||
}
|
||||
s.tryAppend("leave\n")
|
||||
if err := s.buffered.Flush(); err != nil {
|
||||
s.logger.Printf("[ERR] serf: failed to flush leave to snapshot: %v", err)
|
||||
}
|
||||
if err := s.fh.Sync(); err != nil {
|
||||
s.logger.Printf("[ERR] serf: failed to sync leave to snapshot: %v", err)
|
||||
}
|
||||
|
||||
case e := <-s.inCh:
|
||||
// Forward the event immediately
|
||||
if s.outCh != nil {
|
||||
s.outCh <- e
|
||||
}
|
||||
|
||||
// Stop recording events after a leave is issued
|
||||
if s.leaving {
|
||||
continue
|
||||
}
|
||||
switch typed := e.(type) {
|
||||
case MemberEvent:
|
||||
s.processMemberEvent(typed)
|
||||
case UserEvent:
|
||||
s.processUserEvent(typed)
|
||||
case *Query:
|
||||
s.processQuery(typed)
|
||||
default:
|
||||
s.logger.Printf("[ERR] serf: Unknown event to snapshot: %#v", e)
|
||||
}
|
||||
|
||||
case <-clockTicker.C:
|
||||
s.updateClock()
|
||||
|
||||
case <-coordinateTicker.C:
|
||||
s.updateCoordinate()
|
||||
|
||||
case <-s.shutdownCh:
|
||||
if err := s.buffered.Flush(); err != nil {
|
||||
s.logger.Printf("[ERR] serf: failed to flush snapshot: %v", err)
|
||||
}
|
||||
if err := s.fh.Sync(); err != nil {
|
||||
s.logger.Printf("[ERR] serf: failed to sync snapshot: %v", err)
|
||||
}
|
||||
s.fh.Close()
|
||||
close(s.waitCh)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// processMemberEvent is used to handle a single member event
|
||||
func (s *Snapshotter) processMemberEvent(e MemberEvent) {
|
||||
switch e.Type {
|
||||
case EventMemberJoin:
|
||||
for _, mem := range e.Members {
|
||||
addr := net.TCPAddr{IP: mem.Addr, Port: int(mem.Port)}
|
||||
s.aliveNodes[mem.Name] = addr.String()
|
||||
s.tryAppend(fmt.Sprintf("alive: %s %s\n", mem.Name, addr.String()))
|
||||
}
|
||||
|
||||
case EventMemberLeave:
|
||||
fallthrough
|
||||
case EventMemberFailed:
|
||||
for _, mem := range e.Members {
|
||||
delete(s.aliveNodes, mem.Name)
|
||||
s.tryAppend(fmt.Sprintf("not-alive: %s\n", mem.Name))
|
||||
}
|
||||
}
|
||||
s.updateClock()
|
||||
}
|
||||
|
||||
// updateClock is called periodically to check if we should udpate our
|
||||
// clock value. This is done after member events but should also be done
|
||||
// periodically due to race conditions with join and leave intents
|
||||
func (s *Snapshotter) updateClock() {
|
||||
lastSeen := s.clock.Time() - 1
|
||||
if lastSeen > s.lastClock {
|
||||
s.lastClock = lastSeen
|
||||
s.tryAppend(fmt.Sprintf("clock: %d\n", s.lastClock))
|
||||
}
|
||||
}
|
||||
|
||||
// updateCoordinate is called periodically to write out the current local
|
||||
// coordinate. It's safe to call this if coordinates aren't enabled (nil
|
||||
// client) and it will be a no-op.
|
||||
func (s *Snapshotter) updateCoordinate() {
|
||||
if s.coordClient != nil {
|
||||
encoded, err := json.Marshal(s.coordClient.GetCoordinate())
|
||||
if err != nil {
|
||||
s.logger.Printf("[ERR] serf: Failed to encode coordinate: %v", err)
|
||||
} else {
|
||||
s.tryAppend(fmt.Sprintf("coordinate: %s\n", encoded))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// processUserEvent is used to handle a single user event
|
||||
func (s *Snapshotter) processUserEvent(e UserEvent) {
|
||||
// Ignore old clocks
|
||||
if e.LTime <= s.lastEventClock {
|
||||
return
|
||||
}
|
||||
s.lastEventClock = e.LTime
|
||||
s.tryAppend(fmt.Sprintf("event-clock: %d\n", e.LTime))
|
||||
}
|
||||
|
||||
// processQuery is used to handle a single query event
|
||||
func (s *Snapshotter) processQuery(q *Query) {
|
||||
// Ignore old clocks
|
||||
if q.LTime <= s.lastQueryClock {
|
||||
return
|
||||
}
|
||||
s.lastQueryClock = q.LTime
|
||||
s.tryAppend(fmt.Sprintf("query-clock: %d\n", q.LTime))
|
||||
}
|
||||
|
||||
// tryAppend will invoke append line but will not return an error
|
||||
func (s *Snapshotter) tryAppend(l string) {
|
||||
if err := s.appendLine(l); err != nil {
|
||||
s.logger.Printf("[ERR] serf: Failed to update snapshot: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// appendLine is used to append a line to the existing log
|
||||
func (s *Snapshotter) appendLine(l string) error {
|
||||
defer metrics.MeasureSince([]string{"serf", "snapshot", "appendLine"}, time.Now())
|
||||
|
||||
n, err := s.buffered.WriteString(l)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if we should flush
|
||||
now := time.Now()
|
||||
if now.Sub(s.lastFlush) > flushInterval {
|
||||
s.lastFlush = now
|
||||
if err := s.buffered.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a compaction is necessary
|
||||
s.offset += int64(n)
|
||||
if s.offset > s.maxSize {
|
||||
return s.compact()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compact is used to compact the snapshot once it is too large
|
||||
func (s *Snapshotter) compact() error {
|
||||
defer metrics.MeasureSince([]string{"serf", "snapshot", "compact"}, time.Now())
|
||||
|
||||
// Try to open the file to new fiel
|
||||
newPath := s.path + tmpExt
|
||||
fh, err := os.OpenFile(newPath, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open new snapshot: %v", err)
|
||||
}
|
||||
|
||||
// Create a buffered writer
|
||||
buf := bufio.NewWriter(fh)
|
||||
|
||||
// Write out the live nodes
|
||||
var offset int64
|
||||
for name, addr := range s.aliveNodes {
|
||||
line := fmt.Sprintf("alive: %s %s\n", name, addr)
|
||||
n, err := buf.WriteString(line)
|
||||
if err != nil {
|
||||
fh.Close()
|
||||
return err
|
||||
}
|
||||
offset += int64(n)
|
||||
}
|
||||
|
||||
// Write out the clocks
|
||||
line := fmt.Sprintf("clock: %d\n", s.lastClock)
|
||||
n, err := buf.WriteString(line)
|
||||
if err != nil {
|
||||
fh.Close()
|
||||
return err
|
||||
}
|
||||
offset += int64(n)
|
||||
|
||||
line = fmt.Sprintf("event-clock: %d\n", s.lastEventClock)
|
||||
n, err = buf.WriteString(line)
|
||||
if err != nil {
|
||||
fh.Close()
|
||||
return err
|
||||
}
|
||||
offset += int64(n)
|
||||
|
||||
line = fmt.Sprintf("query-clock: %d\n", s.lastQueryClock)
|
||||
n, err = buf.WriteString(line)
|
||||
if err != nil {
|
||||
fh.Close()
|
||||
return err
|
||||
}
|
||||
offset += int64(n)
|
||||
|
||||
// Write out the coordinate.
|
||||
if s.coordClient != nil {
|
||||
encoded, err := json.Marshal(s.coordClient.GetCoordinate())
|
||||
if err != nil {
|
||||
fh.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
line = fmt.Sprintf("coordinate: %s\n", encoded)
|
||||
n, err = buf.WriteString(line)
|
||||
if err != nil {
|
||||
fh.Close()
|
||||
return err
|
||||
}
|
||||
offset += int64(n)
|
||||
}
|
||||
|
||||
// Flush the new snapshot
|
||||
err = buf.Flush()
|
||||
fh.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to flush new snapshot: %v", err)
|
||||
}
|
||||
|
||||
// We now need to swap the old snapshot file with the new snapshot.
|
||||
// Turns out, Windows won't let us rename the files if we have
|
||||
// open handles to them or if the destination already exists. This
|
||||
// means we are forced to close the existing handles, delete the
|
||||
// old file, move the new one in place, and then re-open the file
|
||||
// handles.
|
||||
|
||||
// Flush the existing snapshot, ignoring errors since we will
|
||||
// delete it momentarily.
|
||||
s.buffered.Flush()
|
||||
s.buffered = nil
|
||||
|
||||
// Close the file handle to the old snapshot
|
||||
s.fh.Close()
|
||||
s.fh = nil
|
||||
|
||||
// Delete the old file
|
||||
if err := os.Remove(s.path); err != nil {
|
||||
return fmt.Errorf("failed to remove old snapshot: %v", err)
|
||||
}
|
||||
|
||||
// Move the new file into place
|
||||
if err := os.Rename(newPath, s.path); err != nil {
|
||||
return fmt.Errorf("failed to install new snapshot: %v", err)
|
||||
}
|
||||
|
||||
// Open the new snapshot
|
||||
fh, err = os.OpenFile(s.path, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open snapshot: %v", err)
|
||||
}
|
||||
buf = bufio.NewWriter(fh)
|
||||
|
||||
// Rotate our handles
|
||||
s.fh = fh
|
||||
s.buffered = buf
|
||||
s.offset = offset
|
||||
s.lastFlush = time.Now()
|
||||
return nil
|
||||
}
|
||||
|
||||
// replay is used to seek to reset our internal state by replaying
|
||||
// the snapshot file. It is used at initialization time to read old
|
||||
// state
|
||||
func (s *Snapshotter) replay() error {
|
||||
// Seek to the beginning
|
||||
if _, err := s.fh.Seek(0, os.SEEK_SET); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read each line
|
||||
reader := bufio.NewReader(s.fh)
|
||||
for {
|
||||
line, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
// Skip the newline
|
||||
line = line[:len(line)-1]
|
||||
|
||||
// Switch on the prefix
|
||||
if strings.HasPrefix(line, "alive: ") {
|
||||
info := strings.TrimPrefix(line, "alive: ")
|
||||
addrIdx := strings.LastIndex(info, " ")
|
||||
if addrIdx == -1 {
|
||||
s.logger.Printf("[WARN] serf: Failed to parse address: %v", line)
|
||||
continue
|
||||
}
|
||||
addr := info[addrIdx+1:]
|
||||
name := info[:addrIdx]
|
||||
s.aliveNodes[name] = addr
|
||||
|
||||
} else if strings.HasPrefix(line, "not-alive: ") {
|
||||
name := strings.TrimPrefix(line, "not-alive: ")
|
||||
delete(s.aliveNodes, name)
|
||||
|
||||
} else if strings.HasPrefix(line, "clock: ") {
|
||||
timeStr := strings.TrimPrefix(line, "clock: ")
|
||||
timeInt, err := strconv.ParseUint(timeStr, 10, 64)
|
||||
if err != nil {
|
||||
s.logger.Printf("[WARN] serf: Failed to convert clock time: %v", err)
|
||||
continue
|
||||
}
|
||||
s.lastClock = LamportTime(timeInt)
|
||||
|
||||
} else if strings.HasPrefix(line, "event-clock: ") {
|
||||
timeStr := strings.TrimPrefix(line, "event-clock: ")
|
||||
timeInt, err := strconv.ParseUint(timeStr, 10, 64)
|
||||
if err != nil {
|
||||
s.logger.Printf("[WARN] serf: Failed to convert event clock time: %v", err)
|
||||
continue
|
||||
}
|
||||
s.lastEventClock = LamportTime(timeInt)
|
||||
|
||||
} else if strings.HasPrefix(line, "query-clock: ") {
|
||||
timeStr := strings.TrimPrefix(line, "query-clock: ")
|
||||
timeInt, err := strconv.ParseUint(timeStr, 10, 64)
|
||||
if err != nil {
|
||||
s.logger.Printf("[WARN] serf: Failed to convert query clock time: %v", err)
|
||||
continue
|
||||
}
|
||||
s.lastQueryClock = LamportTime(timeInt)
|
||||
|
||||
} else if strings.HasPrefix(line, "coordinate: ") {
|
||||
if s.coordClient == nil {
|
||||
s.logger.Printf("[WARN] serf: Ignoring snapshot coordinates since they are disabled")
|
||||
continue
|
||||
}
|
||||
|
||||
coordStr := strings.TrimPrefix(line, "coordinate: ")
|
||||
var coord coordinate.Coordinate
|
||||
err := json.Unmarshal([]byte(coordStr), &coord)
|
||||
if err != nil {
|
||||
s.logger.Printf("[WARN] serf: Failed to decode coordinate: %v", err)
|
||||
continue
|
||||
}
|
||||
s.coordClient.SetCoordinate(&coord)
|
||||
} else if line == "leave" {
|
||||
// Ignore a leave if we plan on re-joining
|
||||
if s.rejoinAfterLeave {
|
||||
s.logger.Printf("[INFO] serf: Ignoring previous leave in snapshot")
|
||||
continue
|
||||
}
|
||||
s.aliveNodes = make(map[string]string)
|
||||
s.lastClock = 0
|
||||
s.lastEventClock = 0
|
||||
s.lastQueryClock = 0
|
||||
|
||||
} else if strings.HasPrefix(line, "#") {
|
||||
// Skip comment lines
|
||||
|
||||
} else {
|
||||
s.logger.Printf("[WARN] serf: Unrecognized snapshot line: %v", line)
|
||||
}
|
||||
}
|
||||
|
||||
// Seek to the end
|
||||
if _, err := s.fh.Seek(0, os.SEEK_END); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
12
vendor/github.com/hashicorp/serf/version.go
generated
vendored
Normal file
12
vendor/github.com/hashicorp/serf/version.go
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
package main
|
||||
|
||||
// The git commit that was compiled. This will be filled in by the compiler.
|
||||
var GitCommit string
|
||||
|
||||
// The main version number that is being run at the moment.
|
||||
const Version = "0.7.0"
|
||||
|
||||
// A pre-release marker for the version. If this is "" (empty string)
|
||||
// then it means that it is a final release. Otherwise, this is a pre-release
|
||||
// such as "dev" (in development), "beta", "rc1", etc.
|
||||
const VersionPrerelease = ""
|
Loading…
Add table
Add a link
Reference in a new issue