mirror of https://github.com/k3s-io/k3s
Merge pull request #26607 from mikedanese/fix-godeps
fix godeps on master broken in #26335pull/6/head
commit
f575c813da
|
@ -478,11 +478,6 @@
|
||||||
"Comment": "v2.3.0-282-g8b320e7",
|
"Comment": "v2.3.0-282-g8b320e7",
|
||||||
"Rev": "8b320e7c550067b1dfb37bd1682e8067023e0751"
|
"Rev": "8b320e7c550067b1dfb37bd1682e8067023e0751"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ImportPath": "github.com/coreos/go-etcd/etcd",
|
|
||||||
"Comment": "v2.0.0-38-g003851b",
|
|
||||||
"Rev": "003851be7bb0694fe3cc457a49529a19388ee7cf"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/coreos/go-oidc/http",
|
"ImportPath": "github.com/coreos/go-oidc/http",
|
||||||
"Rev": "5cf2aa52da8c574d3aa4458f471ad6ae2240fe6b"
|
"Rev": "5cf2aa52da8c574d3aa4458f471ad6ae2240fe6b"
|
||||||
|
|
|
@ -18458,216 +18458,6 @@ SOFTWARE.
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
|
|
||||||
================================================================================
|
|
||||||
= vendor/github.com/coreos/go-etcd/etcd licensed under: =
|
|
||||||
|
|
||||||
|
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
= vendor/github.com/coreos/go-etcd/LICENSE 3b83ef96387f14655fc854ddc3c6bd57 -
|
|
||||||
================================================================================
|
|
||||||
|
|
||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
= vendor/github.com/coreos/go-oidc/http licensed under: =
|
= vendor/github.com/coreos/go-oidc/http licensed under: =
|
||||||
|
|
||||||
|
|
|
@ -1,202 +0,0 @@
|
||||||
|
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
|
@ -1,23 +0,0 @@
|
||||||
package etcd
|
|
||||||
|
|
||||||
// Add a new directory with a random etcd-generated key under the given path.
|
|
||||||
func (c *Client) AddChildDir(key string, ttl uint64) (*Response, error) {
|
|
||||||
raw, err := c.post(key, "", ttl)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return raw.Unmarshal()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a new file with a random etcd-generated key under the given path.
|
|
||||||
func (c *Client) AddChild(key string, value string, ttl uint64) (*Response, error) {
|
|
||||||
raw, err := c.post(key, value, ttl)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return raw.Unmarshal()
|
|
||||||
}
|
|
|
@ -1,476 +0,0 @@
|
||||||
package etcd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// See SetConsistency for how to use these constants.
|
|
||||||
const (
|
|
||||||
// Using strings rather than iota because the consistency level
|
|
||||||
// could be persisted to disk, so it'd be better to use
|
|
||||||
// human-readable values.
|
|
||||||
STRONG_CONSISTENCY = "STRONG"
|
|
||||||
WEAK_CONSISTENCY = "WEAK"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultBufferSize = 10
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rand.Seed(int64(time.Now().Nanosecond()))
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
CertFile string `json:"certFile"`
|
|
||||||
KeyFile string `json:"keyFile"`
|
|
||||||
CaCertFile []string `json:"caCertFiles"`
|
|
||||||
DialTimeout time.Duration `json:"timeout"`
|
|
||||||
Consistency string `json:"consistency"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type credentials struct {
|
|
||||||
username string
|
|
||||||
password string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Client struct {
|
|
||||||
config Config `json:"config"`
|
|
||||||
cluster *Cluster `json:"cluster"`
|
|
||||||
httpClient *http.Client
|
|
||||||
credentials *credentials
|
|
||||||
transport *http.Transport
|
|
||||||
persistence io.Writer
|
|
||||||
cURLch chan string
|
|
||||||
// CheckRetry can be used to control the policy for failed requests
|
|
||||||
// and modify the cluster if needed.
|
|
||||||
// The client calls it before sending requests again, and
|
|
||||||
// stops retrying if CheckRetry returns some error. The cases that
|
|
||||||
// this function needs to handle include no response and unexpected
|
|
||||||
// http status code of response.
|
|
||||||
// If CheckRetry is nil, client will call the default one
|
|
||||||
// `DefaultCheckRetry`.
|
|
||||||
// Argument cluster is the etcd.Cluster object that these requests have been made on.
|
|
||||||
// Argument numReqs is the number of http.Requests that have been made so far.
|
|
||||||
// Argument lastResp is the http.Responses from the last request.
|
|
||||||
// Argument err is the reason of the failure.
|
|
||||||
CheckRetry func(cluster *Cluster, numReqs int,
|
|
||||||
lastResp http.Response, err error) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClient create a basic client that is configured to be used
|
|
||||||
// with the given machine list.
|
|
||||||
func NewClient(machines []string) *Client {
|
|
||||||
config := Config{
|
|
||||||
// default timeout is one second
|
|
||||||
DialTimeout: time.Second,
|
|
||||||
Consistency: WEAK_CONSISTENCY,
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &Client{
|
|
||||||
cluster: NewCluster(machines),
|
|
||||||
config: config,
|
|
||||||
}
|
|
||||||
|
|
||||||
client.initHTTPClient()
|
|
||||||
client.saveConfig()
|
|
||||||
|
|
||||||
return client
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTLSClient create a basic client with TLS configuration
|
|
||||||
func NewTLSClient(machines []string, cert, key, caCert string) (*Client, error) {
|
|
||||||
// overwrite the default machine to use https
|
|
||||||
if len(machines) == 0 {
|
|
||||||
machines = []string{"https://127.0.0.1:4001"}
|
|
||||||
}
|
|
||||||
|
|
||||||
config := Config{
|
|
||||||
// default timeout is one second
|
|
||||||
DialTimeout: time.Second,
|
|
||||||
Consistency: WEAK_CONSISTENCY,
|
|
||||||
CertFile: cert,
|
|
||||||
KeyFile: key,
|
|
||||||
CaCertFile: make([]string, 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &Client{
|
|
||||||
cluster: NewCluster(machines),
|
|
||||||
config: config,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := client.initHTTPSClient(cert, key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = client.AddRootCA(caCert)
|
|
||||||
|
|
||||||
client.saveConfig()
|
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClientFromFile creates a client from a given file path.
|
|
||||||
// The given file is expected to use the JSON format.
|
|
||||||
func NewClientFromFile(fpath string) (*Client, error) {
|
|
||||||
fi, err := os.Open(fpath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err := fi.Close(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return NewClientFromReader(fi)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClientFromReader creates a Client configured from a given reader.
|
|
||||||
// The configuration is expected to use the JSON format.
|
|
||||||
func NewClientFromReader(reader io.Reader) (*Client, error) {
|
|
||||||
c := new(Client)
|
|
||||||
|
|
||||||
b, err := ioutil.ReadAll(reader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.Unmarshal(b, c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if c.config.CertFile == "" {
|
|
||||||
c.initHTTPClient()
|
|
||||||
} else {
|
|
||||||
err = c.initHTTPSClient(c.config.CertFile, c.config.KeyFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, caCert := range c.config.CaCertFile {
|
|
||||||
if err := c.AddRootCA(caCert); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Override the Client's HTTP Transport object
|
|
||||||
func (c *Client) SetTransport(tr *http.Transport) {
|
|
||||||
c.httpClient.Transport = tr
|
|
||||||
c.transport = tr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) SetCredentials(username, password string) {
|
|
||||||
c.credentials = &credentials{username, password}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Close() {
|
|
||||||
c.transport.DisableKeepAlives = true
|
|
||||||
c.transport.CloseIdleConnections()
|
|
||||||
}
|
|
||||||
|
|
||||||
// initHTTPClient initializes a HTTP client for etcd client
|
|
||||||
func (c *Client) initHTTPClient() {
|
|
||||||
c.transport = &http.Transport{
|
|
||||||
Dial: c.DefaultDial,
|
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
InsecureSkipVerify: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
c.httpClient = &http.Client{Transport: c.transport}
|
|
||||||
}
|
|
||||||
|
|
||||||
// initHTTPClient initializes a HTTPS client for etcd client
|
|
||||||
func (c *Client) initHTTPSClient(cert, key string) error {
|
|
||||||
if cert == "" || key == "" {
|
|
||||||
return errors.New("Require both cert and key path")
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsCert, err := tls.LoadX509KeyPair(cert, key)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConfig := &tls.Config{
|
|
||||||
Certificates: []tls.Certificate{tlsCert},
|
|
||||||
InsecureSkipVerify: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
c.transport = &http.Transport{
|
|
||||||
TLSClientConfig: tlsConfig,
|
|
||||||
Dial: c.DefaultDial,
|
|
||||||
}
|
|
||||||
|
|
||||||
c.httpClient = &http.Client{Transport: c.transport}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetPersistence sets a writer to which the config will be
|
|
||||||
// written every time it's changed.
|
|
||||||
func (c *Client) SetPersistence(writer io.Writer) {
|
|
||||||
c.persistence = writer
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetConsistency changes the consistency level of the client.
|
|
||||||
//
|
|
||||||
// When consistency is set to STRONG_CONSISTENCY, all requests,
|
|
||||||
// including GET, are sent to the leader. This means that, assuming
|
|
||||||
// the absence of leader failures, GET requests are guaranteed to see
|
|
||||||
// the changes made by previous requests.
|
|
||||||
//
|
|
||||||
// When consistency is set to WEAK_CONSISTENCY, other requests
|
|
||||||
// are still sent to the leader, but GET requests are sent to a
|
|
||||||
// random server from the server pool. This reduces the read
|
|
||||||
// load on the leader, but it's not guaranteed that the GET requests
|
|
||||||
// will see changes made by previous requests (they might have not
|
|
||||||
// yet been committed on non-leader servers).
|
|
||||||
func (c *Client) SetConsistency(consistency string) error {
|
|
||||||
if !(consistency == STRONG_CONSISTENCY || consistency == WEAK_CONSISTENCY) {
|
|
||||||
return errors.New("The argument must be either STRONG_CONSISTENCY or WEAK_CONSISTENCY.")
|
|
||||||
}
|
|
||||||
c.config.Consistency = consistency
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sets the DialTimeout value
|
|
||||||
func (c *Client) SetDialTimeout(d time.Duration) {
|
|
||||||
c.config.DialTimeout = d
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddRootCA adds a root CA cert for the etcd client
|
|
||||||
func (c *Client) AddRootCA(caCert string) error {
|
|
||||||
if c.httpClient == nil {
|
|
||||||
return errors.New("Client has not been initialized yet!")
|
|
||||||
}
|
|
||||||
|
|
||||||
certBytes, err := ioutil.ReadFile(caCert)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
tr, ok := c.httpClient.Transport.(*http.Transport)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
panic("AddRootCA(): Transport type assert should not fail")
|
|
||||||
}
|
|
||||||
|
|
||||||
if tr.TLSClientConfig.RootCAs == nil {
|
|
||||||
caCertPool := x509.NewCertPool()
|
|
||||||
ok = caCertPool.AppendCertsFromPEM(certBytes)
|
|
||||||
if ok {
|
|
||||||
tr.TLSClientConfig.RootCAs = caCertPool
|
|
||||||
}
|
|
||||||
tr.TLSClientConfig.InsecureSkipVerify = false
|
|
||||||
} else {
|
|
||||||
ok = tr.TLSClientConfig.RootCAs.AppendCertsFromPEM(certBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
err = errors.New("Unable to load caCert")
|
|
||||||
}
|
|
||||||
|
|
||||||
c.config.CaCertFile = append(c.config.CaCertFile, caCert)
|
|
||||||
c.saveConfig()
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetCluster updates cluster information using the given machine list.
|
|
||||||
func (c *Client) SetCluster(machines []string) bool {
|
|
||||||
success := c.internalSyncCluster(machines)
|
|
||||||
return success
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) GetCluster() []string {
|
|
||||||
return c.cluster.Machines
|
|
||||||
}
|
|
||||||
|
|
||||||
// SyncCluster updates the cluster information using the internal machine list.
|
|
||||||
// If no members are found, the intenral machine list is left untouched.
|
|
||||||
func (c *Client) SyncCluster() bool {
|
|
||||||
return c.internalSyncCluster(c.cluster.Machines)
|
|
||||||
}
|
|
||||||
|
|
||||||
// internalSyncCluster syncs cluster information using the given machine list.
|
|
||||||
func (c *Client) internalSyncCluster(machines []string) bool {
|
|
||||||
// comma-separated list of machines in the cluster.
|
|
||||||
members := ""
|
|
||||||
|
|
||||||
for _, machine := range machines {
|
|
||||||
httpPath := c.createHttpPath(machine, path.Join(version, "members"))
|
|
||||||
resp, err := c.httpClient.Get(httpPath)
|
|
||||||
if err != nil {
|
|
||||||
// try another machine in the cluster
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK { // fall-back to old endpoint
|
|
||||||
httpPath := c.createHttpPath(machine, path.Join(version, "machines"))
|
|
||||||
resp, err := c.httpClient.Get(httpPath)
|
|
||||||
if err != nil {
|
|
||||||
// try another machine in the cluster
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
b, err := ioutil.ReadAll(resp.Body)
|
|
||||||
resp.Body.Close()
|
|
||||||
if err != nil {
|
|
||||||
// try another machine in the cluster
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
members = string(b)
|
|
||||||
} else {
|
|
||||||
b, err := ioutil.ReadAll(resp.Body)
|
|
||||||
resp.Body.Close()
|
|
||||||
if err != nil {
|
|
||||||
// try another machine in the cluster
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var mCollection memberCollection
|
|
||||||
if err := json.Unmarshal(b, &mCollection); err != nil {
|
|
||||||
// try another machine
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
urls := make([]string, 0)
|
|
||||||
for _, m := range mCollection {
|
|
||||||
urls = append(urls, m.ClientURLs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
members = strings.Join(urls, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
// We should never do an empty cluster update.
|
|
||||||
if members == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// update Machines List
|
|
||||||
c.cluster.updateFromStr(members)
|
|
||||||
logger.Debug("sync.machines ", c.cluster.Machines)
|
|
||||||
c.saveConfig()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// createHttpPath creates a complete HTTP URL.
|
|
||||||
// serverName should contain both the host name and a port number, if any.
|
|
||||||
func (c *Client) createHttpPath(serverName string, _path string) string {
|
|
||||||
u, err := url.Parse(serverName)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
u.Path = path.Join(u.Path, _path)
|
|
||||||
|
|
||||||
if u.Scheme == "" {
|
|
||||||
u.Scheme = "http"
|
|
||||||
}
|
|
||||||
return u.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultDial attempts to open a TCP connection to the provided address, explicitly
|
|
||||||
// enabling keep-alives with a one-second interval.
|
|
||||||
func (c *Client) DefaultDial(network, addr string) (net.Conn, error) {
|
|
||||||
dialer := net.Dialer{
|
|
||||||
Timeout: c.config.DialTimeout,
|
|
||||||
KeepAlive: time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
return dialer.Dial(network, addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) OpenCURL() {
|
|
||||||
c.cURLch = make(chan string, defaultBufferSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) CloseCURL() {
|
|
||||||
c.cURLch = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) sendCURL(command string) {
|
|
||||||
go func() {
|
|
||||||
select {
|
|
||||||
case c.cURLch <- command:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) RecvCURL() string {
|
|
||||||
return <-c.cURLch
|
|
||||||
}
|
|
||||||
|
|
||||||
// saveConfig saves the current config using c.persistence.
|
|
||||||
func (c *Client) saveConfig() error {
|
|
||||||
if c.persistence != nil {
|
|
||||||
b, err := json.Marshal(c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = c.persistence.Write(b)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON implements the Marshaller interface
|
|
||||||
// as defined by the standard JSON package.
|
|
||||||
func (c *Client) MarshalJSON() ([]byte, error) {
|
|
||||||
b, err := json.Marshal(struct {
|
|
||||||
Config Config `json:"config"`
|
|
||||||
Cluster *Cluster `json:"cluster"`
|
|
||||||
}{
|
|
||||||
Config: c.config,
|
|
||||||
Cluster: c.cluster,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON implements the Unmarshaller interface
|
|
||||||
// as defined by the standard JSON package.
|
|
||||||
func (c *Client) UnmarshalJSON(b []byte) error {
|
|
||||||
temp := struct {
|
|
||||||
Config Config `json:"config"`
|
|
||||||
Cluster *Cluster `json:"cluster"`
|
|
||||||
}{}
|
|
||||||
err := json.Unmarshal(b, &temp)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.cluster = temp.Cluster
|
|
||||||
c.config = temp.Config
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
package etcd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/rand"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Cluster struct {
|
|
||||||
Leader string `json:"leader"`
|
|
||||||
Machines []string `json:"machines"`
|
|
||||||
picked int
|
|
||||||
mu sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCluster(machines []string) *Cluster {
|
|
||||||
// if an empty slice was sent in then just assume HTTP 4001 on localhost
|
|
||||||
if len(machines) == 0 {
|
|
||||||
machines = []string{"http://127.0.0.1:4001"}
|
|
||||||
}
|
|
||||||
|
|
||||||
machines = shuffleStringSlice(machines)
|
|
||||||
logger.Debug("Shuffle cluster machines", machines)
|
|
||||||
// default leader and machines
|
|
||||||
return &Cluster{
|
|
||||||
Leader: "",
|
|
||||||
Machines: machines,
|
|
||||||
picked: rand.Intn(len(machines)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *Cluster) failure() {
|
|
||||||
cl.mu.Lock()
|
|
||||||
defer cl.mu.Unlock()
|
|
||||||
cl.picked = (cl.picked + 1) % len(cl.Machines)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *Cluster) pick() string {
|
|
||||||
cl.mu.Lock()
|
|
||||||
defer cl.mu.Unlock()
|
|
||||||
return cl.Machines[cl.picked]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *Cluster) updateFromStr(machines string) {
|
|
||||||
cl.mu.Lock()
|
|
||||||
defer cl.mu.Unlock()
|
|
||||||
|
|
||||||
cl.Machines = strings.Split(machines, ",")
|
|
||||||
for i := range cl.Machines {
|
|
||||||
cl.Machines[i] = strings.TrimSpace(cl.Machines[i])
|
|
||||||
}
|
|
||||||
cl.Machines = shuffleStringSlice(cl.Machines)
|
|
||||||
cl.picked = rand.Intn(len(cl.Machines))
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
package etcd
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
func (c *Client) CompareAndDelete(key string, prevValue string, prevIndex uint64) (*Response, error) {
|
|
||||||
raw, err := c.RawCompareAndDelete(key, prevValue, prevIndex)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return raw.Unmarshal()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) RawCompareAndDelete(key string, prevValue string, prevIndex uint64) (*RawResponse, error) {
|
|
||||||
if prevValue == "" && prevIndex == 0 {
|
|
||||||
return nil, fmt.Errorf("You must give either prevValue or prevIndex.")
|
|
||||||
}
|
|
||||||
|
|
||||||
options := Options{}
|
|
||||||
if prevValue != "" {
|
|
||||||
options["prevValue"] = prevValue
|
|
||||||
}
|
|
||||||
if prevIndex != 0 {
|
|
||||||
options["prevIndex"] = prevIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
raw, err := c.delete(key, options)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return raw, err
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
package etcd
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
func (c *Client) CompareAndSwap(key string, value string, ttl uint64,
|
|
||||||
prevValue string, prevIndex uint64) (*Response, error) {
|
|
||||||
raw, err := c.RawCompareAndSwap(key, value, ttl, prevValue, prevIndex)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return raw.Unmarshal()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) RawCompareAndSwap(key string, value string, ttl uint64,
|
|
||||||
prevValue string, prevIndex uint64) (*RawResponse, error) {
|
|
||||||
if prevValue == "" && prevIndex == 0 {
|
|
||||||
return nil, fmt.Errorf("You must give either prevValue or prevIndex.")
|
|
||||||
}
|
|
||||||
|
|
||||||
options := Options{}
|
|
||||||
if prevValue != "" {
|
|
||||||
options["prevValue"] = prevValue
|
|
||||||
}
|
|
||||||
if prevIndex != 0 {
|
|
||||||
options["prevIndex"] = prevIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
raw, err := c.put(key, value, ttl, options)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return raw, err
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
package etcd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var logger *etcdLogger
|
|
||||||
|
|
||||||
func SetLogger(l *log.Logger) {
|
|
||||||
logger = &etcdLogger{l}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetLogger() *log.Logger {
|
|
||||||
return logger.log
|
|
||||||
}
|
|
||||||
|
|
||||||
type etcdLogger struct {
|
|
||||||
log *log.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *etcdLogger) Debug(args ...interface{}) {
|
|
||||||
msg := "DEBUG: " + fmt.Sprint(args...)
|
|
||||||
p.log.Println(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *etcdLogger) Debugf(f string, args ...interface{}) {
|
|
||||||
msg := "DEBUG: " + fmt.Sprintf(f, args...)
|
|
||||||
// Append newline if necessary
|
|
||||||
if !strings.HasSuffix(msg, "\n") {
|
|
||||||
msg = msg + "\n"
|
|
||||||
}
|
|
||||||
p.log.Print(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *etcdLogger) Warning(args ...interface{}) {
|
|
||||||
msg := "WARNING: " + fmt.Sprint(args...)
|
|
||||||
p.log.Println(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *etcdLogger) Warningf(f string, args ...interface{}) {
|
|
||||||
msg := "WARNING: " + fmt.Sprintf(f, args...)
|
|
||||||
// Append newline if necessary
|
|
||||||
if !strings.HasSuffix(msg, "\n") {
|
|
||||||
msg = msg + "\n"
|
|
||||||
}
|
|
||||||
p.log.Print(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// Default logger uses the go default log.
|
|
||||||
SetLogger(log.New(ioutil.Discard, "go-etcd", log.LstdFlags))
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
package etcd
|
|
||||||
|
|
||||||
// Delete deletes the given key.
|
|
||||||
//
|
|
||||||
// When recursive set to false, if the key points to a
|
|
||||||
// directory the method will fail.
|
|
||||||
//
|
|
||||||
// When recursive set to true, if the key points to a file,
|
|
||||||
// the file will be deleted; if the key points to a directory,
|
|
||||||
// then everything under the directory (including all child directories)
|
|
||||||
// will be deleted.
|
|
||||||
func (c *Client) Delete(key string, recursive bool) (*Response, error) {
|
|
||||||
raw, err := c.RawDelete(key, recursive, false)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return raw.Unmarshal()
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteDir deletes an empty directory or a key value pair
|
|
||||||
func (c *Client) DeleteDir(key string) (*Response, error) {
|
|
||||||
raw, err := c.RawDelete(key, false, true)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return raw.Unmarshal()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) RawDelete(key string, recursive bool, dir bool) (*RawResponse, error) {
|
|
||||||
ops := Options{
|
|
||||||
"recursive": recursive,
|
|
||||||
"dir": dir,
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.delete(key, ops)
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
package etcd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ErrCodeEtcdNotReachable = 501
|
|
||||||
ErrCodeUnhandledHTTPStatus = 502
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errorMap = map[int]string{
|
|
||||||
ErrCodeEtcdNotReachable: "All the given peers are not reachable",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type EtcdError struct {
|
|
||||||
ErrorCode int `json:"errorCode"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
Cause string `json:"cause,omitempty"`
|
|
||||||
Index uint64 `json:"index"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e EtcdError) Error() string {
|
|
||||||
return fmt.Sprintf("%v: %v (%v) [%v]", e.ErrorCode, e.Message, e.Cause, e.Index)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newError(errorCode int, cause string, index uint64) *EtcdError {
|
|
||||||
return &EtcdError{
|
|
||||||
ErrorCode: errorCode,
|
|
||||||
Message: errorMap[errorCode],
|
|
||||||
Cause: cause,
|
|
||||||
Index: index,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleError(b []byte) error {
|
|
||||||
etcdErr := new(EtcdError)
|
|
||||||
|
|
||||||
err := json.Unmarshal(b, etcdErr)
|
|
||||||
if err != nil {
|
|
||||||
logger.Warningf("cannot unmarshal etcd error: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return etcdErr
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
package etcd
|
|
||||||
|
|
||||||
// Get gets the file or directory associated with the given key.
|
|
||||||
// If the key points to a directory, files and directories under
|
|
||||||
// it will be returned in sorted or unsorted order, depending on
|
|
||||||
// the sort flag.
|
|
||||||
// If recursive is set to false, contents under child directories
|
|
||||||
// will not be returned.
|
|
||||||
// If recursive is set to true, all the contents will be returned.
|
|
||||||
func (c *Client) Get(key string, sort, recursive bool) (*Response, error) {
|
|
||||||
raw, err := c.RawGet(key, sort, recursive)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return raw.Unmarshal()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) RawGet(key string, sort, recursive bool) (*RawResponse, error) {
|
|
||||||
var q bool
|
|
||||||
if c.config.Consistency == STRONG_CONSISTENCY {
|
|
||||||
q = true
|
|
||||||
}
|
|
||||||
ops := Options{
|
|
||||||
"recursive": recursive,
|
|
||||||
"sorted": sort,
|
|
||||||
"quorum": q,
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.get(key, ops)
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
package etcd
|
|
||||||
|
|
||||||
import "encoding/json"
|
|
||||||
|
|
||||||
type Member struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
PeerURLs []string `json:"peerURLs"`
|
|
||||||
ClientURLs []string `json:"clientURLs"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type memberCollection []Member
|
|
||||||
|
|
||||||
func (c *memberCollection) UnmarshalJSON(data []byte) error {
|
|
||||||
d := struct {
|
|
||||||
Members []Member
|
|
||||||
}{}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(data, &d); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if d.Members == nil {
|
|
||||||
*c = make([]Member, 0)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
*c = d.Members
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
package etcd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"reflect"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Options map[string]interface{}
|
|
||||||
|
|
||||||
// An internally-used data structure that represents a mapping
|
|
||||||
// between valid options and their kinds
|
|
||||||
type validOptions map[string]reflect.Kind
|
|
||||||
|
|
||||||
// Valid options for GET, PUT, POST, DELETE
|
|
||||||
// Using CAPITALIZED_UNDERSCORE to emphasize that these
|
|
||||||
// values are meant to be used as constants.
|
|
||||||
var (
|
|
||||||
VALID_GET_OPTIONS = validOptions{
|
|
||||||
"recursive": reflect.Bool,
|
|
||||||
"quorum": reflect.Bool,
|
|
||||||
"sorted": reflect.Bool,
|
|
||||||
"wait": reflect.Bool,
|
|
||||||
"waitIndex": reflect.Uint64,
|
|
||||||
}
|
|
||||||
|
|
||||||
VALID_PUT_OPTIONS = validOptions{
|
|
||||||
"prevValue": reflect.String,
|
|
||||||
"prevIndex": reflect.Uint64,
|
|
||||||
"prevExist": reflect.Bool,
|
|
||||||
"dir": reflect.Bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
VALID_POST_OPTIONS = validOptions{}
|
|
||||||
|
|
||||||
VALID_DELETE_OPTIONS = validOptions{
|
|
||||||
"recursive": reflect.Bool,
|
|
||||||
"dir": reflect.Bool,
|
|
||||||
"prevValue": reflect.String,
|
|
||||||
"prevIndex": reflect.Uint64,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Convert options to a string of HTML parameters
|
|
||||||
func (ops Options) toParameters(validOps validOptions) (string, error) {
|
|
||||||
p := "?"
|
|
||||||
values := url.Values{}
|
|
||||||
|
|
||||||
if ops == nil {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range ops {
|
|
||||||
// Check if the given option is valid (that it exists)
|
|
||||||
kind := validOps[k]
|
|
||||||
if kind == reflect.Invalid {
|
|
||||||
return "", fmt.Errorf("Invalid option: %v", k)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the given option is of the valid type
|
|
||||||
t := reflect.TypeOf(v)
|
|
||||||
if kind != t.Kind() {
|
|
||||||
return "", fmt.Errorf("Option %s should be of %v kind, not of %v kind.",
|
|
||||||
k, kind, t.Kind())
|
|
||||||
}
|
|
||||||
|
|
||||||
values.Set(k, fmt.Sprintf("%v", v))
|
|
||||||
}
|
|
||||||
|
|
||||||
p += values.Encode()
|
|
||||||
return p, nil
|
|
||||||
}
|
|
|
@ -1,403 +0,0 @@
|
||||||
package etcd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Errors introduced by handling requests
|
|
||||||
var (
|
|
||||||
ErrRequestCancelled = errors.New("sending request is cancelled")
|
|
||||||
)
|
|
||||||
|
|
||||||
type RawRequest struct {
|
|
||||||
Method string
|
|
||||||
RelativePath string
|
|
||||||
Values url.Values
|
|
||||||
Cancel <-chan bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRawRequest returns a new RawRequest
|
|
||||||
func NewRawRequest(method, relativePath string, values url.Values, cancel <-chan bool) *RawRequest {
|
|
||||||
return &RawRequest{
|
|
||||||
Method: method,
|
|
||||||
RelativePath: relativePath,
|
|
||||||
Values: values,
|
|
||||||
Cancel: cancel,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getCancelable issues a cancelable GET request
|
|
||||||
func (c *Client) getCancelable(key string, options Options,
|
|
||||||
cancel <-chan bool) (*RawResponse, error) {
|
|
||||||
logger.Debugf("get %s [%s]", key, c.cluster.pick())
|
|
||||||
p := keyToPath(key)
|
|
||||||
|
|
||||||
str, err := options.toParameters(VALID_GET_OPTIONS)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p += str
|
|
||||||
|
|
||||||
req := NewRawRequest("GET", p, nil, cancel)
|
|
||||||
resp, err := c.SendRequest(req)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// get issues a GET request
|
|
||||||
func (c *Client) get(key string, options Options) (*RawResponse, error) {
|
|
||||||
return c.getCancelable(key, options, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// put issues a PUT request
|
|
||||||
func (c *Client) put(key string, value string, ttl uint64,
|
|
||||||
options Options) (*RawResponse, error) {
|
|
||||||
|
|
||||||
logger.Debugf("put %s, %s, ttl: %d, [%s]", key, value, ttl, c.cluster.pick())
|
|
||||||
p := keyToPath(key)
|
|
||||||
|
|
||||||
str, err := options.toParameters(VALID_PUT_OPTIONS)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p += str
|
|
||||||
|
|
||||||
req := NewRawRequest("PUT", p, buildValues(value, ttl), nil)
|
|
||||||
resp, err := c.SendRequest(req)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// post issues a POST request
|
|
||||||
func (c *Client) post(key string, value string, ttl uint64) (*RawResponse, error) {
|
|
||||||
logger.Debugf("post %s, %s, ttl: %d, [%s]", key, value, ttl, c.cluster.pick())
|
|
||||||
p := keyToPath(key)
|
|
||||||
|
|
||||||
req := NewRawRequest("POST", p, buildValues(value, ttl), nil)
|
|
||||||
resp, err := c.SendRequest(req)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete issues a DELETE request
|
|
||||||
func (c *Client) delete(key string, options Options) (*RawResponse, error) {
|
|
||||||
logger.Debugf("delete %s [%s]", key, c.cluster.pick())
|
|
||||||
p := keyToPath(key)
|
|
||||||
|
|
||||||
str, err := options.toParameters(VALID_DELETE_OPTIONS)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p += str
|
|
||||||
|
|
||||||
req := NewRawRequest("DELETE", p, nil, nil)
|
|
||||||
resp, err := c.SendRequest(req)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendRequest sends a HTTP request and returns a Response as defined by etcd
|
|
||||||
func (c *Client) SendRequest(rr *RawRequest) (*RawResponse, error) {
|
|
||||||
var req *http.Request
|
|
||||||
var resp *http.Response
|
|
||||||
var httpPath string
|
|
||||||
var err error
|
|
||||||
var respBody []byte
|
|
||||||
|
|
||||||
var numReqs = 1
|
|
||||||
|
|
||||||
checkRetry := c.CheckRetry
|
|
||||||
if checkRetry == nil {
|
|
||||||
checkRetry = DefaultCheckRetry
|
|
||||||
}
|
|
||||||
|
|
||||||
cancelled := make(chan bool, 1)
|
|
||||||
reqLock := new(sync.Mutex)
|
|
||||||
|
|
||||||
if rr.Cancel != nil {
|
|
||||||
cancelRoutine := make(chan bool)
|
|
||||||
defer close(cancelRoutine)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
select {
|
|
||||||
case <-rr.Cancel:
|
|
||||||
cancelled <- true
|
|
||||||
logger.Debug("send.request is cancelled")
|
|
||||||
case <-cancelRoutine:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Repeat canceling request until this thread is stopped
|
|
||||||
// because we have no idea about whether it succeeds.
|
|
||||||
for {
|
|
||||||
reqLock.Lock()
|
|
||||||
c.httpClient.Transport.(*http.Transport).CancelRequest(req)
|
|
||||||
reqLock.Unlock()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-time.After(100 * time.Millisecond):
|
|
||||||
case <-cancelRoutine:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we connect to a follower and consistency is required, retry until
|
|
||||||
// we connect to a leader
|
|
||||||
sleep := 25 * time.Millisecond
|
|
||||||
maxSleep := time.Second
|
|
||||||
|
|
||||||
for attempt := 0; ; attempt++ {
|
|
||||||
if attempt > 0 {
|
|
||||||
select {
|
|
||||||
case <-cancelled:
|
|
||||||
return nil, ErrRequestCancelled
|
|
||||||
case <-time.After(sleep):
|
|
||||||
sleep = sleep * 2
|
|
||||||
if sleep > maxSleep {
|
|
||||||
sleep = maxSleep
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Debug("Connecting to etcd: attempt ", attempt+1, " for ", rr.RelativePath)
|
|
||||||
|
|
||||||
// get httpPath if not set
|
|
||||||
if httpPath == "" {
|
|
||||||
httpPath = c.getHttpPath(rr.RelativePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return a cURL command if curlChan is set
|
|
||||||
if c.cURLch != nil {
|
|
||||||
command := fmt.Sprintf("curl -X %s %s", rr.Method, httpPath)
|
|
||||||
for key, value := range rr.Values {
|
|
||||||
command += fmt.Sprintf(" -d %s=%s", key, value[0])
|
|
||||||
}
|
|
||||||
if c.credentials != nil {
|
|
||||||
command += fmt.Sprintf(" -u %s", c.credentials.username)
|
|
||||||
}
|
|
||||||
c.sendCURL(command)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Debug("send.request.to ", httpPath, " | method ", rr.Method)
|
|
||||||
|
|
||||||
req, err := func() (*http.Request, error) {
|
|
||||||
reqLock.Lock()
|
|
||||||
defer reqLock.Unlock()
|
|
||||||
|
|
||||||
if rr.Values == nil {
|
|
||||||
if req, err = http.NewRequest(rr.Method, httpPath, nil); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
body := strings.NewReader(rr.Values.Encode())
|
|
||||||
if req, err = http.NewRequest(rr.Method, httpPath, body); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Set("Content-Type",
|
|
||||||
"application/x-www-form-urlencoded; param=value")
|
|
||||||
}
|
|
||||||
return req, nil
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.credentials != nil {
|
|
||||||
req.SetBasicAuth(c.credentials.username, c.credentials.password)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err = c.httpClient.Do(req)
|
|
||||||
// clear previous httpPath
|
|
||||||
httpPath = ""
|
|
||||||
defer func() {
|
|
||||||
if resp != nil {
|
|
||||||
resp.Body.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// If the request was cancelled, return ErrRequestCancelled directly
|
|
||||||
select {
|
|
||||||
case <-cancelled:
|
|
||||||
return nil, ErrRequestCancelled
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
numReqs++
|
|
||||||
|
|
||||||
// network error, change a machine!
|
|
||||||
if err != nil {
|
|
||||||
logger.Debug("network error: ", err.Error())
|
|
||||||
lastResp := http.Response{}
|
|
||||||
if checkErr := checkRetry(c.cluster, numReqs, lastResp, err); checkErr != nil {
|
|
||||||
return nil, checkErr
|
|
||||||
}
|
|
||||||
|
|
||||||
c.cluster.failure()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// if there is no error, it should receive response
|
|
||||||
logger.Debug("recv.response.from ", httpPath)
|
|
||||||
|
|
||||||
if validHttpStatusCode[resp.StatusCode] {
|
|
||||||
// try to read byte code and break the loop
|
|
||||||
respBody, err = ioutil.ReadAll(resp.Body)
|
|
||||||
if err == nil {
|
|
||||||
logger.Debug("recv.success ", httpPath)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// ReadAll error may be caused due to cancel request
|
|
||||||
select {
|
|
||||||
case <-cancelled:
|
|
||||||
return nil, ErrRequestCancelled
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == io.ErrUnexpectedEOF {
|
|
||||||
// underlying connection was closed prematurely, probably by timeout
|
|
||||||
// TODO: empty body or unexpectedEOF can cause http.Transport to get hosed;
|
|
||||||
// this allows the client to detect that and take evasive action. Need
|
|
||||||
// to revisit once code.google.com/p/go/issues/detail?id=8648 gets fixed.
|
|
||||||
respBody = []byte{}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode == http.StatusTemporaryRedirect {
|
|
||||||
u, err := resp.Location()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning(err)
|
|
||||||
} else {
|
|
||||||
// set httpPath for following redirection
|
|
||||||
httpPath = u.String()
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if checkErr := checkRetry(c.cluster, numReqs, *resp,
|
|
||||||
errors.New("Unexpected HTTP status code")); checkErr != nil {
|
|
||||||
return nil, checkErr
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
r := &RawResponse{
|
|
||||||
StatusCode: resp.StatusCode,
|
|
||||||
Body: respBody,
|
|
||||||
Header: resp.Header,
|
|
||||||
}
|
|
||||||
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultCheckRetry defines the retrying behaviour for bad HTTP requests
|
|
||||||
// If we have retried 2 * machine number, stop retrying.
|
|
||||||
// If status code is InternalServerError, sleep for 200ms.
|
|
||||||
func DefaultCheckRetry(cluster *Cluster, numReqs int, lastResp http.Response,
|
|
||||||
err error) error {
|
|
||||||
|
|
||||||
if numReqs > 2*len(cluster.Machines) {
|
|
||||||
errStr := fmt.Sprintf("failed to propose on members %v twice [last error: %v]", cluster.Machines, err)
|
|
||||||
return newError(ErrCodeEtcdNotReachable, errStr, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if isEmptyResponse(lastResp) {
|
|
||||||
// always retry if it failed to get response from one machine
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if !shouldRetry(lastResp) {
|
|
||||||
body := []byte("nil")
|
|
||||||
if lastResp.Body != nil {
|
|
||||||
if b, err := ioutil.ReadAll(lastResp.Body); err == nil {
|
|
||||||
body = b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
errStr := fmt.Sprintf("unhandled http status [%s] with body [%s]", http.StatusText(lastResp.StatusCode), body)
|
|
||||||
return newError(ErrCodeUnhandledHTTPStatus, errStr, 0)
|
|
||||||
}
|
|
||||||
// sleep some time and expect leader election finish
|
|
||||||
time.Sleep(time.Millisecond * 200)
|
|
||||||
logger.Warning("bad response status code ", lastResp.StatusCode)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isEmptyResponse(r http.Response) bool { return r.StatusCode == 0 }
|
|
||||||
|
|
||||||
// shouldRetry returns whether the reponse deserves retry.
|
|
||||||
func shouldRetry(r http.Response) bool {
|
|
||||||
// TODO: only retry when the cluster is in leader election
|
|
||||||
// We cannot do it exactly because etcd doesn't support it well.
|
|
||||||
return r.StatusCode == http.StatusInternalServerError
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) getHttpPath(s ...string) string {
|
|
||||||
fullPath := c.cluster.pick() + "/" + version
|
|
||||||
for _, seg := range s {
|
|
||||||
fullPath = fullPath + "/" + seg
|
|
||||||
}
|
|
||||||
return fullPath
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildValues builds a url.Values map according to the given value and ttl
|
|
||||||
func buildValues(value string, ttl uint64) url.Values {
|
|
||||||
v := url.Values{}
|
|
||||||
|
|
||||||
if value != "" {
|
|
||||||
v.Set("value", value)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ttl > 0 {
|
|
||||||
v.Set("ttl", fmt.Sprintf("%v", ttl))
|
|
||||||
}
|
|
||||||
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert key string to http path exclude version, including URL escaping
|
|
||||||
// for example: key[foo] -> path[keys/foo]
|
|
||||||
// key[/%z] -> path[keys/%25z]
|
|
||||||
// key[/] -> path[keys/]
|
|
||||||
func keyToPath(key string) string {
|
|
||||||
// URL-escape our key, except for slashes
|
|
||||||
p := strings.Replace(url.QueryEscape(path.Join("keys", key)), "%2F", "/", -1)
|
|
||||||
|
|
||||||
// corner case: if key is "/" or "//" ect
|
|
||||||
// path join will clear the tailing "/"
|
|
||||||
// we need to add it back
|
|
||||||
if p == "keys" {
|
|
||||||
p = "keys/"
|
|
||||||
}
|
|
||||||
|
|
||||||
return p
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,93 +0,0 @@
|
||||||
package etcd
|
|
||||||
|
|
||||||
//go:generate codecgen -d 1978 -o response.generated.go response.go
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ugorji/go/codec"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
rawResponse = iota
|
|
||||||
normalResponse
|
|
||||||
)
|
|
||||||
|
|
||||||
type responseType int
|
|
||||||
|
|
||||||
type RawResponse struct {
|
|
||||||
StatusCode int
|
|
||||||
Body []byte
|
|
||||||
Header http.Header
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
validHttpStatusCode = map[int]bool{
|
|
||||||
http.StatusCreated: true,
|
|
||||||
http.StatusOK: true,
|
|
||||||
http.StatusBadRequest: true,
|
|
||||||
http.StatusNotFound: true,
|
|
||||||
http.StatusPreconditionFailed: true,
|
|
||||||
http.StatusForbidden: true,
|
|
||||||
http.StatusUnauthorized: true,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Unmarshal parses RawResponse and stores the result in Response
|
|
||||||
func (rr *RawResponse) Unmarshal() (*Response, error) {
|
|
||||||
if rr.StatusCode != http.StatusOK && rr.StatusCode != http.StatusCreated {
|
|
||||||
return nil, handleError(rr.Body)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := new(Response)
|
|
||||||
|
|
||||||
err := codec.NewDecoderBytes(rr.Body, new(codec.JsonHandle)).Decode(resp)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// attach index and term to response
|
|
||||||
resp.EtcdIndex, _ = strconv.ParseUint(rr.Header.Get("X-Etcd-Index"), 10, 64)
|
|
||||||
resp.RaftIndex, _ = strconv.ParseUint(rr.Header.Get("X-Raft-Index"), 10, 64)
|
|
||||||
resp.RaftTerm, _ = strconv.ParseUint(rr.Header.Get("X-Raft-Term"), 10, 64)
|
|
||||||
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Response struct {
|
|
||||||
Action string `json:"action"`
|
|
||||||
Node *Node `json:"node"`
|
|
||||||
PrevNode *Node `json:"prevNode,omitempty"`
|
|
||||||
EtcdIndex uint64 `json:"etcdIndex"`
|
|
||||||
RaftIndex uint64 `json:"raftIndex"`
|
|
||||||
RaftTerm uint64 `json:"raftTerm"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Node struct {
|
|
||||||
Key string `json:"key, omitempty"`
|
|
||||||
Value string `json:"value,omitempty"`
|
|
||||||
Dir bool `json:"dir,omitempty"`
|
|
||||||
Expiration *time.Time `json:"expiration,omitempty"`
|
|
||||||
TTL int64 `json:"ttl,omitempty"`
|
|
||||||
Nodes Nodes `json:"nodes,omitempty"`
|
|
||||||
ModifiedIndex uint64 `json:"modifiedIndex,omitempty"`
|
|
||||||
CreatedIndex uint64 `json:"createdIndex,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Nodes []*Node
|
|
||||||
|
|
||||||
// interfaces for sorting
|
|
||||||
func (ns Nodes) Len() int {
|
|
||||||
return len(ns)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns Nodes) Less(i, j int) bool {
|
|
||||||
return ns[i].Key < ns[j].Key
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns Nodes) Swap(i, j int) {
|
|
||||||
ns[i], ns[j] = ns[j], ns[i]
|
|
||||||
}
|
|
|
@ -1,137 +0,0 @@
|
||||||
package etcd
|
|
||||||
|
|
||||||
// Set sets the given key to the given value.
|
|
||||||
// It will create a new key value pair or replace the old one.
|
|
||||||
// It will not replace a existing directory.
|
|
||||||
func (c *Client) Set(key string, value string, ttl uint64) (*Response, error) {
|
|
||||||
raw, err := c.RawSet(key, value, ttl)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return raw.Unmarshal()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDir sets the given key to a directory.
|
|
||||||
// It will create a new directory or replace the old key value pair by a directory.
|
|
||||||
// It will not replace a existing directory.
|
|
||||||
func (c *Client) SetDir(key string, ttl uint64) (*Response, error) {
|
|
||||||
raw, err := c.RawSetDir(key, ttl)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return raw.Unmarshal()
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateDir creates a directory. It succeeds only if
|
|
||||||
// the given key does not yet exist.
|
|
||||||
func (c *Client) CreateDir(key string, ttl uint64) (*Response, error) {
|
|
||||||
raw, err := c.RawCreateDir(key, ttl)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return raw.Unmarshal()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateDir updates the given directory. It succeeds only if the
|
|
||||||
// given key already exists.
|
|
||||||
func (c *Client) UpdateDir(key string, ttl uint64) (*Response, error) {
|
|
||||||
raw, err := c.RawUpdateDir(key, ttl)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return raw.Unmarshal()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create creates a file with the given value under the given key. It succeeds
|
|
||||||
// only if the given key does not yet exist.
|
|
||||||
func (c *Client) Create(key string, value string, ttl uint64) (*Response, error) {
|
|
||||||
raw, err := c.RawCreate(key, value, ttl)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return raw.Unmarshal()
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateInOrder creates a file with a key that's guaranteed to be higher than other
|
|
||||||
// keys in the given directory. It is useful for creating queues.
|
|
||||||
func (c *Client) CreateInOrder(dir string, value string, ttl uint64) (*Response, error) {
|
|
||||||
raw, err := c.RawCreateInOrder(dir, value, ttl)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return raw.Unmarshal()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update updates the given key to the given value. It succeeds only if the
|
|
||||||
// given key already exists.
|
|
||||||
func (c *Client) Update(key string, value string, ttl uint64) (*Response, error) {
|
|
||||||
raw, err := c.RawUpdate(key, value, ttl)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return raw.Unmarshal()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) RawUpdateDir(key string, ttl uint64) (*RawResponse, error) {
|
|
||||||
ops := Options{
|
|
||||||
"prevExist": true,
|
|
||||||
"dir": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.put(key, "", ttl, ops)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) RawCreateDir(key string, ttl uint64) (*RawResponse, error) {
|
|
||||||
ops := Options{
|
|
||||||
"prevExist": false,
|
|
||||||
"dir": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.put(key, "", ttl, ops)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) RawSet(key string, value string, ttl uint64) (*RawResponse, error) {
|
|
||||||
return c.put(key, value, ttl, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) RawSetDir(key string, ttl uint64) (*RawResponse, error) {
|
|
||||||
ops := Options{
|
|
||||||
"dir": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.put(key, "", ttl, ops)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) RawUpdate(key string, value string, ttl uint64) (*RawResponse, error) {
|
|
||||||
ops := Options{
|
|
||||||
"prevExist": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.put(key, value, ttl, ops)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) RawCreate(key string, value string, ttl uint64) (*RawResponse, error) {
|
|
||||||
ops := Options{
|
|
||||||
"prevExist": false,
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.put(key, value, ttl, ops)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) RawCreateInOrder(dir string, value string, ttl uint64) (*RawResponse, error) {
|
|
||||||
return c.post(dir, value, ttl)
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
package etcd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/rand"
|
|
||||||
)
|
|
||||||
|
|
||||||
func shuffleStringSlice(cards []string) []string {
|
|
||||||
size := len(cards)
|
|
||||||
//Do not need to copy if nothing changed
|
|
||||||
if size <= 1 {
|
|
||||||
return cards
|
|
||||||
}
|
|
||||||
shuffled := make([]string, size)
|
|
||||||
index := rand.Perm(size)
|
|
||||||
for i := range cards {
|
|
||||||
shuffled[index[i]] = cards[i]
|
|
||||||
}
|
|
||||||
return shuffled
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
package etcd
|
|
||||||
|
|
||||||
const (
|
|
||||||
version = "v2"
|
|
||||||
packageVersion = "v2.0.0+git"
|
|
||||||
)
|
|
|
@ -1,103 +0,0 @@
|
||||||
package etcd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Errors introduced by the Watch command.
|
|
||||||
var (
|
|
||||||
ErrWatchStoppedByUser = errors.New("Watch stopped by the user via stop channel")
|
|
||||||
)
|
|
||||||
|
|
||||||
// If recursive is set to true the watch returns the first change under the given
|
|
||||||
// prefix since the given index.
|
|
||||||
//
|
|
||||||
// If recursive is set to false the watch returns the first change to the given key
|
|
||||||
// since the given index.
|
|
||||||
//
|
|
||||||
// To watch for the latest change, set waitIndex = 0.
|
|
||||||
//
|
|
||||||
// If a receiver channel is given, it will be a long-term watch. Watch will block at the
|
|
||||||
//channel. After someone receives the channel, it will go on to watch that
|
|
||||||
// prefix. If a stop channel is given, the client can close long-term watch using
|
|
||||||
// the stop channel.
|
|
||||||
func (c *Client) Watch(prefix string, waitIndex uint64, recursive bool,
|
|
||||||
receiver chan *Response, stop chan bool) (*Response, error) {
|
|
||||||
logger.Debugf("watch %s [%s]", prefix, c.cluster.Leader)
|
|
||||||
if receiver == nil {
|
|
||||||
raw, err := c.watchOnce(prefix, waitIndex, recursive, stop)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return raw.Unmarshal()
|
|
||||||
}
|
|
||||||
defer close(receiver)
|
|
||||||
|
|
||||||
for {
|
|
||||||
raw, err := c.watchOnce(prefix, waitIndex, recursive, stop)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := raw.Unmarshal()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
waitIndex = resp.Node.ModifiedIndex + 1
|
|
||||||
receiver <- resp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) RawWatch(prefix string, waitIndex uint64, recursive bool,
|
|
||||||
receiver chan *RawResponse, stop chan bool) (*RawResponse, error) {
|
|
||||||
|
|
||||||
logger.Debugf("rawWatch %s [%s]", prefix, c.cluster.Leader)
|
|
||||||
if receiver == nil {
|
|
||||||
return c.watchOnce(prefix, waitIndex, recursive, stop)
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
raw, err := c.watchOnce(prefix, waitIndex, recursive, stop)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := raw.Unmarshal()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
waitIndex = resp.Node.ModifiedIndex + 1
|
|
||||||
receiver <- raw
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// helper func
|
|
||||||
// return when there is change under the given prefix
|
|
||||||
func (c *Client) watchOnce(key string, waitIndex uint64, recursive bool, stop chan bool) (*RawResponse, error) {
|
|
||||||
|
|
||||||
options := Options{
|
|
||||||
"wait": true,
|
|
||||||
}
|
|
||||||
if waitIndex > 0 {
|
|
||||||
options["waitIndex"] = waitIndex
|
|
||||||
}
|
|
||||||
if recursive {
|
|
||||||
options["recursive"] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := c.getCancelable(key, options, stop)
|
|
||||||
|
|
||||||
if err == ErrRequestCancelled {
|
|
||||||
return nil, ErrWatchStoppedByUser
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, err
|
|
||||||
}
|
|
Loading…
Reference in New Issue