diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml new file mode 100644 index 000000000..3fdff2572 --- /dev/null +++ b/.github/workflows/testing.yml @@ -0,0 +1,40 @@ +name: Go + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +permissions: + contents: write + +# Always force the use of Go modules +env: + GO111MODULE: on + +jobs: + + build: + runs-on: ubuntu-latest + steps: + # Setup the environment. + - name: Setup Go + uses: actions/setup-go@v2 + with: + go-version: 1.15 + # Checkout latest code + - name: Checkout repo + uses: actions/checkout@v2 + + # Execute vert check + - name: vert check + run: bash vert.sh -install && bash vert.sh + + # Run tests + - name: run tests + run: | + cd ./test + go test + + # TODO: Examine coverage \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..cc2d03228 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +polaris-server +polaris-server-release +polaris-server-release.tar.gz + +/log +/statis + +*.swp +*.code-workspace + +.idea diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..d915636a2 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,26 @@ +# Contributing +--- +If you have good comments or suggestions, welcome to create [Issues](https://github.com/Tencent/polaris/issues) or [Pull Requests](https://github.com/Tencent/polaris/pulls),contribute to the polaris open source community. Polaris continues to recruit contributors, even if it is answering questions in the issue, or doing some simple bugfixes, it will be of great help to polaris. + +[Tencent Open Source Incentive Program](https://opensource.tencent.com/contribution) Encourage developers to participate and contribute, and look forward to your joining. + +## Issue +#### For contributors + +Please ensure that the following conditions are met before submitting an issue: + +* Must be a bug or new feature +* Have searched in the issue, and did not find a similar issue or solution +* When creating a new issue, please provide a detailed description, screenshot or short video to help us locate the problem + +## Pull Request +We welcome everyone to contribute code to make our product more powerful. The code team will monitor all pull requests, and we will do the corresponding code inspection and testing. After the test passes, we will accept the PR, but will not immediately merge into the master branch. + +Please confirm before completing a PR: + +1. Fork your own branch from the master branch. +2. Please modify the corresponding documents and comments after modifying the code. +3. Please add License and Copyright declarations in the newly created file. +4. Ensure a consistent code style. +5. Do adequate testing. +6. Then, you can submit your code to the dev branch. \ No newline at end of file diff --git a/Code-of-Conduct.md b/Code-of-Conduct.md new file mode 100644 index 000000000..99d510964 --- /dev/null +++ b/Code-of-Conduct.md @@ -0,0 +1,5 @@ +# PolarisMesh Community Code of Conduct +PolarisMesh follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). + + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the PolarisMesh Code of Conduct Committee via email: PolarisMesh_Community@qq.com \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..6b631df66 --- /dev/null +++ b/LICENSE @@ -0,0 +1,892 @@ +Tencent is pleased to support the open source community by making polaris available. + +Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + +A polaris is licensed under the BSD 3-Clause License. A copy of the BSD 3-Clause License is included in this file. + + +Terms of BSD 3-Clause License +--------------------------------------------------- +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +Other dependencies and licenses: + + +Open Source Software Licensed under the BSD 2-Clause License: +-------------------------------------------------------------------- +1. errors +Copyright (c) 2015, Dave Cheney + +2. go-check +Copyright (c) 2010-2013 Gustavo Niemeyer + +Terms of the BSD 2-Clause License: +-------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + +Open Source Software Licensed under the BSD 3-Clause License: +-------------------------------------------------------------------- +1. protobuf +Copyright (c) 2013, The GoGo Authors. All rights reserved. + +2. protobuf +Copyright 2010 The Go Authors. All rights reserved. + +3. uuid +Copyright (c) 2009,2014 Google Inc. All rights reserved. + +4. Go Networking +Copyright (c) 2009 The Go Authors. All rights reserved. + +5. Go Time +Copyright (c) 2009 The Go Authors. All rights reserved. + +6. Gonum Stat +Copyright ©2013 The Gonum Authors. All rights reserved. + +7. murmur3 +Copyright 2013, Sébastien Paolacci. +All rights reserved. + +8. protobuf +Copyright 2014, Google Inc. All rights reserved. + +The BSD 3-Clause license applies to all parts of Protocol Buffers except the following: + +- Atomicops support for generic gcc, located in + src/google/protobuf/stubs/atomicops_internals_generic_gcc.h. + This file is copyrighted by Red Hat Inc. + +- Atomicops support for AIX/POWER, located in + src/google/protobuf/stubs/atomicops_internals_power.h. + This file is copyrighted by Bloomberg Finance LP. + +9. re2 +Copyright (c) 2009 The RE2 Authors. All rights reserved. + +10. googletest +Copyright 2008, Google Inc. + + + +Terms of the BSD 3-Clause License: +-------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +Open Source Software Licensed under the Permissive Type License: +-------------------------------------------------------------------- +1. GoConvey +Copyright (c) 2016 SmartyStreets, LLC + +Terms of the Permissive Type License: +-------------------------------------------------------------------- +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +NOTE: Various optional and subordinate components carry their own licensing requirements and restrictions. Use of those components is subject to the terms and conditions outlined the respective license of each component. + + +Open Source Software Licensed under the Apache License Version 2.0: +-------------------------------------------------------------------- +1. gomock +Copyright (c) gomock authors and contributors + +2. Redigo +Copyright (c) Redigo authors and contributors + +3. Cobra +Copyright (c) Cobra authors and contributors + +4. YAML +Copyright (c) YAML authors and contributors + +5. reflect2 +Copyright (c) reflect2 authors and contributors + +6. gRPC-Go +Copyright (c) gRPC-Go authors and contributors + +7. gRPC-Java +Copyright (c) gRPC-Java authors and contributors + +8. Log4J +Copyright 1999-2005 The Apache Software Foundation + +9. benchmark +Copyright (c) benchmark authors and contributors. + +10. spring-boot +Copyright (c) spring-boot authors and contributors. + +11. spring-framework +Copyright (c) spring-framework authors and contributors. + +12. spring-cloud-netflix +Copyright (c) spring-cloud-netflix authors and contributors. + +13. spring-cloud config +Copyright (c) spring-cloud-config authors and contributors. + +14. guava +Copyright (c) guava authors and contributors. + +15. reactor +Copyright (c) reactor authors and contributors. + +16. powermock +Copyright 2007-2017 PowerMock Contributors + + +Terms of the Apache v2.0 License: +-------------------------------------------------------------------- +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: + +You must give any other recipients of the Work or Derivative Works a copy of this License; and + +You must cause any modified files to carry prominent notices stating that You changed the files; and + +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 + +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 + + +Open Source Software Licensed Under the MIT License: +The below software in this distribution may have been modified by THL A29 Limited +-------------------------------------------------------------------- +1. go-restful +Copyright (c) 2012,2013 Ernest Micklei + +2. mapstructure +Copyright (c) 2013 Mitchell Hashimoto + +3. lumberjack +Copyright (c) 2014 Nate Finch + +4. Testify +Copyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors. + +5. zap +Copyright (c) 2016-2017 Uber Technologies, Inc. + +6. gomonkey +Copyright (c) 2018 Zhang Xiaolong + +7. go-homedir +Copyright (c) 2013 Mitchell Hashimoto + +8. SLF4J +Copyright (c) 2004-2017 QOS.ch + +9. nghttp2 +Copyright (c) 2012, 2014, 2015, 2016 Tatsuhiro Tsujikawa +Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors + +10. yaml-cpp +Copyright (c) 2008 Jesse Beder. + +11. murmurhash +Copyright murmurhash authors and contributors + +Terms of the MIT License: +--------------------------------------------------- +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +Open Source Software Licensed under the Mozilla Public License, version 2.0 License: +-------------------------------------------------------------------- +1. Go-MySQL-Driver +Copyright (c) 2013, The GoGo Authors. All rights reserved. + +2. golang-lru +Copyright (c) golang-lru authors and contributors + +3. go-multierror +go-multierror (c) authors and contributors + +Terms of the Mozilla Public License, version 2.0 License: +--------------------------------------------------- +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. + + +Open Source Software Licensed under the Eclipse Public License, version 1.0 License: +-------------------------------------------------------------------- +1. Junit4 +Copyright (c) junit4 authors and contributors + + +Terms of the Eclipse Public License, version 1.0 License: +--------------------------------------------------- +Eclipse Public License - v 1.0 + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC +LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM +CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + + a) in the case of the initial Contributor, the initial code and + documentation distributed under this Agreement, and + b) in the case of each subsequent Contributor: + + i) changes to the Program, and + + ii) additions to the Program; + + where such changes and/or additions to the Program originate from and are +distributed by that particular Contributor. A Contribution 'originates' from a +Contributor if it was added to the Program by such Contributor itself or anyone +acting on such Contributor's behalf. Contributions do not include additions to +the Program which: (i) are separate modules of software distributed in +conjunction with the Program under their own license agreement, and (ii) are +not derivative works of the Program. + +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents " mean patent claims licensable by a Contributor which are +necessarily infringed by the use or sale of its Contribution alone or when +combined with the Program. + +"Program" means the Contributions distributed in accordance with this Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, +including all Contributors. + +2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby grants +Recipient a non-exclusive, worldwide, royalty-free copyright license to +reproduce, prepare derivative works of, publicly display, publicly perform, +distribute and sublicense the Contribution of such Contributor, if any, and +such derivative works, in source code and object code form. + + b) Subject to the terms of this Agreement, each Contributor hereby grants +Recipient a non-exclusive, worldwide, royalty-free patent license under +Licensed Patents to make, use, sell, offer to sell, import and otherwise +transfer the Contribution of such Contributor, if any, in source code and +object code form. This patent license shall apply to the combination of the +Contribution and the Program if, at the time the Contribution is added by the +Contributor, such addition of the Contribution causes such combination to be +covered by the Licensed Patents. The patent license shall not apply to any +other combinations which include the Contribution. No hardware per se is +licensed hereunder. + + c) Recipient understands that although each Contributor grants the +licenses to its Contributions set forth herein, no assurances are provided by +any Contributor that the Program does not infringe the patent or other +intellectual property rights of any other entity. Each Contributor disclaims +any liability to Recipient for claims brought by any other entity based on +infringement of intellectual property rights or otherwise. As a condition to +exercising the rights and licenses granted hereunder, each Recipient hereby +assumes sole responsibility to secure any other intellectual property rights +needed, if any. For example, if a third party patent license is required to +allow Recipient to distribute the Program, it is Recipient's responsibility to +acquire that license before distributing the Program. + + d) Each Contributor represents that to its knowledge it has sufficient +copyright rights in its Contribution, if any, to grant the copyright license +set forth in this Agreement. + +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form under +its own license agreement, provided that: + + a) it complies with the terms and conditions of this Agreement; and + + b) its license agreement: + + i) effectively disclaims on behalf of all Contributors all warranties and +conditions, express and implied, including warranties or conditions of title +and non-infringement, and implied warranties or conditions of merchantability +and fitness for a particular purpose; + + ii) effectively excludes on behalf of all Contributors all liability for +damages, including direct, indirect, special, incidental and consequential +damages, such as lost profits; + + iii) states that any provisions which differ from this Agreement are +offered by that Contributor alone and not by any other party; and + + iv) states that source code for the Program is available from such +Contributor, and informs licensees how to obtain it in a reasonable manner on +or through a medium customarily used for software exchange. + +When the Program is made available in source code form: + + a) it must be made available under this Agreement; and + + b) a copy of this Agreement must be included with each copy of the +Program. + +Contributors may not remove or alter any copyright notices contained within the +Program. + +Each Contributor must identify itself as the originator of its Contribution, if +any, in a manner that reasonably allows subsequent Recipients to identify the +originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities with +respect to end users, business partners and the like. While this license is +intended to facilitate the commercial use of the Program, the Contributor who +includes the Program in a commercial product offering should do so in a manner +which does not create potential liability for other Contributors. Therefore, if +a Contributor includes the Program in a commercial product offering, such +Contributor ("Commercial Contributor") hereby agrees to defend and indemnify +every other Contributor ("Indemnified Contributor") against any losses, damages +and costs (collectively "Losses") arising from claims, lawsuits and other legal +actions brought by a third party against the Indemnified Contributor to the +extent caused by the acts or omissions of such Commercial Contributor in +connection with its distribution of the Program in a commercial product +offering. The obligations in this section do not apply to any claims or Losses +relating to any actual or alleged intellectual property infringement. In order +to qualify, an Indemnified Contributor must: a) promptly notify the Commercial +Contributor in writing of such claim, and b) allow the Commercial Contributor +to control, and cooperate with the Commercial Contributor in, the defense and +any related settlement negotiations. The Indemnified Contributor may +participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial product +offering, Product X. That Contributor is then a Commercial Contributor. If that +Commercial Contributor then makes performance claims, or offers warranties +related to Product X, those performance claims and warranties are such +Commercial Contributor's responsibility alone. Under this section, the +Commercial Contributor would have to defend claims against the other +Contributors related to those performance claims and warranties, and if a court +requires any other Contributor to pay any damages as a result, the Commercial +Contributor must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED 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. Each +Recipient is solely responsible for determining the appropriateness of using +and distributing the Program and assumes all risks associated with its exercise +of rights under this Agreement, including but not limited to the risks and +costs of program errors, compliance with applicable laws, damage to or loss of +data, programs or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY +CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY +WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS +GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under applicable +law, it shall not affect the validity or enforceability of the remainder of the +terms of this Agreement, and without further action by the parties hereto, such +provision shall be reformed to the minimum extent necessary to make such +provision valid and enforceable. + +If Recipient institutes patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging that the +Program itself (excluding combinations of the Program with other software or +hardware) infringes such Recipient's patent(s), then such Recipient's rights +granted under Section 2(b) shall terminate as of the date such litigation is +filed. + +All Recipient's rights under this Agreement shall terminate if it fails to +comply with any of the material terms or conditions of this Agreement and does +not cure such failure in a reasonable period of time after becoming aware of +such noncompliance. If all Recipient's rights under this Agreement terminate, +Recipient agrees to cease use and distribution of the Program as soon as +reasonably practicable. However, Recipient's obligations under this Agreement +and any licenses granted by Recipient relating to the Program shall continue +and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in +order to avoid inconsistency the Agreement is copyrighted and may only be +modified in the following manner. The Agreement Steward reserves the right to +publish new versions (including revisions) of this Agreement from time to time. +No one other than the Agreement Steward has the right to modify this Agreement. +The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to +serve as the Agreement Steward to a suitable separate entity. Each new version +of the Agreement will be given a distinguishing version number. The Program +(including Contributions) may always be distributed subject to the version of +the Agreement under which it was received. In addition, after a new version of +the Agreement is published, Contributor may elect to distribute the Program +(including its Contributions) under the new version. Except as expressly stated +in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to +the intellectual property of any Contributor under this Agreement, whether +expressly, by implication, estoppel or otherwise. All rights in the Program not +expressly granted under this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and the +intellectual property laws of the United States of America. No party to this +Agreement will bring a legal action under this Agreement more than one year +after the cause of action arose. Each party waives its rights to a jury trial +in any resulting litigation. \ No newline at end of file diff --git a/README-zh.md b/README-zh.md new file mode 100644 index 000000000..53ca0b0f1 --- /dev/null +++ b/README-zh.md @@ -0,0 +1,85 @@ + + +___ +# 北极星服务治理中心 +北极星是一个支持多种开发语言、兼容主流开发框架的服务治理中心。支持嵌入式和非嵌入式两种使用方式,嵌入式使用服务治理SDK,非嵌入式使用流量代理Sidecar + +## 概述 +北极星的治理功能是基于服务维度来提供的,北极星的服务可对应到业界主流的框架/平台服务的实现,如[gRPC],[SPRING CLOUD],以及[Kubernetes Service]。基于这些框架/平台开发的应用可以快速接入北极星服务治理。 + +北极星服务端提供以下主流功能特性: + +* ** 服务数据管理 + + 执行可视化控制台或者管理员基于HTTP管理端接口对于服务数据(标签,健康状态,实例信息,治理规则)进行读写操作。 + +* ** 服务注册发现 + + 提供多协议(HTTP,gRPC)接口供被调端服务进行自注册,以及主调端应用发现并拉取其他被调端服务的服务数据,以便接下来进行服务调用。 + +* ** 健康检查 + + 提供多协议(HTTP,gRPC)接口供被调端进行心跳上报,服务端会实时监测心跳记录,对超时的实例进行健康状态变更。 + +## 快速入门 + +### 前置准备 + +#### 准备数据库 + +需要下载并安装MySQL,版本号要求>=5.7,可以在这里进行下载:https://dev.mysql.com/downloads/mysql/5.7.html + +#### 导入数据库建表脚本 + +建表脚本为./store/defaultStore/polaris_server.sql,可通过mysql命令或者admin客户端进行导入 + +#### 准备golang编译环境 + +北极星服务端编译需要golang编译环境,版本号要求>=1.12,可以在这里进行下载:https://golang.org/dl/#featured + +### 编译构建 + +````shell script +chmod +x build.sh +./build.sh +```` +构建完后,可以在当前目录看到polaris-server-release_${version}.tar.gz的软件包。 + +### 安装 + +#### 解压软件包 + +获取polaris-server-release_${version}.tar.gz,并解压 + +#### 修改数据库配置 + +进入解压后的目录,打开polaris-server.yaml,替换DB配置相关的几个变量为实际的数据库参数;##DB_USER##(数据库用户名),##DB_PWD##(数据库密码),##DB_ADDR##(数据库地址),##DB_NAME##(数据库名称) + +#### 执行安装脚本 + +````shell script +chmod +x ./tool/*.sh +#进行安装 +./tool/install.sh +#测试进程是否启动成功 +./tool/p.sh +```` +最后一步运行p.sh后,返回Polaris Server,证明启动成功。 + +#### 验证安装 + +````shell script +curl http://127.0.0.1:8080 +```` +返回Polaris Server,证明功能正常 + +## License + +The polaris is licensed under the BSD 3-Clause License. Copyright and license information can be found in the file [LICENSE](LICENSE) + + + + + + + diff --git a/README.md b/README.md index 7d9450e11..f8d2453ac 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,83 @@ -# polaris - -test + + +___ +# Polaris +Polaris is an operation centre that supports multiple programming languages, with high compatibility to different application framework. +It supports accessing with SDK or sidecar proxy. + +## Overview +Polaris's operation features provided are based on the dimension of the service, +Polaris's service can actualize industry standard framework and service platform, like [gRPC],[SPRING CLOUD],and [Kubernetes Service]. +These applications can switch in Polaris with no downtime. + +Polaris provide features listed as below: + +* ** Service Data Management + + Bringing visibility to the control panel, admin can configure HTTP port (label, health status, instance information, policy). + +* ** Registration and Discovery + + Provide multi-protocol(HTTP,gRPC), self-registration, and caller server's ability to discover and distribute other server end's data for invocation. + +* ** Health Check + + Provide multi-protocol(HTTP,gRPC), provide heartbeat report, server end will monitor heartbeat record, configure overtime health status. + +## Quick Guide + +### Preconditions + +#### Prepare database + +Please download and install MySQL, version requirement >=5.7, download available here: +https://dev.mysql.com/downloads/mysql/5.7.html + +#### Import SQL script + +Point Script: ./store/defaultStore/polaris_server.sql, one can import through mysql admin or console. + +#### Prepare golang compile environment + +Polaris server end needs golang compile environment, version number needs >=1.12, download available here: https://golang.org/dl/#featured. + +### Build + +````shell script +chmod +x build.sh +./build.sh +```` +After built, one can see 'polaris-server-release_${version}.tar.gz' package from the list. + +### Installation + +#### Unzip package + +Obtain polaris-server-release_${version}.tar.gz, and unzip. + +#### Change polaris configuration + +After unzipped, vi polaris-server.yaml, replace DB configuration's variable to real database information +: ##DB_USER## (database username), ##DB_PWD##(database password), ##DB_ADDR##(database address), ##DB_NAME##(database name) + +#### Execute Installation Script + +````shell script +chmod +x ./tool/*.sh +# install +./tool/install.sh +# test whether the process is successful +./tool/p.sh +```` +After all, run ./p.sh, prompt Polaris Server, proof the installation is successful + +#### Verify installation + +````shell script +curl http://127.0.0.1:8080 +```` +Return text is 'Polaris Server', proof features run smoothly + +## License + +The polaris is licensed under the BSD 3-Clause License. Copyright and license information can be found in the file [LICENSE](LICENSE) \ No newline at end of file diff --git a/apiserver/README.md b/apiserver/README.md new file mode 100644 index 000000000..9eec8f6d0 --- /dev/null +++ b/apiserver/README.md @@ -0,0 +1 @@ +# API Server diff --git a/apiserver/apiserver.go b/apiserver/apiserver.go new file mode 100644 index 000000000..3fb0e447b --- /dev/null +++ b/apiserver/apiserver.go @@ -0,0 +1,111 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package apiserver + +import ( + "context" + "errors" + "fmt" + "github.com/polarismesh/polaris-server/common/log" + "strings" +) + +const ( + DiscoverAccess string = "discover" + RegisterAccess string = "register" + HealthcheckAccess string = "healthcheck" +) + +/** + * @brief API服务器配置 + */ +type Config struct { + Name string + Option map[string]interface{} + API map[string]APIConfig +} + +/** + * @brief API配置 + */ +type APIConfig struct { + Enable bool + Include []string +} + +/** + * @brief API服务器接口 + */ +type Apiserver interface { + GetProtocol() string + GetPort() uint32 + Initialize(ctx context.Context, option map[string]interface{}, api map[string]APIConfig) error + Run(errCh chan error) + Stop() + Restart(option map[string]interface{}, api map[string]APIConfig, errCh chan error) error +} + +var ( + Slots = make(map[string]Apiserver) +) + +/** + * @brief 注册API服务器 + */ +func Register(name string, server Apiserver) error { + if _, exist := Slots[name]; exist { + err := errors.New("apiserver name exist") + return err + } + + Slots[name] = server + + return nil +} + +/** + * @brief 获取客户端openMethod + */ +func GetClientOpenMethod(include []string, protocol string) (map[string]bool, error) { + clientAccess := make(map[string][]string) + clientAccess[DiscoverAccess] = []string{"Discover", "ReportClient"} + clientAccess[RegisterAccess] = []string{"RegisterInstance", "DeregisterInstance"} + clientAccess[HealthcheckAccess] = []string{"Heartbeat"} + + openMethod := make(map[string]bool) + // 如果为空,开启全部接口 + if len(include) == 0 { + for key := range clientAccess { + include = append(include, key) + } + } + + for _, item := range include { + if methods, ok := clientAccess[item]; ok { + for _, method := range methods { + method = "/v1.Polaris" + strings.ToUpper(protocol) + "/" + method + openMethod[method] = true + } + } else { + log.Errorf("method %s does not exist in %sserver client access", item, protocol) + return nil, fmt.Errorf("method %s does not exist in %sserver client access", item, protocol) + } + } + + return openMethod, nil +} diff --git a/apiserver/grpcserver/client_access.go b/apiserver/grpcserver/client_access.go new file mode 100644 index 000000000..e56c30c62 --- /dev/null +++ b/apiserver/grpcserver/client_access.go @@ -0,0 +1,193 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package grpcserver + +import ( + "context" + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/utils" + "go.uber.org/zap" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/peer" + "io" + "strings" +) + +/** + * @brief 客户端上报 + */ +func (g *Grpcserver) ReportClient(ctx context.Context, in *api.Client) (*api.Response, error) { + out := g.namingServer.ReportClient(convertContext(ctx), in) + return out, nil +} + +/** + * @brief 注册服务实例 + */ +func (g *Grpcserver) RegisterInstance(ctx context.Context, in *api.Instance) (*api.Response, error) { + // 需要记录操作来源,提高效率,只针对特殊接口添加operator + rCtx := convertContext(ctx) + operator := ParseGrpcOperator(ctx) + rCtx = context.WithValue(rCtx, utils.StringContext("operator"), operator) + out := g.namingServer.CreateInstance(rCtx, in) + return out, nil +} + +/** + * @brief 反注册服务实例 + */ +func (g *Grpcserver) DeregisterInstance(ctx context.Context, in *api.Instance) (*api.Response, error) { + // 需要记录操作来源,提高效率,只针对特殊接口添加operator + rCtx := convertContext(ctx) + operator := ParseGrpcOperator(ctx) + rCtx = context.WithValue(rCtx, utils.StringContext("operator"), operator) + out := g.namingServer.DeleteInstance(rCtx, in) + return out, nil +} + +/** + * @brief 统一发现接口 + */ +func (g *Grpcserver) Discover(server api.PolarisGRPC_DiscoverServer) error { + ctx := convertContext(server.Context()) + clientIP, _ := ctx.Value(utils.StringContext("client-ip")).(string) + clientAddress, _ := ctx.Value(utils.StringContext("client-address")).(string) + requestID, _ := ctx.Value(utils.StringContext("request-id")).(string) + userAgent, _ := ctx.Value(utils.StringContext("user-agent")).(string) + method, _ := grpc.MethodFromServerStream(server) + + for { + in, err := server.Recv() + if nil != err { + if io.EOF == err { + return nil + } + return err + } + + var msg string + msg = fmt.Sprintf("receive grpc discover request: %s", in.Service.String()) + log.Info(msg, + zap.String("type", api.DiscoverRequest_DiscoverRequestType_name[int32(in.Type)]), + zap.String("client-address", clientAddress), + zap.String("user-agent", userAgent), + zap.String("request-id", requestID), + ) + + // 是否允许访问 + if ok := g.allowAccess(method); !ok { + resp := api.NewDiscoverResponse(api.ClientAPINotOpen) + if sendErr := server.Send(resp); sendErr != nil { + return sendErr + } + continue + } + + // stream模式,需要对每个包进行检测 + if code := g.enterRatelimit(clientIP, method); code != api.ExecuteSuccess { + resp := api.NewDiscoverResponse(code) + if err = server.Send(resp); err != nil { + return err + } + continue + } + + var out *api.DiscoverResponse + switch in.Type { + case api.DiscoverRequest_INSTANCE: + out = g.namingServer.ServiceInstancesCache(ctx, in.Service) + case api.DiscoverRequest_ROUTING: + out = g.namingServer.GetRoutingConfigWithCache(ctx, in.Service) + case api.DiscoverRequest_RATE_LIMIT: + out = g.namingServer.GetRateLimitWithCache(ctx, in.Service) + case api.DiscoverRequest_CIRCUIT_BREAKER: + out = g.namingServer.GetCircuitBreakerWithCache(ctx, in.Service) + case api.DiscoverRequest_SERVICES: + out = g.namingServer.GetServiceWithCache(ctx, in.Service) + default: + out = api.NewDiscoverRoutingResponse(api.InvalidDiscoverResource, in.Service) + } + + err = server.Send(out) + if err != nil { + return err + } + } +} + +/** + * @brief 上报心跳 + */ +func (g *Grpcserver) Heartbeat(ctx context.Context, in *api.Instance) (*api.Response, error) { + out := g.namingServer.Heartbeat(convertContext(ctx), in) + return out, nil +} + +/** + * @brief 将GRPC上下文转换成内部上下文 + */ +func convertContext(ctx context.Context) context.Context { + requestID := "" + userAgent := "" + meta, exist := metadata.FromIncomingContext(ctx) + if exist { + ids := meta["request-id"] + if len(ids) > 0 { + requestID = ids[0] + } + agents := meta["user-agent"] + if len(agents) > 0 { + userAgent = agents[0] + } + } + + clientIP := "" + address := "" + if pr, ok := peer.FromContext(ctx); ok && pr.Addr != nil { + address = pr.Addr.String() + addrSlice := strings.Split(address, ":") + if len(addrSlice) == 2 { + clientIP = addrSlice[0] + } + } + + ctx = context.Background() + ctx = context.WithValue(ctx, utils.StringContext("request-id"), requestID) + ctx = context.WithValue(ctx, utils.StringContext("client-ip"), clientIP) + ctx = context.WithValue(ctx, utils.StringContext("client-address"), address) + ctx = context.WithValue(ctx, utils.StringContext("user-agent"), userAgent) + return ctx +} + +// 构造请求源 +func ParseGrpcOperator(ctx context.Context) string { + // 获取请求源 + operator := "GRPC" + if pr, ok := peer.FromContext(ctx); ok && pr.Addr != nil { + address := pr.Addr.String() + addrSlice := strings.Split(address, ":") + if len(addrSlice) == 2 { + operator += ":" + addrSlice[0] + } + } + + return operator +} diff --git a/apiserver/grpcserver/default.go b/apiserver/grpcserver/default.go new file mode 100644 index 000000000..1cac51e30 --- /dev/null +++ b/apiserver/grpcserver/default.go @@ -0,0 +1,29 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package grpcserver + +import ( + "github.com/polarismesh/polaris-server/apiserver" +) + +/** + * @brief 自注册到API服务器插槽 + */ +func init() { + _ = apiserver.Register("grpcserver", &Grpcserver{}) +} diff --git a/apiserver/grpcserver/server.go b/apiserver/grpcserver/server.go new file mode 100644 index 000000000..9ce97b5c2 --- /dev/null +++ b/apiserver/grpcserver/server.go @@ -0,0 +1,450 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package grpcserver + +import ( + "context" + "fmt" + "github.com/polarismesh/polaris-server/apiserver" + "io" + "net" + "net/http" + "strings" + "time" + + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/connlimit" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/naming" + "github.com/polarismesh/polaris-server/plugin" + "go.uber.org/zap" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/peer" +) + +/** + * @brief GRPC API服务器 + */ +type Grpcserver struct { + listenIP string + listenPort uint32 + connLimitConfig *connlimit.Config + start bool + restart bool + exitCh chan struct{} + + server *grpc.Server + namingServer *naming.Server + statis plugin.Statis + ratelimit plugin.Ratelimit + + openAPI map[string]apiserver.APIConfig + openMethod map[string]bool +} + +/** + * @brief 获取端口 + */ +func (g *Grpcserver) GetPort() uint32 { + return g.listenPort +} + +/** + * @brief 获取Server的协议 + */ +func (g *Grpcserver) GetProtocol() string { + return "grpc" +} + +/** + * @brief 初始化GRPC API服务器 + */ +func (g *Grpcserver) Initialize(ctx context.Context, option map[string]interface{}, + api map[string]apiserver.APIConfig) error { + g.listenIP = option["listenIP"].(string) + g.listenPort = uint32(option["listenPort"].(int)) + g.openAPI = api + + if raw, _ := option["connLimit"].(map[interface{}]interface{}); raw != nil { + connConfig, err := connlimit.ParseConnLimitConfig(raw) + if err != nil { + return err + } + g.connLimitConfig = connConfig + } + if ratelimit := plugin.GetRatelimit(); ratelimit != nil { + log.Infof("grpc server open the ratelimit") + g.ratelimit = ratelimit + } + + return nil +} + +/** + * @brief 启动GRPC API服务器 + */ +func (g *Grpcserver) Run(errCh chan error) { + log.Infof("start grpcserver") + g.exitCh = make(chan struct{}) + g.start = true + defer func() { + close(g.exitCh) + g.start = false + }() + + address := fmt.Sprintf("%v:%v", g.listenIP, g.listenPort) + listener, err := net.Listen("tcp", address) + if err != nil { + log.Errorf("%v", err) + errCh <- err + return + } + defer listener.Close() + + // 如果设置最大连接数 + if g.connLimitConfig != nil && g.connLimitConfig.OpenConnLimit { + log.Infof("grpc server use max connection limit: %d, grpc max limit: %d", + g.connLimitConfig.MaxConnPerHost, g.connLimitConfig.MaxConnLimit) + listener, err = connlimit.NewListener(listener, g.GetProtocol(), g.connLimitConfig) + if err != nil { + log.Errorf("conn limit init err: %s", err.Error()) + errCh <- err + return + } + + } + + server := grpc.NewServer( + grpc.UnaryInterceptor(g.unaryInterceptor), + grpc.StreamInterceptor(g.streamInterceptor), + ) + + for name, config := range g.openAPI { + switch name { + case "client": + if config.Enable { + api.RegisterPolarisGRPCServer(server, g) + openMethod, getErr := apiserver.GetClientOpenMethod(config.Include, g.GetProtocol()) + if getErr != nil { + errCh <- getErr + return + } + g.openMethod = openMethod + } + default: + log.Errorf("api %s does not exist in grpcserver", name) + errCh <- fmt.Errorf("api %s does not exist in grpcserver", name) + return + } + } + g.server = server + + // 引入功能模块和插件 + g.namingServer, err = naming.GetServer() + if err != nil { + log.Errorf("%v", err) + errCh <- err + return + } + g.statis = plugin.GetStatis() + + if err := server.Serve(listener); err != nil { + log.Errorf("%v", err) + errCh <- err + return + } + + log.Infof("grpcserver stop") +} + +// 关闭GRPC +func (g *Grpcserver) Stop() { + connlimit.RemoteLimitListener(g.GetProtocol()) + if g.server != nil { + g.server.Stop() + } +} + +// restart +func (g *Grpcserver) Restart(option map[string]interface{}, api map[string]apiserver.APIConfig, + errCh chan error) error { + log.Infof("restart grpc server with new config: %+v", option) + + g.restart = true + g.Stop() + if g.start { + <-g.exitCh + } + + log.Infof("old grpc server has stopped, begin restarting it") + if err := g.Initialize(context.Background(), option, api); err != nil { + log.Errorf("restart grpc server err: %s", err.Error()) + return err + } + + log.Infof("init grpc server successfully, restart it") + g.restart = false + go g.Run(errCh) + + return nil +} + +/** + * @brief 虚拟Stream + * @note 继承ServerStream + */ +type VirtualStream struct { + Method string + ClientAddress string + ClientIP string + UserAgent string + RequestID string + + grpc.ServerStream + Code int + + preprocess PreProcessFunc + postprocess PostProcessFunc + + StartTime time.Time +} + +/** + * @brief VirtualStream接收消息函数 + * @note 拦截ServerSteam接收消息函数 + */ +func (v *VirtualStream) RecvMsg(m interface{}) error { + err := v.ServerStream.RecvMsg(m) + if err == io.EOF { + return err + } + + if err == nil { + err = v.preprocess(v, false) + } else { + v.Code = -1 + } + + return err +} + +/** + * @brief VirtualStream发送消息函数 + * @note 拦截ServerSteam发送消息函数 + */ +func (v *VirtualStream) SendMsg(m interface{}) error { + v.postprocess(v, m) + + err := v.ServerStream.SendMsg(m) + if err != nil { + v.Code = -2 + } + + return err +} + +/** + * @brief 创建VirtualStream + */ +func newVirtualStream(ctx context.Context, method string, stream grpc.ServerStream, + preprocess PreProcessFunc, postprocess PostProcessFunc) *VirtualStream { + var clientAddress string + var clientIP string + var userAgent string + var requestID string + + peer, exist := peer.FromContext(ctx) + if exist { + clientAddress = peer.Addr.String() + // 解析获取clientIP + items := strings.Split(clientAddress, ":") + if len(items) == 2 { + clientIP = items[0] + } + } + + meta, exist := metadata.FromIncomingContext(ctx) + if exist { + agents := meta["user-agent"] + if len(agents) > 0 { + userAgent = agents[0] + } + + ids := meta["request-id"] + if len(ids) > 0 { + requestID = ids[0] + } + } + + return &VirtualStream{ + Method: method, + ClientAddress: clientAddress, + ClientIP: clientIP, + UserAgent: userAgent, + RequestID: requestID, + ServerStream: stream, + Code: 0, + preprocess: preprocess, + postprocess: postprocess, + } +} + +/** + * @brief 在接收和回复请求时统一处理 + */ +func (g *Grpcserver) unaryInterceptor(ctx context.Context, req interface{}, + info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (rsp interface{}, err error) { + + stream := newVirtualStream(ctx, info.FullMethod, nil, g.preprocess, g.postprocess) + + func() { + if err := g.preprocess(stream, true); err != nil { + return + } + + // 判断是否允许访问 + if ok := g.allowAccess(stream.Method); !ok { + rsp = api.NewResponse(api.ClientAPINotOpen) + return + } + + // handler执行前,限流 + if code := g.enterRatelimit(stream.ClientIP, stream.Method); code != api.ExecuteSuccess { + rsp = api.NewResponse(code) + return + } + rsp, err = handler(ctx, req) + }() + + g.postprocess(stream, rsp) + + return +} + +/** + * @brief 在接收和回复请求时统一处理 + */ +func (g *Grpcserver) streamInterceptor(srv interface{}, ss grpc.ServerStream, + info *grpc.StreamServerInfo, handler grpc.StreamHandler) (err error) { + + stream := newVirtualStream(ss.Context(), info.FullMethod, ss, g.preprocess, g.postprocess) + + err = handler(srv, stream) + if err != nil { // 存在非EOF读错误或者写错误 + log.Error(err.Error(), + zap.String("client-address", stream.ClientAddress), + zap.String("user-agent", stream.UserAgent), + zap.String("request-id", stream.RequestID), + zap.String("method", stream.Method), + ) + + g.statis.AddAPICall(stream.Method, stream.Code, 0) + } + + return +} + +// 请求预处理函数定义 +type PreProcessFunc func(stream *VirtualStream, isPrint bool) error + +/** + * @brief 请求预处理 + */ +func (g *Grpcserver) preprocess(stream *VirtualStream, isPrint bool) error { + // 设置开始时间 + stream.StartTime = time.Now() + + if isPrint { + // 打印请求 + log.Info("receive request", + zap.String("client-address", stream.ClientAddress), + zap.String("user-agent", stream.UserAgent), + zap.String("request-id", stream.RequestID), + zap.String("method", stream.Method), + ) + } + + return nil +} + +// 请求后处理函数定义 +type PostProcessFunc func(stream *VirtualStream, m interface{}) + +/** + * @brief 请求后处理 + */ +func (g *Grpcserver) postprocess(stream *VirtualStream, m interface{}) { + response := m.(api.ResponseMessage) + code := api.CalcCode(response) + + // 打印回复 + if code != http.StatusOK { + log.Error(response.String(), + zap.String("client-address", stream.ClientAddress), + zap.String("user-agent", stream.UserAgent), + zap.String("request-id", stream.RequestID), + zap.String("method", stream.Method), + ) + } + + // 接口调用统计 + now := time.Now() + diff := now.Sub(stream.StartTime) + + // 打印耗时超过1s的请求 + if diff > time.Second { + log.Info("handling time > 1s", + zap.String("client-address", stream.ClientAddress), + zap.String("user-agent", stream.UserAgent), + zap.String("request-id", stream.RequestID), + zap.String("method", stream.Method), + zap.Duration("handling-time", diff), + ) + } + + _ = g.statis.AddAPICall(stream.Method, int(response.GetCode().GetValue()), diff.Nanoseconds()) +} + +// 限流 +func (g *Grpcserver) enterRatelimit(ip string, method string) uint32 { + if g.ratelimit == nil { + return api.ExecuteSuccess + } + + //ipRatelimit + if ok := g.ratelimit.Allow(plugin.IPRatelimit, ip); !ok { + log.Error("[grpc] ip ratelimit is not allow", zap.String("client-ip", ip), + zap.String("method", method)) + return api.IPRateLimit + } + // apiRatelimit + if ok := g.ratelimit.Allow(plugin.APIRatelimit, method); !ok { + log.Error("[grpc] api rate limit is not allow", zap.String("client-ip", ip), + zap.String("method", method)) + return api.APIRateLimit + } + + return api.ExecuteSuccess +} + +// 限制访问 +func (g *Grpcserver) allowAccess(method string) bool { + _, ok := g.openMethod[method] + return ok +} diff --git a/apiserver/httpserver/admin.go b/apiserver/httpserver/admin.go new file mode 100644 index 000000000..5a8ea01d3 --- /dev/null +++ b/apiserver/httpserver/admin.go @@ -0,0 +1,41 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package httpserver + +import ( + "github.com/emicklei/go-restful" + "io" +) + +/** + * @brief 注册接口 + */ +func (h *Httpserver) GetAdminServer() *restful.WebService { + ws := new(restful.WebService) + + ws.Route(ws.GET("/").To(h.index)) + + return ws +} + +/** + * @brief URL: "/" + */ +func (h *Httpserver) index(req *restful.Request, rsp *restful.Response) { + io.WriteString(rsp, "Polaris Server") +} diff --git a/apiserver/httpserver/client_access.go b/apiserver/httpserver/client_access.go new file mode 100644 index 000000000..4a06bd8b4 --- /dev/null +++ b/apiserver/httpserver/client_access.go @@ -0,0 +1,198 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package httpserver + +import ( + "fmt" + + "github.com/polarismesh/polaris-server/apiserver" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/log" + "github.com/emicklei/go-restful" + "go.uber.org/zap" +) + +/** + * @brief 注册客户端接口 + */ +func (h *Httpserver) GetClientAccessServer(include []string) (*restful.WebService, error) { + clientAccess := []string{apiserver.DiscoverAccess, apiserver.RegisterAccess, apiserver.HealthcheckAccess} + + ws := new(restful.WebService) + + ws.Path("/v1").Consumes(restful.MIME_JSON).Produces(restful.MIME_JSON) + + // 如果为空,则开启全部接口 + if len(include) == 0 { + include = clientAccess + } + + // 客户端接口:增删改请求操作存储层,查请求访问缓存 + for _, item := range include { + switch item { + case apiserver.DiscoverAccess: + h.addDiscoverAccess(ws) + case apiserver.RegisterAccess: + h.addRegisterAccess(ws) + case apiserver.HealthcheckAccess: + h.addHealthCheckAccess(ws) + default: + log.Errorf("method %s does not exist in httpserver client access", item) + return nil, fmt.Errorf("method %s does not exist in httpserver client access", item) + } + } + + return ws, nil +} + +/** + * @brief 增加服务发现接口 + */ +func (h *Httpserver) addDiscoverAccess(ws *restful.WebService) { + ws.Route(ws.POST("/ReportClient").To(h.ReportClient)) + ws.Route(ws.POST("/Discover").To(h.Discover)) +} + +/** + * @brief 增加注册/反注册接口 + */ +func (h *Httpserver) addRegisterAccess(ws *restful.WebService) { + ws.Route(ws.POST("/RegisterInstance").To(h.RegisterInstance)) + ws.Route(ws.POST("/DeregisterInstance").To(h.DeregisterInstance)) +} + +/** + * @brief 增加健康检查接口 + */ +func (h *Httpserver) addHealthCheckAccess(ws *restful.WebService) { + ws.Route(ws.POST("/Heartbeat").To(h.Heartbeat)) +} + +/** + * @brief 客户端上报信息 + */ +func (h *Httpserver) ReportClient(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + client := &api.Client{} + context, err := handler.Parse(client) + if context == nil { + ret := api.NewResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.ReportClient(context, client) + handler.WriteHeaderAndProto(ret) +} + +/** + * @brief 注册服务实例 + */ +func (h *Httpserver) RegisterInstance(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + instance := &api.Instance{} + context, err := handler.Parse(instance) + if context == nil { + ret := api.NewResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.CreateInstance(context, instance) + handler.WriteHeaderAndProto(ret) +} + +/** + * @brief 反注册服务实例 + */ +func (h *Httpserver) DeregisterInstance(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + instance := &api.Instance{} + context, err := handler.Parse(instance) + if context == nil { + ret := api.NewResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.DeleteInstance(context, instance) + handler.WriteHeaderAndProto(ret) +} + +/** + * @brief 统一发现接口 + */ +func (h *Httpserver) Discover(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + discoverRequest := &api.DiscoverRequest{} + context, err := handler.Parse(discoverRequest) + if context == nil { + ret := api.NewResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + var msg string + msg = fmt.Sprintf("receive http discover request: %s", discoverRequest.Service.String()) + log.Info(msg, + zap.String("type", api.DiscoverRequest_DiscoverRequestType_name[int32(discoverRequest.Type)]), + zap.String("client-address", req.Request.RemoteAddr), + zap.String("user-agent", req.HeaderParameter("User-Agent")), + zap.String("request-id", req.HeaderParameter("Request-Id")), + ) + + var ret *api.DiscoverResponse + switch discoverRequest.Type { + case api.DiscoverRequest_INSTANCE: + ret = h.namingServer.ServiceInstancesCache(context, discoverRequest.Service) + case api.DiscoverRequest_ROUTING: + ret = h.namingServer.GetRoutingConfigWithCache(context, discoverRequest.Service) + case api.DiscoverRequest_RATE_LIMIT: + ret = h.namingServer.GetRateLimitWithCache(context, discoverRequest.Service) + case api.DiscoverRequest_CIRCUIT_BREAKER: + ret = h.namingServer.GetCircuitBreakerWithCache(context, discoverRequest.Service) + case api.DiscoverRequest_SERVICES: + ret = h.namingServer.GetServiceWithCache(context, discoverRequest.Service) + default: + ret = api.NewDiscoverRoutingResponse(api.InvalidDiscoverResource, discoverRequest.Service) + } + + handler.WriteHeaderAndProto(ret) +} + +/** + * @brief 服务实例心跳 + */ +func (h *Httpserver) Heartbeat(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + instance := &api.Instance{} + context, err := handler.Parse(instance) + if context == nil { + ret := api.NewResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.Heartbeat(context, instance) + handler.WriteHeaderAndProto(ret) +} diff --git a/apiserver/httpserver/console_access.go b/apiserver/httpserver/console_access.go new file mode 100644 index 000000000..71d25e644 --- /dev/null +++ b/apiserver/httpserver/console_access.go @@ -0,0 +1,1065 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package httpserver + +import ( + "context" + "fmt" + "github.com/polarismesh/polaris-server/common/log" + "net/http" + + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/utils" + "github.com/emicklei/go-restful" +) + +const ( + defaultReadAccess string = "default-read" + defaultAccess string = "default" +) + +/** + * @brief 注册管理端接口 + */ +func (h *Httpserver) GetConsoleAccessServer(include []string) (*restful.WebService, error) { + consoleAccess := []string{defaultAccess} + + ws := new(restful.WebService) + + ws.Path("/naming/v1").Consumes(restful.MIME_JSON).Produces(restful.MIME_JSON) + + // 如果为空,则开启全部接口 + if len(include) == 0 { + include = consoleAccess + } + + var hasDefault = false + for _, item := range include { + if item == defaultAccess { + hasDefault = true + break + } + } + for _, item := range include { + switch item { + case defaultReadAccess: + if !hasDefault { + h.addDefaultReadAccess(ws) + } + case defaultAccess: + h.addDefaultAccess(ws) + default: + log.Errorf("method %s does not exist in httpserver console access", item) + return nil, fmt.Errorf("method %s does not exist in httpserver console access", item) + } + } + return ws, nil +} + +/** + * @brief 增加默认读接口 + */ +func (h *Httpserver) addDefaultReadAccess(ws *restful.WebService) { + // 管理端接口:只包含读接口 + ws.Route(ws.GET("/namespaces").To(h.GetNamespaces)) + ws.Route(ws.GET("/namespace/token").To(h.GetNamespaceToken)) + + ws.Route(ws.GET("/services").To(h.GetServices)) + ws.Route(ws.GET("/services/count").To(h.GetServicesCount)) + ws.Route(ws.GET("/service/token").To(h.GetServiceToken)) + ws.Route(ws.POST("/service/alias").To(h.CreateServiceAlias)) + ws.Route(ws.GET("/service/aliases").To(h.GetServiceAliases)) + ws.Route(ws.GET("/service/circuitbreaker").To(h.GetCircuitBreakerByService)) + ws.Route(ws.POST("/service/owner").To(h.GetServiceOwner)) + + ws.Route(ws.GET("/instances").To(h.GetInstances)) + ws.Route(ws.GET("/instances/count").To(h.GetInstancesCount)) + + ws.Route(ws.POST("/routings").To(h.CreateRoutings)) + ws.Route(ws.GET("/routings").To(h.GetRoutings)) + + ws.Route(ws.POST("/ratelimits").To(h.CreateRateLimits)) + ws.Route(ws.GET("/ratelimits").To(h.GetRateLimits)) + + ws.Route(ws.GET("/circuitbreaker").To(h.GetCircuitBreaker)) + ws.Route(ws.GET("/circuitbreaker/versions").To(h.GetCircuitBreakerVersions)) + ws.Route(ws.GET("/circuitbreakers/master").To(h.GetMasterCircuitBreakers)) + ws.Route(ws.GET("/circuitbreakers/release").To(h.GetReleaseCircuitBreakers)) + ws.Route(ws.GET("/circuitbreaker/token").To(h.GetCircuitBreakerToken)) + + ws.Route(ws.GET("/platforms").To(h.GetPlatforms)) + ws.Route(ws.GET("/platform/token").To(h.GetPlatformToken)) +} + +/** + * @brief 增加默认接口 + */ +func (h *Httpserver) addDefaultAccess(ws *restful.WebService) { + // 管理端接口:增删改查请求全部操作存储层 + ws.Route(ws.POST("/namespaces").To(h.CreateNamespaces)) + ws.Route(ws.POST("/namespaces/delete").To(h.DeleteNamespaces)) + ws.Route(ws.PUT("/namespaces").To(h.UpdateNamespaces)) + ws.Route(ws.GET("/namespaces").To(h.GetNamespaces)) + ws.Route(ws.GET("/namespace/token").To(h.GetNamespaceToken)) + ws.Route(ws.PUT("/namespace/token").To(h.UpdateNamespaceToken)) + + ws.Route(ws.POST("/services").To(h.CreateServices)) + ws.Route(ws.POST("/services/delete").To(h.DeleteServices)) + ws.Route(ws.PUT("/services").To(h.UpdateServices)) + ws.Route(ws.GET("/services").To(h.GetServices)) + ws.Route(ws.GET("/services/count").To(h.GetServicesCount)) + ws.Route(ws.GET("/service/token").To(h.GetServiceToken)) + ws.Route(ws.PUT("/service/token").To(h.UpdateServiceToken)) + ws.Route(ws.POST("/service/alias").To(h.CreateServiceAlias)) + ws.Route(ws.POST("/service/alias/no-auth").To(h.CreateServiceAliasNoAuth)) + ws.Route(ws.PUT("/service/alias").To(h.UpdateServiceAlias)) + ws.Route(ws.GET("/service/aliases").To(h.GetServiceAliases)) + ws.Route(ws.GET("/service/circuitbreaker").To(h.GetCircuitBreakerByService)) + ws.Route(ws.POST("/service/owner").To(h.GetServiceOwner)) + + ws.Route(ws.POST("/instances").To(h.CreateInstances)) + ws.Route(ws.POST("/instances/delete").To(h.DeleteInstances)) + ws.Route(ws.POST("/instances/delete/host").To(h.DeleteInstancesByHost)) + ws.Route(ws.PUT("/instances").To(h.UpdateInstances)) + ws.Route(ws.PUT("/instances/isolate/host").To(h.UpdateInstancesIsolate)) + ws.Route(ws.GET("/instances").To(h.GetInstances)) + ws.Route(ws.GET("/instances/count").To(h.GetInstancesCount)) + + ws.Route(ws.POST("/routings").To(h.CreateRoutings)) + ws.Route(ws.POST("/routings/delete").To(h.DeleteRoutings)) + ws.Route(ws.PUT("/routings").To(h.UpdateRoutings)) + ws.Route(ws.GET("/routings").To(h.GetRoutings)) + + ws.Route(ws.POST("/ratelimits").To(h.CreateRateLimits)) + ws.Route(ws.POST("/ratelimits/delete").To(h.DeleteRateLimits)) + ws.Route(ws.PUT("/ratelimits").To(h.UpdateRateLimits)) + ws.Route(ws.GET("/ratelimits").To(h.GetRateLimits)) + + ws.Route(ws.POST("/circuitbreakers").To(h.CreateCircuitBreakers)) + ws.Route(ws.POST("/circuitbreakers/version").To(h.CreateCircuitBreakerVersions)) + ws.Route(ws.POST("/circuitbreakers/delete").To(h.DeleteCircuitBreakers)) + ws.Route(ws.PUT("/circuitbreakers").To(h.UpdateCircuitBreakers)) + ws.Route(ws.POST("/circuitbreakers/release").To(h.ReleaseCircuitBreakers)) + ws.Route(ws.POST("/circuitbreakers/unbind").To(h.UnBindCircuitBreakers)) + ws.Route(ws.GET("/circuitbreaker").To(h.GetCircuitBreaker)) + ws.Route(ws.GET("/circuitbreaker/versions").To(h.GetCircuitBreakerVersions)) + ws.Route(ws.GET("/circuitbreakers/master").To(h.GetMasterCircuitBreakers)) + ws.Route(ws.GET("/circuitbreakers/release").To(h.GetReleaseCircuitBreakers)) + ws.Route(ws.GET("/circuitbreaker/token").To(h.GetCircuitBreakerToken)) + + ws.Route(ws.POST("/platforms").To(h.CreatePlatforms)) + ws.Route(ws.POST("/platforms/delete").To(h.DeletePlatforms)) + ws.Route(ws.PUT("/platforms").To(h.UpdatePlatforms)) + ws.Route(ws.GET("/platforms").To(h.GetPlatforms)) + ws.Route(ws.GET("/platform/token").To(h.GetPlatformToken)) + +} + +/** + * @brief 创建命名空间 + */ +func (h *Httpserver) CreateNamespaces(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var namespaces NamespaceArr + ctx, err := handler.Parse(&namespaces) + if ctx == nil { + ret := api.NewBatchWriteResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.CreateNamespaces(ctx, namespaces) + handler.WriteHeaderAndProto(ret) +} + +/** + * @brief 删除命名空间 + */ +func (h *Httpserver) DeleteNamespaces(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var namespaces NamespaceArr + ctx, err := handler.Parse(&namespaces) + if ctx == nil { + ret := api.NewBatchWriteResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.DeleteNamespaces(ctx, namespaces) + if code := api.CalcCode(ret); code != http.StatusOK { + handler.WriteHeaderAndProto(ret) + return + } + + handler.WriteHeader(ret.GetCode().GetValue(), http.StatusOK) +} + +/** + * @brief 修改命名空间 + */ +func (h *Httpserver) UpdateNamespaces(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var namespaces NamespaceArr + ctx, err := handler.Parse(&namespaces) + if ctx == nil { + ret := api.NewBatchWriteResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.UpdateNamespaces(ctx, namespaces) + if code := api.CalcCode(ret); code != http.StatusOK { + handler.WriteHeaderAndProto(ret) + return + } + + handler.WriteHeader(ret.GetCode().GetValue(), http.StatusOK) +} + +/** + * @brief 查询命名空间 + */ +func (h *Httpserver) GetNamespaces(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + ret := h.namingServer.GetNamespaces(req.Request.URL.Query()) + handler.WriteHeaderAndProto(ret) +} + +// 命名空间token的获取 +func (h *Httpserver) GetNamespaceToken(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + token := req.HeaderParameter("Polaris-Token") + ctx := context.WithValue(context.Background(), utils.StringContext("polaris-token"), token) + + queryParams := parseQueryParams(req) + namespace := &api.Namespace{ + Name: utils.NewStringValue(queryParams["name"]), + Token: utils.NewStringValue(queryParams["token"]), + } + + ret := h.namingServer.GetNamespaceToken(ctx, namespace) + handler.WriteHeaderAndProto(ret) +} + +// 更新命名空间的token +func (h *Httpserver) UpdateNamespaceToken(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var namespace api.Namespace + ctx, err := handler.Parse(&namespace) + if ctx == nil { + ret := api.NewResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.UpdateNamespaceToken(ctx, &namespace) + handler.WriteHeaderAndProto(ret) +} + +/** + * @brief 创建服务 + */ +func (h *Httpserver) CreateServices(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var services ServiceArr + ctx, err := handler.Parse(&services) + if ctx == nil { + ret := api.NewBatchWriteResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.CreateServices(ctx, services) + handler.WriteHeaderAndProto(ret) +} + +/** + * @brief 删除服务 + */ +func (h *Httpserver) DeleteServices(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var services ServiceArr + ctx, err := handler.Parse(&services) + if ctx == nil { + ret := api.NewBatchWriteResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.DeleteServices(ctx, services) + if code := api.CalcCode(ret); code != http.StatusOK { + handler.WriteHeaderAndProto(ret) + return + } + + handler.WriteHeader(ret.GetCode().GetValue(), http.StatusOK) +} + +/** + * @brief 修改服务 + */ +func (h *Httpserver) UpdateServices(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var services ServiceArr + ctx, err := handler.Parse(&services) + if ctx == nil { + ret := api.NewBatchWriteResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.UpdateServices(ctx, services) + if code := api.CalcCode(ret); code != http.StatusOK { + handler.WriteHeaderAndProto(ret) + return + } + handler.WriteHeader(ret.GetCode().GetValue(), http.StatusOK) +} + +/** + * @brief 查询服务 + */ +func (h *Httpserver) GetServices(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + queryParams := parseQueryParams(req) + ret := h.namingServer.GetServices(queryParams) + handler.WriteHeaderAndProto(ret) +} + +/** + * @brief 查询服务总数 + */ +func (h *Httpserver) GetServicesCount(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + ret := h.namingServer.GetServicesCount() + handler.WriteHeaderAndProto(ret) +} + +// 获取服务token +func (h *Httpserver) GetServiceToken(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + token := req.HeaderParameter("Polaris-Token") + ctx := context.WithValue(context.Background(), utils.StringContext("polaris-token"), token) + + queryParams := parseQueryParams(req) + service := &api.Service{ + Name: utils.NewStringValue(queryParams["name"]), + Namespace: utils.NewStringValue(queryParams["namespace"]), + Token: utils.NewStringValue(queryParams["token"]), + } + + ret := h.namingServer.GetServiceToken(ctx, service) + handler.WriteHeaderAndProto(ret) +} + +// 更新服务token +func (h *Httpserver) UpdateServiceToken(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var service api.Service + ctx, err := handler.Parse(&service) + if ctx == nil { + ret := api.NewResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.UpdateServiceToken(ctx, &service) + handler.WriteHeaderAndProto(ret) +} + +// service alias +func (h *Httpserver) CreateServiceAlias(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var alias api.ServiceAlias + ctx, err := handler.Parse(&alias) + if ctx == nil { + ret := api.NewResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + out := h.namingServer.CreateServiceAlias(ctx, &alias) + handler.WriteHeaderAndProto(out) + return +} + +/** + * @brief 创建服务别名 + * @note 不需要鉴权 + */ +func (h *Httpserver) CreateServiceAliasNoAuth(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var alias api.ServiceAlias + ctx, err := handler.Parse(&alias) + if ctx == nil { + ret := api.NewResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + out := h.namingServer.CreateServiceAliasNoAuth(ctx, &alias) + handler.WriteHeaderAndProto(out) + return +} + +// 修改服务别名 +func (h *Httpserver) UpdateServiceAlias(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var alias api.ServiceAlias + ctx, err := handler.Parse(&alias) + if ctx == nil { + ret := api.NewResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.UpdateServiceAlias(ctx, &alias) + if code := api.CalcCode(ret); code != http.StatusOK { + handler.WriteHeaderAndProto(ret) + return + } + handler.WriteHeader(ret.GetCode().GetValue(), http.StatusOK) + return +} + +// 根据源服务获取服务别名 +func (h *Httpserver) GetServiceAliases(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + queryParams := parseQueryParams(req) + ret := h.namingServer.GetServiceAliases(queryParams) + handler.WriteHeaderAndProto(ret) +} + +/** + * @brief 创建服务实例 + */ +func (h *Httpserver) CreateInstances(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var instances InstanceArr + ctx, err := handler.Parse(&instances) + if ctx == nil { + ret := api.NewBatchWriteResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.CreateInstances(ctx, instances) + handler.WriteHeaderAndProto(ret) +} + +/** + * @brief 删除服务实例 + */ +func (h *Httpserver) DeleteInstances(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var instances InstanceArr + ctx, err := handler.Parse(&instances) + if ctx == nil { + ret := api.NewBatchWriteResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.DeleteInstances(ctx, instances) + if code := api.CalcCode(ret); code != http.StatusOK { + handler.WriteHeaderAndProto(ret) + return + } + + handler.WriteHeader(ret.GetCode().GetValue(), http.StatusOK) +} + +/** + * @brief 根据host删除服务实例 + */ +func (h *Httpserver) DeleteInstancesByHost(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var instances InstanceArr + ctx, err := handler.Parse(&instances) + if ctx == nil { + ret := api.NewBatchWriteResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.DeleteInstancesByHost(ctx, instances) + if code := api.CalcCode(ret); code != http.StatusOK { + handler.WriteHeaderAndProto(ret) + return + } + + handler.WriteHeader(ret.GetCode().GetValue(), http.StatusOK) +} + +/** + * @brief 修改服务实例 + */ +func (h *Httpserver) UpdateInstances(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var instances InstanceArr + ctx, err := handler.Parse(&instances) + if ctx == nil { + ret := api.NewBatchWriteResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.UpdateInstances(ctx, instances) + if code := api.CalcCode(ret); code != http.StatusOK { + handler.WriteHeaderAndProto(ret) + return + } + + handler.WriteHeader(ret.GetCode().GetValue(), http.StatusOK) +} + +/** + * @brief 修改服务实例的隔离状态 + */ +func (h *Httpserver) UpdateInstancesIsolate(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var instances InstanceArr + ctx, err := handler.Parse(&instances) + if ctx == nil { + ret := api.NewBatchWriteResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.UpdateInstancesIsolate(ctx, instances) + if code := api.CalcCode(ret); code != http.StatusOK { + handler.WriteHeaderAndProto(ret) + return + } + + handler.WriteHeader(ret.GetCode().GetValue(), http.StatusOK) +} + +/** + * @brief 查询服务实例 + */ +func (h *Httpserver) GetInstances(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + queryParams := parseQueryParams(req) + ret := h.namingServer.GetInstances(queryParams) + handler.WriteHeaderAndProto(ret) +} + +/** + * @brief 查询服务实例 + */ +func (h *Httpserver) GetInstancesCount(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + ret := h.namingServer.GetInstancesCount() + handler.WriteHeaderAndProto(ret) +} + +/** + * @brief 创建规则路由 + */ +func (h *Httpserver) CreateRoutings(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var routings RoutingArr + ctx, err := handler.Parse(&routings) + if ctx == nil { + ret := api.NewBatchWriteResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.CreateRoutingConfigs(ctx, routings) + handler.WriteHeaderAndProto(ret) +} + +/** + * @brief 删除规则路由 + */ +func (h *Httpserver) DeleteRoutings(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var routings RoutingArr + ctx, err := handler.Parse(&routings) + if ctx == nil { + ret := api.NewBatchWriteResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.DeleteRoutingConfigs(ctx, routings) + if code := api.CalcCode(ret); code != http.StatusOK { + handler.WriteHeaderAndProto(ret) + return + } + + handler.WriteHeader(ret.GetCode().GetValue(), http.StatusOK) +} + +/** + * @brief 修改规则路由 + */ +func (h *Httpserver) UpdateRoutings(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var routings RoutingArr + ctx, err := handler.Parse(&routings) + if ctx == nil { + ret := api.NewBatchWriteResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.UpdateRoutingConfigs(ctx, routings) + if code := api.CalcCode(ret); code != http.StatusOK { + handler.WriteHeaderAndProto(ret) + return + } + + handler.WriteHeader(ret.GetCode().GetValue(), http.StatusOK) +} + +/** + * @brief 查询规则路由 + */ +func (h *Httpserver) GetRoutings(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + queryParams := parseQueryParams(req) + ret := h.namingServer.GetRoutingConfigs(nil, queryParams) + handler.WriteHeaderAndProto(ret) +} + +/** + * @brief 创建限流规则 + */ +func (h *Httpserver) CreateRateLimits(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var rateLimits RateLimitArr + ctx, err := handler.Parse(&rateLimits) + if ctx == nil { + ret := api.NewBatchWriteResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.CreateRateLimits(ctx, rateLimits) + handler.WriteHeaderAndProto(ret) +} + +/** + * @brief 删除限流规则 + */ +func (h *Httpserver) DeleteRateLimits(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var rateLimits RateLimitArr + ctx, err := handler.Parse(&rateLimits) + if ctx == nil { + ret := api.NewBatchWriteResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.DeleteRateLimits(ctx, rateLimits) + if code := api.CalcCode(ret); code != http.StatusOK { + handler.WriteHeaderAndProto(ret) + return + } + handler.WriteHeader(ret.GetCode().GetValue(), http.StatusOK) +} + +/** + * @brief 修改限流规则 + */ +func (h *Httpserver) UpdateRateLimits(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var rateLimits RateLimitArr + ctx, err := handler.Parse(&rateLimits) + if ctx == nil { + ret := api.NewBatchWriteResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.UpdateRateLimits(ctx, rateLimits) + if code := api.CalcCode(ret); code != http.StatusOK { + handler.WriteHeaderAndProto(ret) + return + } + + handler.WriteHeader(ret.GetCode().GetValue(), http.StatusOK) +} + +/** + * @brief 查询限流规则 + */ +func (h *Httpserver) GetRateLimits(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + queryParams := parseQueryParams(req) + ret := h.namingServer.GetRateLimits(queryParams) + handler.WriteHeaderAndProto(ret) +} + +/** + * @brief 创建熔断规则 + */ +func (h *Httpserver) CreateCircuitBreakers(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var circuitBreakers CircuitBreakerArr + ctx, err := handler.Parse(&circuitBreakers) + if ctx == nil { + ret := api.NewBatchWriteResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.CreateCircuitBreakers(ctx, circuitBreakers) + handler.WriteHeaderAndProto(ret) +} + +/** + * @brief 创建熔断规则版本 + */ +func (h *Httpserver) CreateCircuitBreakerVersions(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var circuitBreakers CircuitBreakerArr + ctx, err := handler.Parse(&circuitBreakers) + if ctx == nil { + ret := api.NewBatchQueryResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.CreateCircuitBreakerVersions(ctx, circuitBreakers) + handler.WriteHeaderAndProto(ret) +} + +/** + * @brief 删除熔断规则 + */ +func (h *Httpserver) DeleteCircuitBreakers(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var circuitBreakers CircuitBreakerArr + ctx, err := handler.Parse(&circuitBreakers) + if ctx == nil { + ret := api.NewBatchWriteResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.DeleteCircuitBreakers(ctx, circuitBreakers) + if code := api.CalcCode(ret); code != http.StatusOK { + handler.WriteHeaderAndProto(ret) + return + } + handler.WriteHeader(ret.GetCode().GetValue(), http.StatusOK) +} + +/** + * @brief 修改熔断规则 + */ +func (h *Httpserver) UpdateCircuitBreakers(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var circuitBreakers CircuitBreakerArr + ctx, err := handler.Parse(&circuitBreakers) + if ctx == nil { + ret := api.NewBatchWriteResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.UpdateCircuitBreakers(ctx, circuitBreakers) + if code := api.CalcCode(ret); code != http.StatusOK { + handler.WriteHeaderAndProto(ret) + return + } + handler.WriteHeader(ret.GetCode().GetValue(), http.StatusOK) +} + +/** + * @brief 发布熔断规则 + */ +func (h *Httpserver) ReleaseCircuitBreakers(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var configRelease ConfigReleaseArr + ctx, err := handler.Parse(&configRelease) + if ctx == nil { + ret := api.NewBatchWriteResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.ReleaseCircuitBreakers(ctx, configRelease) + if code := api.CalcCode(ret); code != http.StatusOK { + handler.WriteHeaderAndProto(ret) + return + } + handler.WriteHeader(ret.GetCode().GetValue(), http.StatusOK) +} + +/** + * @brief 解绑熔断规则 + */ +func (h *Httpserver) UnBindCircuitBreakers(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var configRelease ConfigReleaseArr + ctx, err := handler.Parse(&configRelease) + if ctx == nil { + ret := api.NewBatchWriteResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.UnBindCircuitBreakers(ctx, configRelease) + if code := api.CalcCode(ret); code != http.StatusOK { + handler.WriteHeaderAndProto(ret) + return + } + handler.WriteHeader(ret.GetCode().GetValue(), http.StatusOK) +} + +/** + * @brief 根据id和version获取熔断规则 + */ +func (h *Httpserver) GetCircuitBreaker(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + queryParams := parseQueryParams(req) + ret := h.namingServer.GetCircuitBreaker(queryParams) + handler.WriteHeaderAndProto(ret) +} + +/** + * @brief 查询熔断规则的所有版本 + */ +func (h *Httpserver) GetCircuitBreakerVersions(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + queryParams := parseQueryParams(req) + ret := h.namingServer.GetCircuitBreakerVersions(queryParams) + handler.WriteHeaderAndProto(ret) +} + +/** + * @brief 查询master熔断规则 + */ +func (h *Httpserver) GetMasterCircuitBreakers(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + queryParams := parseQueryParams(req) + ret := h.namingServer.GetMasterCircuitBreakers(queryParams) + handler.WriteHeaderAndProto(ret) +} + +/** + * @brief 根据规则id查询已发布的熔断规则 + */ +func (h *Httpserver) GetReleaseCircuitBreakers(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + queryParams := parseQueryParams(req) + ret := h.namingServer.GetReleaseCircuitBreakers(queryParams) + handler.WriteHeaderAndProto(ret) +} + +/** + * @brief 根据服务查询绑定熔断规则 + */ +func (h *Httpserver) GetCircuitBreakerByService(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + queryParams := parseQueryParams(req) + ret := h.namingServer.GetCircuitBreakerByService(queryParams) + handler.WriteHeaderAndProto(ret) +} + +/** + * @brief 根据服务获取服务负责人 + */ +func (h *Httpserver) GetServiceOwner(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var services ServiceArr + ctx, err := handler.Parse(&services) + if ctx == nil { + ret := api.NewBatchWriteResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.GetServiceOwner(ctx, services) + handler.WriteHeaderAndProto(ret) +} + +/** + * @brief 获取熔断规则token + */ +func (h *Httpserver) GetCircuitBreakerToken(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + token := req.HeaderParameter("Polaris-Token") + ctx := context.WithValue(context.Background(), utils.StringContext("polaris-token"), token) + + queryParams := parseQueryParams(req) + circuitBreaker := &api.CircuitBreaker{ + Id: utils.NewStringValue(queryParams["id"]), + Version: utils.NewStringValue("master"), + Token: utils.NewStringValue(queryParams["token"]), + } + ret := h.namingServer.GetCircuitBreakerToken(ctx, circuitBreaker) + handler.WriteHeaderAndProto(ret) +} + +/* + * @brief 创建平台 + */ +func (h *Httpserver) CreatePlatforms(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var platforms PlatformArr + ctx, err := handler.Parse(&platforms) + + if ctx == nil { + ret := api.NewBatchWriteResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.CreatePlatforms(ctx, platforms) + handler.WriteHeaderAndProto(ret) +} + +/** + * @brief 修改平台 + */ +func (h *Httpserver) UpdatePlatforms(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var platforms PlatformArr + ctx, err := handler.Parse(&platforms) + + if ctx == nil { + ret := api.NewBatchWriteResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.UpdatePlatforms(ctx, platforms) + + if code := api.CalcCode(ret); code != http.StatusOK { + handler.WriteHeaderAndProto(ret) + return + } + + handler.WriteHeader(ret.GetCode().GetValue(), http.StatusOK) +} + +/** + * @brief 删除平台 + */ +func (h *Httpserver) DeletePlatforms(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + var platforms PlatformArr + ctx, err := handler.Parse(&platforms) + if ctx == nil { + ret := api.NewBatchQueryResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + ret := h.namingServer.DeletePlatforms(ctx, platforms) + if code := api.CalcCode(ret); code != http.StatusOK { + handler.WriteHeaderAndProto(ret) + return + } + + handler.WriteHeader(ret.GetCode().GetValue(), http.StatusOK) +} + +/** + * @brief 查询平台 + */ +func (h *Httpserver) GetPlatforms(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + queryParams := parseQueryParams(req) + ret := h.namingServer.GetPlatforms(queryParams) + handler.WriteHeaderAndProto(ret) +} + +/** + * @brief 查询平台Token + */ +func (h *Httpserver) GetPlatformToken(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + token := req.HeaderParameter("Polaris-Token") + ctx := context.WithValue(context.Background(), utils.StringContext("polaris-token"), token) + + queryParams := parseQueryParams(req) + platform := &api.Platform{ + Id: utils.NewStringValue(queryParams["id"]), + Token: utils.NewStringValue(queryParams["token"]), + } + + ret := h.namingServer.GetPlatformToken(ctx, platform) + handler.WriteHeaderAndProto(ret) +} + +/** + * @brief 解析并获取HTTP的query params + */ +func parseQueryParams(req *restful.Request) map[string]string { + queryParams := make(map[string]string) + for key, value := range req.Request.URL.Query() { + if len(value) > 0 { + queryParams[key] = value[0] // 暂时默认只支持一个查询 + } + } + + return queryParams +} diff --git a/apiserver/httpserver/default.go b/apiserver/httpserver/default.go new file mode 100644 index 000000000..f6e1fc362 --- /dev/null +++ b/apiserver/httpserver/default.go @@ -0,0 +1,29 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package httpserver + +import ( + "github.com/polarismesh/polaris-server/apiserver" +) + +/** + * @brief 自注册到API服务器插槽 + */ +func init() { + _ = apiserver.Register("httpserver", &Httpserver{}) +} diff --git a/apiserver/httpserver/handler.go b/apiserver/httpserver/handler.go new file mode 100644 index 000000000..cf15956ba --- /dev/null +++ b/apiserver/httpserver/handler.go @@ -0,0 +1,127 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package httpserver + +import ( + "context" + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/utils" + "github.com/emicklei/go-restful" + "github.com/golang/protobuf/jsonpb" + "github.com/golang/protobuf/proto" + "go.uber.org/zap" + "net/http" + "strings" +) + +/** + * @brief HTTP请求/回复处理器 + */ +type Handler struct { + *restful.Request + *restful.Response +} + +/** + * @brief 解析请求 + */ +func (h *Handler) Parse(message proto.Message) (context.Context, error) { + requestID := h.Request.HeaderParameter("Request-Id") + platformID := h.Request.HeaderParameter("Platform-Id") + platformToken := h.Request.HeaderParameter("Platform-Token") + token := h.Request.HeaderParameter("Polaris-Token") + + if err := jsonpb.Unmarshal(h.Request.Request.Body, message); err != nil { + log.Error(err.Error(), zap.String("request-id", requestID)) + return nil, err + } + + ctx := context.Background() + ctx = context.WithValue(ctx, utils.StringContext("request-id"), requestID) + ctx = context.WithValue(ctx, utils.StringContext("platform-id"), platformID) + ctx = context.WithValue(ctx, utils.StringContext("platform-token"), platformToken) + if token != "" { + ctx = context.WithValue(ctx, utils.StringContext("polaris-token"), token) + } + + var operator string + addrSlice := strings.Split(h.Request.Request.RemoteAddr, ":") + if len(addrSlice) == 2 { + operator = "HTTP:" + addrSlice[0] + if platformID != "" { + operator += "(" + platformID + ")" + } + } + if staffName := h.Request.HeaderParameter("Staffname"); staffName != "" { + operator = staffName + } + ctx = context.WithValue(ctx, utils.StringContext("operator"), operator) + + return ctx, nil +} + +/** + * @brief 仅返回Code + */ +func (h *Handler) WriteHeader(polarisCode uint32, httpStatus int) { + requestID := h.Request.HeaderParameter(utils.PolarisRequestID) + h.Request.SetAttribute(utils.PolarisCode, polarisCode) // api统计的时候,用该code + + // 对于非200000的返回,补充实际的code到header中 + if polarisCode != api.ExecuteSuccess { + h.Response.AddHeader(utils.PolarisCode, fmt.Sprintf("%d", polarisCode)) + h.Response.AddHeader(utils.PolarisMessage, api.Code2Info(polarisCode)) + } + h.Response.AddHeader("Request-Id", requestID) + h.Response.WriteHeader(httpStatus) +} + +/** + * @brief 返回Code和Proto + */ +func (h *Handler) WriteHeaderAndProto(obj api.ResponseMessage) { + requestID := h.Request.HeaderParameter(utils.PolarisRequestID) + h.Request.SetAttribute(utils.PolarisCode, obj.GetCode().GetValue()) + status := api.CalcCode(obj) + + if status != http.StatusOK { + log.Error(obj.String(), zap.String("request-id", requestID)) + } + if code := obj.GetCode().GetValue(); code != api.ExecuteSuccess { + h.Response.AddHeader(utils.PolarisCode, fmt.Sprintf("%d", code)) + h.Response.AddHeader(utils.PolarisMessage, api.Code2Info(code)) + } + + h.Response.AddHeader(utils.PolarisRequestID, requestID) + h.Response.WriteHeader(status) + + m := jsonpb.Marshaler{Indent: " "} + err := m.Marshal(h.Response, obj) + if err != nil { + log.Error(err.Error(), zap.String("request-id", requestID)) + } +} + +// http答复简单封装 +func HTTPResponse(req *restful.Request, rsp *restful.Response, code uint32) { + handler := &Handler{req, rsp} + resp := api.NewResponse(code) + handler.WriteHeaderAndProto(resp) +} diff --git a/apiserver/httpserver/maintain_access.go b/apiserver/httpserver/maintain_access.go new file mode 100644 index 000000000..b2e3d6aba --- /dev/null +++ b/apiserver/httpserver/maintain_access.go @@ -0,0 +1,234 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package httpserver + +import ( + "encoding/json" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/connlimit" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/utils" + "github.com/emicklei/go-restful" + "net/http" + "runtime/debug" + "strconv" + "time" +) + +// 运维接口 +func (h *Httpserver) GetMaintainAccessServer() *restful.WebService { + ws := new(restful.WebService) + ws.Path("/maintain/v1").Consumes(restful.MIME_JSON).Produces(restful.MIME_JSON) + + ws.Route(ws.GET("/apiserver/conn").To(h.GetServerConnections)) + ws.Route(ws.GET("/apiserver/conn/stats").To(h.GetServerConnStats)) + ws.Route(ws.POST("apiserver/conn/close").To(h.CloseConnections)) + ws.Route(ws.POST("/memory/free").To(h.FreeOSMemory)) + ws.Route(ws.POST("/instance/clean").Consumes(restful.MIME_JSON).To(h.CleanInstance)) + ws.Route(ws.GET("/instance/heartbeat").To(h.GetLastHeartbeat)) + return ws +} + +// 查看server的连接数 +// query参数:protocol,必须,查看指定协议server +// host,可选,查看指定host +func (h *Httpserver) GetServerConnections(req *restful.Request, rsp *restful.Response) { + params := parseQueryParams(req) + protocol := params["protocol"] + host := params["host"] + if protocol == "" { + _ = rsp.WriteErrorString(http.StatusBadRequest, "missing param protocol") + return + } + + lis := connlimit.GetLimitListener(protocol) + if lis == nil { + _ = rsp.WriteErrorString(http.StatusBadRequest, "not found the protocol") + return + } + + var out struct { + Protocol string + Total int32 + Host map[string]int32 + } + + out.Protocol = protocol + out.Total = lis.GetListenerConnCount() + out.Host = make(map[string]int32) + if host != "" { + out.Host[host] = lis.GetHostConnCount(host) + } else { + lis.Range(func(host string, count int32) bool { + out.Host[host] = count + return true + }) + } + + _ = rsp.WriteEntity(out) +} + +// 获取连接缓存里面的统计信息 +func (h *Httpserver) GetServerConnStats(req *restful.Request, rsp *restful.Response) { + params := parseQueryParams(req) + protocol := params["protocol"] + host := params["host"] + if protocol == "" { + _ = rsp.WriteErrorString(http.StatusBadRequest, "missing param protocol") + return + } + + lis := connlimit.GetLimitListener(protocol) + if lis == nil { + _ = rsp.WriteErrorString(http.StatusBadRequest, "not found the protocol") + return + } + var out struct { + Protocol string + ActiveConnTotal int32 + StatsTotal int + StatsSize int + Stats []*connlimit.HostConnStat + } + out.Protocol = protocol + out.ActiveConnTotal = lis.GetListenerConnCount() + + stats := lis.GetHostConnStats(host) + out.Stats = stats + out.StatsTotal = len(stats) + + // 过滤amount + if amountStr, ok := params["amount"]; ok { + out.Stats = make([]*connlimit.HostConnStat, 0) + amount, _ := strconv.Atoi(amountStr) + for _, stat := range stats { + if stat.Amount >= int32(amount) { + out.Stats = append(out.Stats, stat) + } + } + } + out.StatsSize = len(out.Stats) + + if out.Stats == nil { + out.Stats = make([]*connlimit.HostConnStat, 0) + } + _ = rsp.WriteAsJson(out) +} + +// 关闭指定client ip的连接 +func (h *Httpserver) CloseConnections(req *restful.Request, rsp *restful.Response) { + log.Info("[HTTP] Start doing close connections") + var body []struct { + Protocol string + Host string + Port int // 可以为0,为0意味着关闭host所有的连接 + } + decoder := json.NewDecoder(req.Request.Body) + if err := decoder.Decode(&body); err != nil { + log.Errorf("[HTTP] close connection decode body err: %s", err.Error()) + _ = rsp.WriteError(http.StatusBadRequest, err) + return + } + for _, entry := range body { + if entry.Protocol == "" { + log.Errorf("[HTTP] close connection missing protocol") + _ = rsp.WriteErrorString(http.StatusBadRequest, "missing protocol") + return + } + if entry.Host == "" { + log.Errorf("[HTTP] close connection missing host") + _ = rsp.WriteErrorString(http.StatusBadRequest, "missing host") + return + } + } + + for _, entry := range body { + listener := connlimit.GetLimitListener(entry.Protocol) + if listener == nil { + log.Warnf("[HTTP] not found listener for protocol(%s)", entry.Protocol) + continue + } + if entry.Port != 0 { + if conn := listener.GetHostConnection(entry.Host, entry.Port); conn != nil { + log.Infof("[HTTP] address(%s:%d) to be closed", entry.Host, entry.Port) + _ = conn.Close() + continue + } + } + + log.Infof("[HTTP] host(%s) connections to be closed", entry.Host) + activeConns := listener.GetHostActiveConns(entry.Host) + for _, conn := range activeConns { + if conn != nil { + _ = conn.Close() + } + } + } +} + +// 增加一个释放系统内存的接口 +func (h *Httpserver) FreeOSMemory(req *restful.Request, rsp *restful.Response) { + log.Info("[HTTP] start doing free os memory") + // 防止并发释放 + start := time.Now() + h.freeMemMu.Lock() + debug.FreeOSMemory() + h.freeMemMu.Unlock() + log.Infof("[HTTP] finish doing free os memory, used time: %v", time.Now().Sub(start)) +} + +// 彻底清理flag=1的实例运维接口 +// 支持一个个清理 +func (h *Httpserver) CleanInstance(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + + instance := &api.Instance{} + ctx, err := handler.Parse(instance) + if ctx == nil { + ret := api.NewResponseWithMsg(api.ParseException, err.Error()) + handler.WriteHeaderAndProto(ret) + return + } + + ret := h.namingServer.CleanInstance(ctx, instance) + handler.WriteHeaderAndProto(ret) +} + +// 获取实例,上一次心跳的时间 +func (h *Httpserver) GetLastHeartbeat(req *restful.Request, rsp *restful.Response) { + handler := &Handler{req, rsp} + params := parseQueryParams(req) + instance := &api.Instance{} + if id, ok := params["id"]; ok && id != "" { + instance.Id = utils.NewStringValue(id) + ret := h.namingServer.GetLastHeartbeat(instance) + handler.WriteHeaderAndProto(ret) + return + } + + instance.Service = utils.NewStringValue(params["service"]) + instance.Namespace = utils.NewStringValue(params["namespace"]) + instance.VpcId = utils.NewStringValue(params["vpc_id"]) + instance.Host = utils.NewStringValue(params["host"]) + port, _ := strconv.Atoi(params["port"]) + instance.Port = utils.NewUInt32Value(uint32(port)) + + ret := h.namingServer.GetLastHeartbeat(instance) + handler.WriteHeaderAndProto(ret) + return +} diff --git a/apiserver/httpserver/proto.go b/apiserver/httpserver/proto.go new file mode 100644 index 000000000..14c1a6524 --- /dev/null +++ b/apiserver/httpserver/proto.go @@ -0,0 +1,135 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package httpserver + +import ( + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/golang/protobuf/proto" +) + +/** + * @brief 命名空间数组定义 + */ +type NamespaceArr []*api.Namespace + +// +func (m *NamespaceArr) Reset() { *m = NamespaceArr{} } + +// +func (m *NamespaceArr) String() string { return proto.CompactTextString(m) } + +// +func (*NamespaceArr) ProtoMessage() {} + +/** + * @brief 服务数组定义 + */ +type ServiceArr []*api.Service + +// +func (m *ServiceArr) Reset() { *m = ServiceArr{} } + +// +func (m *ServiceArr) String() string { return proto.CompactTextString(m) } + +// +func (*ServiceArr) ProtoMessage() {} + +/** + * @brief 服务实例数组定义 + */ +type InstanceArr []*api.Instance + +// +func (m *InstanceArr) Reset() { *m = InstanceArr{} } + +// +func (m *InstanceArr) String() string { return proto.CompactTextString(m) } + +// +func (*InstanceArr) ProtoMessage() {} + +/** + * @brief 路由规则数组定义 + */ +type RoutingArr []*api.Routing + +// +func (m *RoutingArr) Reset() { *m = RoutingArr{} } + +// +func (m *RoutingArr) String() string { return proto.CompactTextString(m) } + +// +func (*RoutingArr) ProtoMessage() {} + +/** + * @brief 限流规则数组定义 + */ +type RateLimitArr []*api.Rule + +// +func (m *RateLimitArr) Reset() { *m = RateLimitArr{} } + +// +func (m *RateLimitArr) String() string { return proto.CompactTextString(m) } + +// +func (*RateLimitArr) ProtoMessage() {} + +/** + * @brief 熔断规则数组定义 + */ +type CircuitBreakerArr []*api.CircuitBreaker + +// +func (m *CircuitBreakerArr) Reset() { *m = CircuitBreakerArr{} } + +// +func (m *CircuitBreakerArr) String() string { return proto.CompactTextString(m) } + +// +func (*CircuitBreakerArr) ProtoMessage() {} + +/** + * @brief 发布规则数组定义 + */ +type ConfigReleaseArr []*api.ConfigRelease + +// +func (m *ConfigReleaseArr) Reset() { *m = ConfigReleaseArr{} } + +// +func (m *ConfigReleaseArr) String() string { return proto.CompactTextString(m) } + +// +func (*ConfigReleaseArr) ProtoMessage() {} + +/* + * @brief 平台数组定义 + */ +type PlatformArr []*api.Platform + +// proto reset +func (m *PlatformArr) Reset() { *m = PlatformArr{} } + +// proto string +func (m *PlatformArr) String() string { return proto.CompactTextString(m) } + +// proto message +func (m *PlatformArr) ProtoMessage() {} diff --git a/apiserver/httpserver/server.go b/apiserver/httpserver/server.go new file mode 100644 index 000000000..35922bb61 --- /dev/null +++ b/apiserver/httpserver/server.go @@ -0,0 +1,468 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package httpserver + +import ( + "context" + "fmt" + "github.com/polarismesh/polaris-server/apiserver" + "github.com/polarismesh/polaris-server/common/utils" + "net" + "net/http" + "net/http/pprof" + "strings" + "sync" + "time" + + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/connlimit" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/naming" + "github.com/polarismesh/polaris-server/plugin" + "github.com/emicklei/go-restful" + "github.com/pkg/errors" + "go.uber.org/zap" +) + +/** + * @brief HTTP API服务器 + */ +type Httpserver struct { + listenIP string + listenPort uint32 + connLimitConfig *connlimit.Config + option map[string]interface{} + openAPI map[string]apiserver.APIConfig + start bool + restart bool + exitCh chan struct{} + + enablePprof bool + + freeMemMu *sync.Mutex + + server *http.Server + namingServer *naming.Server + rateLimit plugin.Ratelimit + statis plugin.Statis + auth plugin.Auth +} + +const ( + Discover string = "Discover" +) + +/** + * @brief 获取端口 + */ +func (h *Httpserver) GetPort() uint32 { + return h.listenPort +} + +/** + * @brief 获取Server的协议 + */ +func (h *Httpserver) GetProtocol() string { + return "http" +} + +/** + * @brief 初始化HTTP API服务器 + */ +func (h *Httpserver) Initialize(ctx context.Context, option map[string]interface{}, + api map[string]apiserver.APIConfig) error { + h.option = option + h.openAPI = api + h.listenIP = option["listenIP"].(string) + h.listenPort = uint32(option["listenPort"].(int)) + h.enablePprof, _ = option["enablePprof"].(bool) + // 连接数限制的配置 + if raw, _ := option["connLimit"].(map[interface{}]interface{}); raw != nil { + connLimitConfig, err := connlimit.ParseConnLimitConfig(raw) + if err != nil { + return err + } + h.connLimitConfig = connLimitConfig + } + if rateLimit := plugin.GetRatelimit(); rateLimit != nil { + log.Infof("http server open the ratelimit") + h.rateLimit = rateLimit + } + + if auth := plugin.GetAuth(); auth != nil { + log.Infof("http server open the auth") + h.auth = auth + } + + h.freeMemMu = new(sync.Mutex) + + return nil +} + +/** + * @brief 启动HTTP API服务器 + */ +func (h *Httpserver) Run(errCh chan error) { + log.Infof("start httpserver") + h.exitCh = make(chan struct{}) + h.start = true + defer func() { + close(h.exitCh) + h.start = false + }() + + var err error + // 引入功能模块和插件 + h.namingServer, err = naming.GetServer() + if err != nil { + log.Errorf("%v", err) + errCh <- err + return + } + h.statis = plugin.GetStatis() + + // 初始化http server + address := fmt.Sprintf("%v:%v", h.listenIP, h.listenPort) + + wsContainer, err := h.createRestfulContainer() + if err != nil { + errCh <- err + return + } + + server := http.Server{Addr: address, Handler: wsContainer, WriteTimeout: 1 * time.Minute} + + ln, err := net.Listen("tcp", address) + if err != nil { + log.Errorf("net listen(%s) err: %s", address, err.Error()) + errCh <- err + return + } + ln = &tcpKeepAliveListener{ln.(*net.TCPListener)} + // 开启最大连接数限制 + if h.connLimitConfig != nil && h.connLimitConfig.OpenConnLimit { + log.Infof("http server use max connection limit per ip: %d, http max limit: %d", + h.connLimitConfig.MaxConnPerHost, h.connLimitConfig.MaxConnLimit) + ln, err = connlimit.NewListener(ln, h.GetProtocol(), h.connLimitConfig) + if err != nil { + log.Errorf("conn limit init err: %s", err.Error()) + errCh <- err + return + } + } + h.server = &server + + // 开始对外服务 + if err := server.Serve(ln); err != nil { + log.Errorf("%+v", err) + if !h.restart { + log.Infof("not in restart progress, broadcast error") + errCh <- err + } + + return + } + + log.Infof("httpserver stop") +} + +// shutdown server +func (h *Httpserver) Stop() { + // 释放connLimit的数据,如果没有开启,也需要执行一下 + // 目的:防止restart的时候,connLimit冲突 + connlimit.RemoteLimitListener(h.GetProtocol()) + if h.server != nil { + _ = h.server.Close() + } +} + +// restart server +func (h *Httpserver) Restart(option map[string]interface{}, api map[string]apiserver.APIConfig, + errCh chan error) error { + log.Infof("restart httpserver new config: %+v", option) + // 备份一下option + backupOption := h.option + // 备份一下api + backupAPI := h.openAPI + + // 设置restart标记,防止stop的时候把错误抛出 + h.restart = true + // 关闭httpserver + h.Stop() + // 等待httpserver退出 + if h.start { + <-h.exitCh + } + + log.Infof("old httpserver has stopped, begin restart httpserver") + + if err := h.Initialize(context.Background(), option, api); err != nil { + h.restart = false + if initErr := h.Initialize(context.Background(), backupOption, backupAPI); initErr != nil { + log.Errorf("start httpserver with backup cfg err: %s", initErr.Error()) + return initErr + } + go h.Run(errCh) + + log.Errorf("restart httpserver initialize err: %s", err.Error()) + return err + } + + log.Infof("init httpserver successfully, restart it") + h.restart = false + go h.Run(errCh) + return nil +} + +// 创建handler +func (h *Httpserver) createRestfulContainer() (*restful.Container, error) { + wsContainer := restful.NewContainer() + + // 增加CORS TODO + cors := restful.CrossOriginResourceSharing{ + // ExposeHeaders: []string{"X-My-Header"}, + AllowedHeaders: []string{"Content-Type", "Accept", "Request-Id"}, + AllowedMethods: []string{"GET", "POST", "PUT"}, + CookiesAllowed: false, + Container: wsContainer} + wsContainer.Filter(cors.Filter) + + // Add container filter to respond to OPTIONS + wsContainer.Filter(wsContainer.OPTIONSFilter) + + wsContainer.Filter(h.process) + + for name, config := range h.openAPI { + switch name { + case "admin": + if config.Enable { + wsContainer.Add(h.GetAdminServer()) + wsContainer.Add(h.GetMaintainAccessServer()) + } + case "console": + if config.Enable { + service, err := h.GetConsoleAccessServer(config.Include) + if err != nil { + return nil, err + } + wsContainer.Add(service) + } + case "client": + if config.Enable { + service, err := h.GetClientAccessServer(config.Include) + if err != nil { + return nil, err + } + wsContainer.Add(service) + } + default: + log.Errorf("api %s does not exist in httpserver", name) + return nil, fmt.Errorf("api %s does not exist in httpserver", name) + } + } + + if h.enablePprof { + h.enablePprofAccess(wsContainer) + } + return wsContainer, nil +} + +// 开启pprof接口 +func (h *Httpserver) enablePprofAccess(wsContainer *restful.Container) { + wsContainer.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index)) + wsContainer.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline)) + wsContainer.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile)) + wsContainer.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol)) +} + +/** + * @brief 在接收和回复时统一处理请求 + */ +func (h *Httpserver) process(req *restful.Request, rsp *restful.Response, chain *restful.FilterChain) { + func() { + if err := h.preprocess(req, rsp); err != nil { + return + } + + chain.ProcessFilter(req, rsp) + }() + + h.postproccess(req, rsp) +} + +/** + * @brief 请求预处理 + */ +func (h *Httpserver) preprocess(req *restful.Request, rsp *restful.Response) error { + // 设置开始时间 + req.SetAttribute("start-time", time.Now()) + + // 处理请求ID + requestID := req.HeaderParameter("Request-Id") + if requestID == "" { + // TODO: 设置请求ID + } + platformID := req.HeaderParameter("Platform-Id") + + if !strings.Contains(req.Request.URL.String(), Discover) { + // 打印请求 + log.Info("receive request", + zap.String("client-address", req.Request.RemoteAddr), + zap.String("user-agent", req.HeaderParameter("User-Agent")), + zap.String("request-id", requestID), + zap.String("platform-id", platformID), + zap.String("method", req.Request.Method), + zap.String("url", req.Request.URL.String()), + ) + } + + // 管理端接口访问鉴权 + if strings.Contains(req.Request.URL.String(), "naming") { + if err := h.enterAuth(req, rsp); err != nil { + return err + } + } + + // 限流 + if err := h.enterRateLimit(req, rsp); err != nil { + return err + } + + return nil +} + +/** + * @brief 请求后处理:统计 + */ +func (h *Httpserver) postproccess(req *restful.Request, rsp *restful.Response) { + now := time.Now() + + // 接口调用统计 + path := req.Request.URL.Path + if path != "/" { + // 去掉最后一个"/" + path = strings.TrimSuffix(path, "/") + } + method := req.Request.Method + ":" + path + startTime := req.Attribute("start-time").(time.Time) + code, ok := req.Attribute(utils.PolarisCode).(uint32) + if !ok { + code = uint32(rsp.StatusCode()) + } + + diff := now.Sub(startTime) + // 打印耗时超过1s的请求 + if diff > time.Second { + log.Info("handling time > 1s", + zap.String("client-address", req.Request.RemoteAddr), + zap.String("user-agent", req.HeaderParameter("User-Agent")), + zap.String("request-id", req.HeaderParameter("Request-Id")), + zap.String("method", req.Request.Method), + zap.String("url", req.Request.URL.String()), + zap.Duration("handling-time", diff), + ) + } + + _ = h.statis.AddAPICall(method, int(code), diff.Nanoseconds()) +} + +/** + * @brief 访问鉴权 + */ +func (h *Httpserver) enterAuth(req *restful.Request, rsp *restful.Response) error { + // 判断鉴权插件是否开启 + if h.auth == nil { + return nil + } + + rid := req.HeaderParameter("Request-Id") + pid := req.HeaderParameter("Platform-Id") + pToken := req.HeaderParameter("Platform-Token") + + address := req.Request.RemoteAddr + segments := strings.Split(address, ":") + if len(segments) != 2 { + return nil + } + + if !h.auth.IsWhiteList(segments[0]) && !h.auth.Allow(pid, pToken) { + log.Error("http access is not allowed", + zap.String("client", address), + zap.String("request-id", rid), + zap.String("platform-id", pid), + zap.String("platform-token", pToken)) + HTTPResponse(req, rsp, api.NotAllowedAccess) + return errors.New("http access is not allowed") + } + return nil +} + +// 访问限制 +func (h *Httpserver) enterRateLimit(req *restful.Request, rsp *restful.Response) error { + // 检查限流插件是否开启 + if h.rateLimit == nil { + return nil + } + + rid := req.HeaderParameter("Request-Id") + // IP级限流 + // 先获取当前请求的address + address := req.Request.RemoteAddr + segments := strings.Split(address, ":") + if len(segments) != 2 { + return nil + } + if ok := h.rateLimit.Allow(plugin.IPRatelimit, segments[0]); !ok { + log.Error("ip ratelimit is not allow", zap.String("client", address), + zap.String("request-id", rid)) + HTTPResponse(req, rsp, api.IPRateLimit) + return errors.New("ip ratelimit is not allow") + } + + // 接口级限流 + apiName := fmt.Sprintf("%s:%s", req.Request.Method, + strings.TrimSuffix(req.Request.URL.Path, "/")) + if ok := h.rateLimit.Allow(plugin.APIRatelimit, apiName); !ok { + log.Error("api ratelimit is not allow", zap.String("client", address), + zap.String("request-id", rid), zap.String("api", apiName)) + HTTPResponse(req, rsp, api.APIRateLimit) + return errors.New("api ratelimit is not allow") + } + + return nil +} + +// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted +// connections. It's used by ListenAndServe and ListenAndServeTLS so +// dead TCP connections (e.g. closing laptop mid-download) eventually +// go away. +// 来自net/http +type tcpKeepAliveListener struct { + *net.TCPListener +} + +// 来自于net/http +func (ln tcpKeepAliveListener) Accept() (net.Conn, error) { + tc, err := ln.AcceptTCP() + if err != nil { + return nil, err + } + tc.SetKeepAlive(true) + tc.SetKeepAlivePeriod(3 * time.Minute) + return tc, nil +} diff --git a/apiserver/l5pbserver/default.go b/apiserver/l5pbserver/default.go new file mode 100644 index 000000000..2e5712ae2 --- /dev/null +++ b/apiserver/l5pbserver/default.go @@ -0,0 +1,29 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package l5pbserver + +import ( + "github.com/polarismesh/polaris-server/apiserver" +) + +/** + * @brief 自注册到API服务器插槽 + */ +func init() { + _ = apiserver.Register("l5pbserver", &L5pbserver{}) +} diff --git a/apiserver/l5pbserver/naming.go b/apiserver/l5pbserver/naming.go new file mode 100644 index 000000000..eb0a9618c --- /dev/null +++ b/apiserver/l5pbserver/naming.go @@ -0,0 +1,212 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package l5pbserver + +import ( + "bufio" + "context" + "encoding/binary" + "github.com/polarismesh/polaris-server/common/api/l5" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/utils" + "github.com/golang/protobuf/proto" + "go.uber.org/zap" + "io" + "net" + "time" +) + +type l5Code uint32 + +const ( + l5Success l5Code = iota + l5ResponseFailed + l5UnmarshalPacketFailed + l5SyncByAgentCmdFailed + l5RegisterByNameCmdFailed + l5MarshalPacketFailed + l5PacketCmdFailed +) + +// 连接处理的协程函数 +// 每个客户端会新建一个协程 +// 使用方法 go handleConnection(conn) +func (l *L5pbserver) handleConnection(conn net.Conn) { + defer conn.Close() + + req := &cl5Request{ + conn: conn, + clientAddr: conn.RemoteAddr().String(), + } + + // 先读取头部数据,然后再根据packetLength读取body数据 + header := make([]byte, headSize) + bufRead := bufio.NewReader(conn) + for { + if _, err := io.ReadFull(bufRead, header); err != nil { + // end fo the reader + if err == io.EOF { + return + } + log.Errorf("[Cl5] read header from conn(%s) err: %s", req.clientAddr, err.Error()) + return + } + packetLength := checkRequest(header) + if packetLength <= headSize || packetLength > maxSize { // 包大小检查 + log.Errorf("[Cl5] read header from conn(%s) found body length(%d) invalid", + req.clientAddr, packetLength) + return + } + body := make([]byte, packetLength-headSize) + if _, err := io.ReadFull(bufRead, body); err != nil { + log.Errorf("[Cl5] read body from conn(%s) err: %s", req.clientAddr, err.Error()) + return + } + if code := l.handleRequest(req, body); code != l5Success { + log.Error("[CL5] catch error code", zap.Uint32("code", uint32(code)), + zap.String("client", req.clientAddr)) + return + } + } +} + +// cl5请求的handle入口 +func (l *L5pbserver) handleRequest(req *cl5Request, requestData []byte) l5Code { + req.start = time.Now() // 从解包开始计算开始时间 + cl5Pkg := &l5.Cl5Pkg{} + err := proto.Unmarshal(requestData, cl5Pkg) + if err != nil { + log.Errorf("[Cl5] client(%s) Unmarshal requestData error: %v", + req.clientAddr, err) + return l5UnmarshalPacketFailed + } + + req.cmd = cl5Pkg.GetCmd() + l.PreProcess(req) + switch cl5Pkg.GetCmd() { + case int32(l5.CL5_CMD_CL5_REGISTER_BY_NAME_CMD): + req.code = l.handleRegisterByNameCmd(req.conn, cl5Pkg) + case int32(l5.CL5_CMD_CL5_SYNC_BY_AGENT_CMD): + req.code = l.handleSyncByAgentCmd(req.conn, cl5Pkg) + default: + log.Errorf("receive invalid cmd[%d] from [%d]", cl5Pkg.GetCmd(), cl5Pkg.GetIp()) + req.code = l5PacketCmdFailed + } + l.PostProcess(req) + return req.code +} + +// 根据SID列表获取路由信息 +func (l *L5pbserver) handleSyncByAgentCmd(conn net.Conn, iPkg *l5.Cl5Pkg) l5Code { + ctx := context.Background() + ctx = context.WithValue(ctx, utils.Cl5ServerCluster{}, l.clusterName) + ctx = context.WithValue(ctx, utils.Cl5ServerProtocol{}, l.GetProtocol()) + syncByAgentAck, err := l.namingServer.SyncByAgentCmd(ctx, iPkg.GetSyncByAgentCmd()) + if err != nil { + log.Errorf("%v", err) + return l5SyncByAgentCmdFailed + } + + oPkg := &l5.Cl5Pkg{ + Seqno: proto.Int32(iPkg.GetSeqno()), + Cmd: proto.Int(int(l5.CL5_CMD_CL5_SYNC_BY_AGENT_ACK_CMD)), + Result: proto.Int32(success), + Ip: proto.Int32(iPkg.GetIp()), + SyncByAgentAckCmd: syncByAgentAck, + } + + log.Infof("handle sync by agent cmd, sid count(%d), callee count(%d), ip count(%d)", + len(iPkg.GetSyncByAgentCmd().GetOptList().GetOpt()), + len(oPkg.GetSyncByAgentAckCmd().GetServList().GetServ()), + len(oPkg.GetSyncByAgentAckCmd().GetIpcList().GetIpc())) + return response(conn, oPkg) +} + +// 根据服务名列表寻找对应的SID列表 +func (l *L5pbserver) handleRegisterByNameCmd(conn net.Conn, iPkg *l5.Cl5Pkg) l5Code { + registerByNameAck, err := l.namingServer.RegisterByNameCmd(iPkg.GetRegisterByNameCmd()) + if err != nil { + log.Errorf("%v", err) + return l5RegisterByNameCmdFailed + } + + oPkg := &l5.Cl5Pkg{ + Seqno: proto.Int32(iPkg.GetSeqno()), + Cmd: proto.Int(int(l5.CL5_CMD_CL5_REGISTER_BY_NAME_ACK_CMD)), + Result: proto.Int32(success), + Ip: proto.Int32(iPkg.GetIp()), + RegisterByNameAckCmd: registerByNameAck, + } + + return response(conn, oPkg) +} + +// 请求包完整性检查 +func checkRequest(buffer []byte) int { + var length uint32 + isLittle := isLittleEndian() + if isLittle { + length = binary.LittleEndian.Uint32(buffer[sohSize:headSize]) + } else { + length = binary.BigEndian.Uint32(buffer[sohSize:headSize]) + } + + return int(length) +} + +// 回复数据 +func response(conn net.Conn, pkg *l5.Cl5Pkg) l5Code { + responseData, err := proto.Marshal(pkg) + if err != nil { + log.Errorf("Marshal responseData error: %v", err) + return l5MarshalPacketFailed + } + + sohData := make([]byte, 2) + lengthData := make([]byte, 4) + var soh uint16 = 1 + length := uint32(binary.Size(responseData) + headSize) + + isLittle := isLittleEndian() + if isLittle { + binary.LittleEndian.PutUint16(sohData, soh) + binary.LittleEndian.PutUint32(lengthData, length) + } else { + binary.BigEndian.PutUint16(sohData, soh) + binary.BigEndian.PutUint32(lengthData, length) + } + + sohData = append(sohData, lengthData...) + sohData = append(sohData, responseData...) + + if _, err = conn.Write(sohData); err != nil { + log.Errorf("conn write error: %v", err) + return l5ResponseFailed + } + return l5Success +} + +// 判断系统是大端/小端存储 +func isLittleEndian() bool { + a := int16(0x1234) + b := int8(a) + if 0x34 == b { + return true + } + return false +} diff --git a/apiserver/l5pbserver/server.go b/apiserver/l5pbserver/server.go new file mode 100644 index 000000000..2a46b14ad --- /dev/null +++ b/apiserver/l5pbserver/server.go @@ -0,0 +1,178 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package l5pbserver + +import ( + "context" + "fmt" + "github.com/polarismesh/polaris-server/apiserver" + "github.com/polarismesh/polaris-server/plugin" + "go.uber.org/zap" + "net" + "time" + + "github.com/polarismesh/polaris-server/common/api/l5" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/naming" +) + +const ( + success int32 = 100 + sohSize int = 2 + headSize int = 6 + maxSize int = 1024 * 1024 * 2 + defaultClusterName string = "cl5.discover" +) + +// 每个链接,封装为一个请求 +type cl5Request struct { + conn net.Conn + start time.Time + clientAddr string + cmd int32 + code l5Code +} + +/** + * @brief CL5 API服务器 + */ +type L5pbserver struct { + listenIP string + listenPort uint32 + clusterName string // 集群名 + + listener net.Listener + namingServer *naming.Server + statis plugin.Statis +} + +/** + * @brief 获取端口 + */ +func (l *L5pbserver) GetPort() uint32 { + return l.listenPort +} + +/** + * @brief 获取Server的协议 + */ +func (l *L5pbserver) GetProtocol() string { + return "l5pb" +} + +/** + * @brief 初始化CL5 API服务器 + */ +func (l *L5pbserver) Initialize(ctx context.Context, option map[string]interface{}, + api map[string]apiserver.APIConfig) error { + l.listenIP = option["listenIP"].(string) + l.listenPort = uint32(option["listenPort"].(int)) + // 获取当前集群 + l.clusterName = defaultClusterName + if clusterName, _ := option["clusterName"].(string); clusterName != "" { + l.clusterName = clusterName + } + + return nil +} + +/** + * @brief 启动CL5 API服务器 + */ +func (l *L5pbserver) Run(errCh chan error) { + log.Infof("start l5pbserver") + + address := fmt.Sprintf("%v:%v", l.listenIP, l.listenPort) + listener, err := net.Listen("tcp", address) + if err != nil { + log.Errorf("listen error: %v", err) + errCh <- err + return + } + l.listener = listener + + // 引入功能模块和插件 + l.namingServer, err = naming.GetServer() + if err != nil { + log.Errorf("%v", err) + errCh <- err + return + } + l.statis = plugin.GetStatis() + + for { + conn, err := listener.Accept() + if err != nil { + log.Errorf("accept error: %v", err) + errCh <- err + return + } + //log.Infof("new connect: %v", conn.RemoteAddr()) + go l.handleConnection(conn) + } +} + +// stop server +func (l *L5pbserver) Stop() { + if l.listener != nil { + _ = l.listener.Close() + } +} + +// restart server +func (l *L5pbserver) Restart(option map[string]interface{}, api map[string]apiserver.APIConfig, + errCh chan error) error { + return nil +} + +/** + * @brief 请求预处理:限频/鉴权 + */ +func (l *L5pbserver) PreProcess(req *cl5Request) bool { + log.Info("[Cl5] handle request", zap.String("ClientAddr", req.clientAddr), zap.Int32("Cmd", req.cmd)) + var result = true + // 访问频率限制 + + // 访问权限控制 + + return result +} + +/** + * @brief 请求后处理:统计/告警 + */ +func (l *L5pbserver) PostProcess(req *cl5Request) { + now := time.Now() + // 统计 + cmdStr, ok := l5.CL5_CMD_name[req.cmd] + if !ok { + cmdStr = "Unrecognizable_Cmd" + } + + diff := now.Sub(req.start) + // 打印耗时超过1s的请求 + if diff > time.Second { + log.Info("handling time > 1s", + zap.String("client-addr", req.clientAddr), + zap.String("cmd", cmdStr), + zap.Duration("handling-time", diff), + ) + } + _ = l.statis.AddAPICall(cmdStr, int(req.code), diff.Nanoseconds()) + // 告警 +} diff --git a/bootstrap/config.go b/bootstrap/config.go new file mode 100644 index 000000000..88cd8b453 --- /dev/null +++ b/bootstrap/config.go @@ -0,0 +1,18 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package bootstrap diff --git a/bootstrap/run_linux.go b/bootstrap/run_linux.go new file mode 100644 index 000000000..e39bf47ef --- /dev/null +++ b/bootstrap/run_linux.go @@ -0,0 +1,54 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package bootstrap + +import ( + "github.com/polarismesh/polaris-server/apiserver" + "github.com/polarismesh/polaris-server/common/log" + "os" + "os/signal" + "syscall" +) + +// server主循环 +func RunMainLoop(servers []apiserver.Apiserver, errCh chan error) { + defer StopServers(servers) + + ch := make(chan os.Signal) + signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, + syscall.SIGSEGV, syscall.SIGUSR1) + for { + select { + case s := <-ch: + // restart信号 + if s.(syscall.Signal) == syscall.SIGUSR1 { + // 注意:重启失败,退出程序 + if err := RestartServers(errCh); err != nil { + log.Errorf("restart servers err: %s", err.Error()) + return + } + } else { + log.Infof("catch signal(%+v), stop servers", s) + return + } + case err := <-errCh: + log.Errorf("catch api server err: %s", err.Error()) + return + } + } +} \ No newline at end of file diff --git a/bootstrap/run_windows.go b/bootstrap/run_windows.go new file mode 100644 index 000000000..fd44f56d3 --- /dev/null +++ b/bootstrap/run_windows.go @@ -0,0 +1,45 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package bootstrap + +import ( + "github.com/polarismesh/polaris-server/apiserver" + "github.com/polarismesh/polaris-server/common/log" + "os" + "os/signal" + "syscall" +) + +// server主循环 +func RunMainLoop(servers []apiserver.Apiserver, errCh chan error) { + defer StopServers(servers) + + ch := make(chan os.Signal) + signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, + syscall.SIGSEGV) + for { + select { + case s := <-ch: + log.Infof("catch signal(%+v), stop servers", s) + return + case err := <-errCh: + log.Errorf("catch api server err: %s", err.Error()) + return + } + } +} \ No newline at end of file diff --git a/bootstrap/server.go b/bootstrap/server.go new file mode 100644 index 000000000..5aaf45e19 --- /dev/null +++ b/bootstrap/server.go @@ -0,0 +1,414 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package bootstrap + +import ( + "context" + "errors" + "fmt" + "net" + "strings" + "time" + + "github.com/polarismesh/polaris-server/common/version" + "github.com/polarismesh/polaris-server/config" + + "github.com/polarismesh/polaris-server/apiserver" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/utils" + "github.com/polarismesh/polaris-server/naming" + "github.com/polarismesh/polaris-server/plugin" + "github.com/polarismesh/polaris-server/store" +) + +var ( + SelfServiceInstance = make([]*api.Instance, 0) + LocalHost = "127.0.0.1" + ConfigFilePath = "" +) + +/** + * @brief 启动 + */ +func Start(configFilePath string) { + // 加载配置 + ConfigFilePath = configFilePath + cfg, err := config.Load(configFilePath) + if err != nil { + fmt.Printf("[ERROR] loadConfig fail\n") + return + } + + fmt.Printf("%+v\n", *cfg) + + // 初始化日志打印 + err = cfg.Bootstrap.Logger.SetOutputLevel(log.DefaultScopeName, cfg.Bootstrap.Logger.Level) + if err != nil { + fmt.Printf("[ERROR] %v\n", err) + return + } + + cfg.Bootstrap.Logger.SetStackTraceLevel(log.DefaultScopeName, "none") + + cfg.Bootstrap.Logger.SetLogCallers(log.DefaultScopeName, true) + + err = log.Configure(&cfg.Bootstrap.Logger) + if err != nil { + fmt.Printf("[ERROR] %v\n", err) + return + } + + // 初始化 + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // 获取本地IP地址 + ctx, err = acquireLocalhost(ctx, &cfg.Bootstrap.PolarisService) + if err != nil { + fmt.Printf("[ERROR] %s\n", err.Error()) + return + } + + // 设置插件配置 + plugin.SetPluginConfig(&cfg.Plugin) + + // 初始化存储层 + store.SetStoreConfig(&cfg.Store) + s, _ := store.GetStore() + + // 开启进入启动流程,初始化插件,加载数据等 + tx, err := StartBootstrapOrder(s, cfg) + if err != nil { + // 多次尝试加锁失败 + fmt.Printf("[ERROR] %v\n", err) + return + } + + cfg.Naming.HealthCheck.LocalHost = LocalHost //补充healthCheck的配置 + naming.SetHealthCheckConfig(&cfg.Naming.HealthCheck) + err = naming.Initialize(ctx, &cfg.Naming, &cfg.Cache) + if err != nil { + fmt.Printf("[ERROR] %v\n", err) + return + } + + errCh := make(chan error, len(cfg.APIServers)) + servers, err := StartServers(ctx, cfg, errCh) + if err != nil { + return + } + + if err := polarisServiceRegister(&cfg.Bootstrap.PolarisService, cfg.APIServers); err != nil { + fmt.Printf("[ERROR] %v\n", err) + return + } + _ = FinishBootstrapOrder(tx) // 启动完成,解锁 + fmt.Println("finish starting server") + + RunMainLoop(servers, errCh) +} + +// 启动server +func StartServers(ctx context.Context, cfg *config.Config, errCh chan error) ( + []apiserver.Apiserver, error) { + // 启动API服务器 + var servers []apiserver.Apiserver + for _, protocol := range cfg.APIServers { + slot, exist := apiserver.Slots[protocol.Name] + if !exist { + fmt.Printf("[ERROR] apiserver slot %s not exists\n", protocol.Name) + return nil, fmt.Errorf("apiserver slot %s not exists", protocol.Name) + } + + err := slot.Initialize(ctx, protocol.Option, protocol.API) + if err != nil { + fmt.Printf("[ERROR] %v\n", err) + return nil, fmt.Errorf("apiserver %s initialize err: %s", protocol.Name, err.Error()) + } + + servers = append(servers, slot) + go slot.Run(errCh) + } + + return servers, nil +} + +// 重启server +func RestartServers(errCh chan error) error { + // 重新加载配置 + cfg, err := config.Load(ConfigFilePath) + if err != nil { + log.Infof("restart servers, reload config") + return err + } + log.Infof("new config: %+v", cfg) + + // 把配置的每个apiserver,进行重启 + for _, protocol := range cfg.APIServers { + server, exist := apiserver.Slots[protocol.Name] + if !exist { + log.Errorf("apiserver slot %s not exists\n", protocol.Name) + return err + } + log.Infof("begin restarting server: %s", protocol.Name) + if err := server.Restart(protocol.Option, protocol.API, errCh); err != nil { + return err + } + } + return nil +} + +// 接受外部信号,停止server +func StopServers(servers []apiserver.Apiserver) { + // 先反注册所有服务 + SelfDeregister() + + // 停掉服务 + for _, s := range servers { + log.Infof("stop server protocol: %s", s.GetProtocol()) + s.Stop() + } +} + +// 开始进入启动加锁 +// 原因:Server启动的时候会从数据库拉取大量数据,防止同时启动把DB压死 +// 还有一种场景,server全部宕机批量重启,导致数据库被压死,导致雪崩 +func StartBootstrapOrder(s store.Store, c *config.Config) (store.Transaction, error) { + order := c.Bootstrap.StartInOrder + log.Infof("[Bootstrap] get bootstrap order config: %+v", order) + open, _ := order["open"].(bool) + key, _ := order["key"].(string) + if !open || key == "" { + log.Infof("[Bootstrap] start in order config is not open or key is null") + return nil, nil + } + + log.Infof("bootstrap start in order with key: %s", key) + + // 启动一个日志协程,当等锁的时候,可以看到server正在等待锁 + stopCh := make(chan struct{}) + defer close(stopCh) // 函数退出的时候,关闭stopCh + go func() { + ticker := time.NewTicker(time.Second * 10) + defer ticker.Stop() + for { + select { + case <-ticker.C: + log.Infof("bootstrap waiting the lock") + case <-stopCh: + return + } + } + }() + + // 重试多次 + maxTimes := 10 + for i := 0; i < maxTimes; i++ { + tx, err := s.CreateTransaction() + if err != nil { + log.Errorf("create transaction err: %s", err.Error()) + return nil, err + } + // 这里可能会出现锁超时,超时则重试 + if err := tx.LockBootstrap(key, LocalHost); err != nil { + log.Errorf("lock bootstrap err: %s", err.Error()) + _ = tx.Commit() + continue + } + // 加锁成功,直接返回 + log.Infof("lock bootstrap success") + return tx, nil + } + + return nil, errors.New("lock bootstrap error") +} + +// FinishBootstrapOrder +func FinishBootstrapOrder(tx store.Transaction) error { + if tx != nil { + return tx.Commit() + } + + return nil +} + +// 生成一个context +func genContext() context.Context { + ctx := context.Background() + ctx = context.WithValue(ctx, utils.StringContext("request-id"), fmt.Sprintf("self-%d", time.Now().Nanosecond())) + return ctx +} + +// 探测获取本机IP地址 +func acquireLocalhost(ctx context.Context, polarisService *config.PolarisService) (context.Context, error) { + if polarisService == nil || !polarisService.EnableRegister { + log.Infof("[Bootstrap] not found polaris service config") + return ctx, nil + } + + localHost, err := getLocalHost(polarisService.ProbeAddress) + if err != nil { + log.Errorf("get local host err: %s", err.Error()) + return nil, err + } + log.Infof("[Bootstrap] get local host: %s", localHost) + LocalHost = localHost + utils.WithLocalhost(ctx, localHost) + return utils.WithLocalhost(ctx, localHost), nil +} + +// 自注册主函数 +func polarisServiceRegister(polarisService *config.PolarisService, apiServers []apiserver.Config) error { + if polarisService == nil || !polarisService.EnableRegister { + log.Infof("[Bootstrap] not enable register the polaris service") + return nil + } + + apiServerNames := make(map[string]bool) + for _, server := range apiServers { + apiServerNames[server.Name] = true + } + + // 开始注册每个服务 + for _, service := range polarisService.Services { + protocols := service.Protocols + // 如果service.Protocols为空,默认采用protocols注册 + if len(protocols) == 0 { + for _, server := range apiServers { + protocols = append(protocols, server.Name) + } + } + + for _, name := range protocols { + if _, exist := apiServerNames[name]; !exist { + return fmt.Errorf("not register the server(%s)", name) + } + slot, exist := apiserver.Slots[name] + if !exist { + return fmt.Errorf("not exist the server(%s)", name) + } + host := LocalHost + port := slot.GetPort() + protocol := slot.GetProtocol() + if err := selfRegister(host, port, protocol, polarisService.Isolated, service); err != nil { + log.Errorf("self register err: %s", err.Error()) + return err + } + } + } + + return nil +} + +// 服务自注册 +func selfRegister(host string, port uint32, protocol string, isolated bool, polarisService *config.Service) error { + server, err := naming.GetServer() + if err != nil { + return err + } + storage, err := store.GetStore() + if err != nil { + return err + } + + name := config.DefaultPolarisName + namespace := config.DefaultPolarisNamespace + if polarisService.Name != "" { + name = polarisService.Name + } + + if polarisService.Namespace != "" { + namespace = polarisService.Namespace + } + + service, err := storage.GetService(name, namespace) + if err != nil { + return err + } + if service == nil { + return fmt.Errorf("not found the self service(%s), namespace(%s)", + name, namespace) + } + + req := &api.Instance{ + Service: utils.NewStringValue(name), + Namespace: utils.NewStringValue(namespace), + Host: utils.NewStringValue(host), + Port: utils.NewUInt32Value(port), + Protocol: utils.NewStringValue(protocol), + ServiceToken: utils.NewStringValue(service.Token), + Version: utils.NewStringValue(version.Get()), + Isolate: utils.NewBoolValue(isolated), // 自注册,默认是隔离的 + Metadata: map[string]string{ + "build-revision": version.GetRevision(), + }, + } + + resp := server.CreateInstance(genContext(), req) + if api.CalcCode(resp) != 200 { + // 如果self之前注册过,那么可以忽略 + if resp.GetCode().GetValue() != api.ExistedResource { + return fmt.Errorf("%s", resp.GetInfo().GetValue()) + } + + resp = server.UpdateInstance(genContext(), req) + if api.CalcCode(resp) != 200 { + return fmt.Errorf("%s", resp.GetInfo().GetValue()) + } + + } + SelfServiceInstance = append(SelfServiceInstance, req) + + return nil +} + +// Server退出的时候,自动反注册 +func SelfDeregister() { + namingServer, err := naming.GetServer() + if err != nil { + log.Errorf("get naming server obj err: %s", err.Error()) + return + } + for _, req := range SelfServiceInstance { + log.Infof("Deregister the instance(%+v)", req) + if resp := namingServer.DeleteInstance(genContext(), req); api.CalcCode(resp) != 200 { + // 遇到失败,继续反注册其他的实例 + log.Errorf("Deregister instance error: %s", resp.GetInfo().GetValue()) + } + } + + return +} + +// 获取本地IP地址 +func getLocalHost(vip string) (string, error) { + conn, err := net.Dial("tcp", vip) + if err != nil { + return "", err + } + defer func() { _ = conn.Close() }() + + localAddr := conn.LocalAddr().String() // ip:port + segs := strings.Split(localAddr, ":") + if len(segs) != 2 { + return "", fmt.Errorf("get local address format is invalid") + } + + return segs[0], nil +} diff --git a/build.sh b/build.sh new file mode 100644 index 000000000..854b05fa0 --- /dev/null +++ b/build.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +workdir=$(dirname $(realpath $0)) +version=$(cat version 2>/dev/null) +folder_name="polaris-server-release_${version}" +pkg_name="${folder_name}.tar.gz" + +cd $workdir + +# 清理环境 +rm -rf ${folder_name} +rm -f "${pkg_name}" + +# 编译 +rm -f polaris-server + +build_date=$(date "+%Y%m%d.%H%M%S") +package="github.com/polarismesh/polaris-server/common/version" +go build -o polaris-server -ldflags="-X ${package}.Version=${version} -X ${package}.BuildDate=${build_date}" + +# 打包 +mkdir -p ${folder_name} +mv polaris-server ${folder_name} +cp polaris-server.yaml ${folder_name} +cp -r tool ${folder_name}/ +tar -czvf "${pkg_name}" ${folder_name} +md5sum ${pkg_name} > "${pkg_name}.md5sum" diff --git a/build_cmdb.sh b/build_cmdb.sh new file mode 100644 index 000000000..906eb1694 --- /dev/null +++ b/build_cmdb.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +workdir=$(dirname $(realpath $0)) +version=$(cat version 2>/dev/null) +folder_name="polaris-cmdb-syncer-release_${version}" +pkg_name="${folder_name}.tar.gz" + +cd $workdir/plugin/cmdb/tencent/cmdbSyncer + +# 清理环境 +rm -rf ${folder_name} +rm -f "${pkg_name}" + +# 编译 +rm -f polaris-cmdb-syncer + +build_date=$(date "+%Y%m%d.%H%M%S") +package="github.com/polarismesh/polaris-server/common/version" +go build -mod=vendor -o polaris-cmdb-syncer -ldflags="-X ${package}.Version=${version} -X ${package}.BuildDate=${build_date}" + +# 打包 +mkdir -p ${folder_name} +mv polaris-cmdb-syncer ${folder_name} +cp config.yaml ${folder_name} +cp -r ../cmdb-tools/ ${folder_name}/cmdb-tools +cp -r ../tool/ ${folder_name}/tool +tar -czvf "${pkg_name}" ${folder_name} +mv "${pkg_name}" $workdir +cd $workdir +md5sum ${pkg_name} > "${pkg_name}.md5sum" \ No newline at end of file diff --git a/cmd/config.go b/cmd/config.go new file mode 100644 index 000000000..9327b5e20 --- /dev/null +++ b/cmd/config.go @@ -0,0 +1,27 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package cmd + +import "github.com/polarismesh/polaris-server/plugin" + +/** + * @brief 启动配置 + */ +type Config struct { + Plugin *plugin.Config +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 000000000..0d9a7adf5 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,47 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package cmd + +import ( + "github.com/spf13/cobra" +) + +var ( + rootCmd = &cobra.Command{ + Use: "polaris-server", + Short: "polaris server", + Long: "polaris server", + SilenceUsage: true, + } +) + +/** + * @brief 初始化命令行工具 + */ +func init() { + rootCmd.AddCommand(startCmd) + rootCmd.AddCommand(versionCmd) + rootCmd.AddCommand(revisionCmd) +} + +/** + * @brief 执行命令行解析 + */ +func Execute() { + rootCmd.Execute() +} diff --git a/cmd/start.go b/cmd/start.go new file mode 100644 index 000000000..804d3b965 --- /dev/null +++ b/cmd/start.go @@ -0,0 +1,43 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package cmd + +import ( + "github.com/polarismesh/polaris-server/bootstrap" + "github.com/spf13/cobra" +) + +var ( + configFilePath = "" + + startCmd = &cobra.Command{ + Use: "start", + Short: "start running", + Long: "start running", + Run: func(c *cobra.Command, args []string) { + bootstrap.Start(configFilePath) + }, + } +) + +/** + * @brief 解析命令参数 + */ +func init() { + startCmd.PersistentFlags().StringVarP(&configFilePath, "config", "c", "polaris-server.yaml", "config file path") +} diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 000000000..a7a91bfc7 --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,44 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package cmd + +import ( + "fmt" + "github.com/polarismesh/polaris-server/common/version" + "github.com/spf13/cobra" +) + +var ( + versionCmd = &cobra.Command{ + Use: "version", + Short: "print version", + Long: "print version", + Run: func(c *cobra.Command, args []string) { + fmt.Printf("version: %v\n", version.Get()) + }, + } + + revisionCmd = &cobra.Command{ + Use: "revision", + Short: "print revision with building date", + Long: "print revision with building date", + Run: func(cmd *cobra.Command, args []string) { + fmt.Printf("revision: %v\n", version.GetRevision()) + }, + } +) diff --git a/common/api/l5/cl5.pb.go b/common/api/l5/cl5.pb.go new file mode 100644 index 000000000..d7aec0dc8 --- /dev/null +++ b/common/api/l5/cl5.pb.go @@ -0,0 +1,1271 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: cl5.proto + +package l5 + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type CL5_CMD int32 + +const ( + // ------------------------------------------------ + CL5_CMD_CL5_SYNC_BY_AGENT_CMD CL5_CMD = 100 + CL5_CMD_CL5_SYNC_BY_AGENT_ACK_CMD CL5_CMD = 101 + CL5_CMD_CL5_REGISTER_BY_ID_CMD CL5_CMD = 102 + CL5_CMD_CL5_REGISTER_BY_ID_ACK_CMD CL5_CMD = 103 + CL5_CMD_CL5_REGISTER_BY_NAME_CMD CL5_CMD = 104 + CL5_CMD_CL5_REGISTER_BY_NAME_ACK_CMD CL5_CMD = 105 +) + +var CL5_CMD_name = map[int32]string{ + 100: "CL5_SYNC_BY_AGENT_CMD", + 101: "CL5_SYNC_BY_AGENT_ACK_CMD", + 102: "CL5_REGISTER_BY_ID_CMD", + 103: "CL5_REGISTER_BY_ID_ACK_CMD", + 104: "CL5_REGISTER_BY_NAME_CMD", + 105: "CL5_REGISTER_BY_NAME_ACK_CMD", +} +var CL5_CMD_value = map[string]int32{ + "CL5_SYNC_BY_AGENT_CMD": 100, + "CL5_SYNC_BY_AGENT_ACK_CMD": 101, + "CL5_REGISTER_BY_ID_CMD": 102, + "CL5_REGISTER_BY_ID_ACK_CMD": 103, + "CL5_REGISTER_BY_NAME_CMD": 104, + "CL5_REGISTER_BY_NAME_ACK_CMD": 105, +} + +func (x CL5_CMD) Enum() *CL5_CMD { + p := new(CL5_CMD) + *p = x + return p +} +func (x CL5_CMD) String() string { + return proto.EnumName(CL5_CMD_name, int32(x)) +} +func (x *CL5_CMD) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(CL5_CMD_value, data, "CL5_CMD") + if err != nil { + return err + } + *x = CL5_CMD(value) + return nil +} +func (CL5_CMD) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_cl5_5845103484782c18, []int{0} +} + +// IP配置信息,用于实现就近访问。L5 server从表t_ip_config读取,由OSS从CMDB查询IP相关信息写入表中 +type Cl5IpcObj struct { + Ip *int32 `protobuf:"varint,1,req,name=ip" json:"ip,omitempty"` + AreaId *int32 `protobuf:"varint,2,req,name=area_id" json:"area_id,omitempty"` + CityId *int32 `protobuf:"varint,3,req,name=city_id" json:"city_id,omitempty"` + IdcId *int32 `protobuf:"varint,4,req,name=idc_id" json:"idc_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Cl5IpcObj) Reset() { *m = Cl5IpcObj{} } +func (m *Cl5IpcObj) String() string { return proto.CompactTextString(m) } +func (*Cl5IpcObj) ProtoMessage() {} +func (*Cl5IpcObj) Descriptor() ([]byte, []int) { + return fileDescriptor_cl5_5845103484782c18, []int{0} +} +func (m *Cl5IpcObj) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Cl5IpcObj.Unmarshal(m, b) +} +func (m *Cl5IpcObj) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Cl5IpcObj.Marshal(b, m, deterministic) +} +func (dst *Cl5IpcObj) XXX_Merge(src proto.Message) { + xxx_messageInfo_Cl5IpcObj.Merge(dst, src) +} +func (m *Cl5IpcObj) XXX_Size() int { + return xxx_messageInfo_Cl5IpcObj.Size(m) +} +func (m *Cl5IpcObj) XXX_DiscardUnknown() { + xxx_messageInfo_Cl5IpcObj.DiscardUnknown(m) +} + +var xxx_messageInfo_Cl5IpcObj proto.InternalMessageInfo + +func (m *Cl5IpcObj) GetIp() int32 { + if m != nil && m.Ip != nil { + return *m.Ip + } + return 0 +} + +func (m *Cl5IpcObj) GetAreaId() int32 { + if m != nil && m.AreaId != nil { + return *m.AreaId + } + return 0 +} + +func (m *Cl5IpcObj) GetCityId() int32 { + if m != nil && m.CityId != nil { + return *m.CityId + } + return 0 +} + +func (m *Cl5IpcObj) GetIdcId() int32 { + if m != nil && m.IdcId != nil { + return *m.IdcId + } + return 0 +} + +// IP配置信息列表 +type Cl5IpcList struct { + Ipc []*Cl5IpcObj `protobuf:"bytes,1,rep,name=ipc" json:"ipc,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Cl5IpcList) Reset() { *m = Cl5IpcList{} } +func (m *Cl5IpcList) String() string { return proto.CompactTextString(m) } +func (*Cl5IpcList) ProtoMessage() {} +func (*Cl5IpcList) Descriptor() ([]byte, []int) { + return fileDescriptor_cl5_5845103484782c18, []int{1} +} +func (m *Cl5IpcList) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Cl5IpcList.Unmarshal(m, b) +} +func (m *Cl5IpcList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Cl5IpcList.Marshal(b, m, deterministic) +} +func (dst *Cl5IpcList) XXX_Merge(src proto.Message) { + xxx_messageInfo_Cl5IpcList.Merge(dst, src) +} +func (m *Cl5IpcList) XXX_Size() int { + return xxx_messageInfo_Cl5IpcList.Size(m) +} +func (m *Cl5IpcList) XXX_DiscardUnknown() { + xxx_messageInfo_Cl5IpcList.DiscardUnknown(m) +} + +var xxx_messageInfo_Cl5IpcList proto.InternalMessageInfo + +func (m *Cl5IpcList) GetIpc() []*Cl5IpcObj { + if m != nil { + return m.Ipc + } + return nil +} + +// Sid属性 +type Cl5SidObj struct { + ModId *int32 `protobuf:"varint,1,req,name=mod_id" json:"mod_id,omitempty"` + CmdId *int32 `protobuf:"varint,2,req,name=cmd_id" json:"cmd_id,omitempty"` + Name *string `protobuf:"bytes,3,opt,name=name" json:"name,omitempty"` + Policy *int32 `protobuf:"varint,4,opt,name=policy" json:"policy,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Cl5SidObj) Reset() { *m = Cl5SidObj{} } +func (m *Cl5SidObj) String() string { return proto.CompactTextString(m) } +func (*Cl5SidObj) ProtoMessage() {} +func (*Cl5SidObj) Descriptor() ([]byte, []int) { + return fileDescriptor_cl5_5845103484782c18, []int{2} +} +func (m *Cl5SidObj) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Cl5SidObj.Unmarshal(m, b) +} +func (m *Cl5SidObj) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Cl5SidObj.Marshal(b, m, deterministic) +} +func (dst *Cl5SidObj) XXX_Merge(src proto.Message) { + xxx_messageInfo_Cl5SidObj.Merge(dst, src) +} +func (m *Cl5SidObj) XXX_Size() int { + return xxx_messageInfo_Cl5SidObj.Size(m) +} +func (m *Cl5SidObj) XXX_DiscardUnknown() { + xxx_messageInfo_Cl5SidObj.DiscardUnknown(m) +} + +var xxx_messageInfo_Cl5SidObj proto.InternalMessageInfo + +func (m *Cl5SidObj) GetModId() int32 { + if m != nil && m.ModId != nil { + return *m.ModId + } + return 0 +} + +func (m *Cl5SidObj) GetCmdId() int32 { + if m != nil && m.CmdId != nil { + return *m.CmdId + } + return 0 +} + +func (m *Cl5SidObj) GetName() string { + if m != nil && m.Name != nil { + return *m.Name + } + return "" +} + +func (m *Cl5SidObj) GetPolicy() int32 { + if m != nil && m.Policy != nil { + return *m.Policy + } + return 0 +} + +// Sid属性列表 +type Cl5SidList struct { + Sid []*Cl5SidObj `protobuf:"bytes,1,rep,name=sid" json:"sid,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Cl5SidList) Reset() { *m = Cl5SidList{} } +func (m *Cl5SidList) String() string { return proto.CompactTextString(m) } +func (*Cl5SidList) ProtoMessage() {} +func (*Cl5SidList) Descriptor() ([]byte, []int) { + return fileDescriptor_cl5_5845103484782c18, []int{3} +} +func (m *Cl5SidList) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Cl5SidList.Unmarshal(m, b) +} +func (m *Cl5SidList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Cl5SidList.Marshal(b, m, deterministic) +} +func (dst *Cl5SidList) XXX_Merge(src proto.Message) { + xxx_messageInfo_Cl5SidList.Merge(dst, src) +} +func (m *Cl5SidList) XXX_Size() int { + return xxx_messageInfo_Cl5SidList.Size(m) +} +func (m *Cl5SidList) XXX_DiscardUnknown() { + xxx_messageInfo_Cl5SidList.DiscardUnknown(m) +} + +var xxx_messageInfo_Cl5SidList proto.InternalMessageInfo + +func (m *Cl5SidList) GetSid() []*Cl5SidObj { + if m != nil { + return m.Sid + } + return nil +} + +// Sid +type Cl5OptObj struct { + ModId *int32 `protobuf:"varint,1,req,name=mod_id" json:"mod_id,omitempty"` + CmdId *int32 `protobuf:"varint,2,req,name=cmd_id" json:"cmd_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Cl5OptObj) Reset() { *m = Cl5OptObj{} } +func (m *Cl5OptObj) String() string { return proto.CompactTextString(m) } +func (*Cl5OptObj) ProtoMessage() {} +func (*Cl5OptObj) Descriptor() ([]byte, []int) { + return fileDescriptor_cl5_5845103484782c18, []int{4} +} +func (m *Cl5OptObj) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Cl5OptObj.Unmarshal(m, b) +} +func (m *Cl5OptObj) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Cl5OptObj.Marshal(b, m, deterministic) +} +func (dst *Cl5OptObj) XXX_Merge(src proto.Message) { + xxx_messageInfo_Cl5OptObj.Merge(dst, src) +} +func (m *Cl5OptObj) XXX_Size() int { + return xxx_messageInfo_Cl5OptObj.Size(m) +} +func (m *Cl5OptObj) XXX_DiscardUnknown() { + xxx_messageInfo_Cl5OptObj.DiscardUnknown(m) +} + +var xxx_messageInfo_Cl5OptObj proto.InternalMessageInfo + +func (m *Cl5OptObj) GetModId() int32 { + if m != nil && m.ModId != nil { + return *m.ModId + } + return 0 +} + +func (m *Cl5OptObj) GetCmdId() int32 { + if m != nil && m.CmdId != nil { + return *m.CmdId + } + return 0 +} + +// Sid列表,用于CL5_SYNC_BY_AGENT_CMD请求包 +type Cl5OptList struct { + Opt []*Cl5OptObj `protobuf:"bytes,1,rep,name=opt" json:"opt,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Cl5OptList) Reset() { *m = Cl5OptList{} } +func (m *Cl5OptList) String() string { return proto.CompactTextString(m) } +func (*Cl5OptList) ProtoMessage() {} +func (*Cl5OptList) Descriptor() ([]byte, []int) { + return fileDescriptor_cl5_5845103484782c18, []int{5} +} +func (m *Cl5OptList) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Cl5OptList.Unmarshal(m, b) +} +func (m *Cl5OptList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Cl5OptList.Marshal(b, m, deterministic) +} +func (dst *Cl5OptList) XXX_Merge(src proto.Message) { + xxx_messageInfo_Cl5OptList.Merge(dst, src) +} +func (m *Cl5OptList) XXX_Size() int { + return xxx_messageInfo_Cl5OptList.Size(m) +} +func (m *Cl5OptList) XXX_DiscardUnknown() { + xxx_messageInfo_Cl5OptList.DiscardUnknown(m) +} + +var xxx_messageInfo_Cl5OptList proto.InternalMessageInfo + +func (m *Cl5OptList) GetOpt() []*Cl5OptObj { + if m != nil { + return m.Opt + } + return nil +} + +// Sid名字列表,用于CL5_REGISTER_BY_NAME_CMD请求包 +type Cl5NameList struct { + Name []string `protobuf:"bytes,1,rep,name=name" json:"name,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Cl5NameList) Reset() { *m = Cl5NameList{} } +func (m *Cl5NameList) String() string { return proto.CompactTextString(m) } +func (*Cl5NameList) ProtoMessage() {} +func (*Cl5NameList) Descriptor() ([]byte, []int) { + return fileDescriptor_cl5_5845103484782c18, []int{6} +} +func (m *Cl5NameList) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Cl5NameList.Unmarshal(m, b) +} +func (m *Cl5NameList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Cl5NameList.Marshal(b, m, deterministic) +} +func (dst *Cl5NameList) XXX_Merge(src proto.Message) { + xxx_messageInfo_Cl5NameList.Merge(dst, src) +} +func (m *Cl5NameList) XXX_Size() int { + return xxx_messageInfo_Cl5NameList.Size(m) +} +func (m *Cl5NameList) XXX_DiscardUnknown() { + xxx_messageInfo_Cl5NameList.DiscardUnknown(m) +} + +var xxx_messageInfo_Cl5NameList proto.InternalMessageInfo + +func (m *Cl5NameList) GetName() []string { + if m != nil { + return m.Name + } + return nil +} + +// l5 server列表,用于agent实现在连接l5 server失败时重试其他l5 server +type Cl5L5SvrList struct { + Ip []int32 `protobuf:"varint,1,rep,name=ip" json:"ip,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Cl5L5SvrList) Reset() { *m = Cl5L5SvrList{} } +func (m *Cl5L5SvrList) String() string { return proto.CompactTextString(m) } +func (*Cl5L5SvrList) ProtoMessage() {} +func (*Cl5L5SvrList) Descriptor() ([]byte, []int) { + return fileDescriptor_cl5_5845103484782c18, []int{7} +} +func (m *Cl5L5SvrList) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Cl5L5SvrList.Unmarshal(m, b) +} +func (m *Cl5L5SvrList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Cl5L5SvrList.Marshal(b, m, deterministic) +} +func (dst *Cl5L5SvrList) XXX_Merge(src proto.Message) { + xxx_messageInfo_Cl5L5SvrList.Merge(dst, src) +} +func (m *Cl5L5SvrList) XXX_Size() int { + return xxx_messageInfo_Cl5L5SvrList.Size(m) +} +func (m *Cl5L5SvrList) XXX_DiscardUnknown() { + xxx_messageInfo_Cl5L5SvrList.DiscardUnknown(m) +} + +var xxx_messageInfo_Cl5L5SvrList proto.InternalMessageInfo + +func (m *Cl5L5SvrList) GetIp() []int32 { + if m != nil { + return m.Ip + } + return nil +} + +// 被调server +type Cl5ServObj struct { + ModId *int32 `protobuf:"varint,1,req,name=mod_id" json:"mod_id,omitempty"` + CmdId *int32 `protobuf:"varint,2,req,name=cmd_id" json:"cmd_id,omitempty"` + Ip *int32 `protobuf:"varint,3,req,name=ip" json:"ip,omitempty"` + Port *int32 `protobuf:"varint,4,req,name=port" json:"port,omitempty"` + Weight *int32 `protobuf:"varint,5,req,name=weight" json:"weight,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Cl5ServObj) Reset() { *m = Cl5ServObj{} } +func (m *Cl5ServObj) String() string { return proto.CompactTextString(m) } +func (*Cl5ServObj) ProtoMessage() {} +func (*Cl5ServObj) Descriptor() ([]byte, []int) { + return fileDescriptor_cl5_5845103484782c18, []int{8} +} +func (m *Cl5ServObj) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Cl5ServObj.Unmarshal(m, b) +} +func (m *Cl5ServObj) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Cl5ServObj.Marshal(b, m, deterministic) +} +func (dst *Cl5ServObj) XXX_Merge(src proto.Message) { + xxx_messageInfo_Cl5ServObj.Merge(dst, src) +} +func (m *Cl5ServObj) XXX_Size() int { + return xxx_messageInfo_Cl5ServObj.Size(m) +} +func (m *Cl5ServObj) XXX_DiscardUnknown() { + xxx_messageInfo_Cl5ServObj.DiscardUnknown(m) +} + +var xxx_messageInfo_Cl5ServObj proto.InternalMessageInfo + +func (m *Cl5ServObj) GetModId() int32 { + if m != nil && m.ModId != nil { + return *m.ModId + } + return 0 +} + +func (m *Cl5ServObj) GetCmdId() int32 { + if m != nil && m.CmdId != nil { + return *m.CmdId + } + return 0 +} + +func (m *Cl5ServObj) GetIp() int32 { + if m != nil && m.Ip != nil { + return *m.Ip + } + return 0 +} + +func (m *Cl5ServObj) GetPort() int32 { + if m != nil && m.Port != nil { + return *m.Port + } + return 0 +} + +func (m *Cl5ServObj) GetWeight() int32 { + if m != nil && m.Weight != nil { + return *m.Weight + } + return 0 +} + +// 被调server列表 +type Cl5ServList struct { + Serv []*Cl5ServObj `protobuf:"bytes,1,rep,name=serv" json:"serv,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Cl5ServList) Reset() { *m = Cl5ServList{} } +func (m *Cl5ServList) String() string { return proto.CompactTextString(m) } +func (*Cl5ServList) ProtoMessage() {} +func (*Cl5ServList) Descriptor() ([]byte, []int) { + return fileDescriptor_cl5_5845103484782c18, []int{9} +} +func (m *Cl5ServList) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Cl5ServList.Unmarshal(m, b) +} +func (m *Cl5ServList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Cl5ServList.Marshal(b, m, deterministic) +} +func (dst *Cl5ServList) XXX_Merge(src proto.Message) { + xxx_messageInfo_Cl5ServList.Merge(dst, src) +} +func (m *Cl5ServList) XXX_Size() int { + return xxx_messageInfo_Cl5ServList.Size(m) +} +func (m *Cl5ServList) XXX_DiscardUnknown() { + xxx_messageInfo_Cl5ServList.DiscardUnknown(m) +} + +var xxx_messageInfo_Cl5ServList proto.InternalMessageInfo + +func (m *Cl5ServList) GetServ() []*Cl5ServObj { + if m != nil { + return m.Serv + } + return nil +} + +// 有状态规则配置 +type Cl5PolyObj struct { + ModId *int32 `protobuf:"varint,1,req,name=mod_id" json:"mod_id,omitempty"` + Div *int32 `protobuf:"varint,2,req,name=div" json:"div,omitempty"` + Mod *int32 `protobuf:"varint,3,req,name=mod" json:"mod,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Cl5PolyObj) Reset() { *m = Cl5PolyObj{} } +func (m *Cl5PolyObj) String() string { return proto.CompactTextString(m) } +func (*Cl5PolyObj) ProtoMessage() {} +func (*Cl5PolyObj) Descriptor() ([]byte, []int) { + return fileDescriptor_cl5_5845103484782c18, []int{10} +} +func (m *Cl5PolyObj) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Cl5PolyObj.Unmarshal(m, b) +} +func (m *Cl5PolyObj) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Cl5PolyObj.Marshal(b, m, deterministic) +} +func (dst *Cl5PolyObj) XXX_Merge(src proto.Message) { + xxx_messageInfo_Cl5PolyObj.Merge(dst, src) +} +func (m *Cl5PolyObj) XXX_Size() int { + return xxx_messageInfo_Cl5PolyObj.Size(m) +} +func (m *Cl5PolyObj) XXX_DiscardUnknown() { + xxx_messageInfo_Cl5PolyObj.DiscardUnknown(m) +} + +var xxx_messageInfo_Cl5PolyObj proto.InternalMessageInfo + +func (m *Cl5PolyObj) GetModId() int32 { + if m != nil && m.ModId != nil { + return *m.ModId + } + return 0 +} + +func (m *Cl5PolyObj) GetDiv() int32 { + if m != nil && m.Div != nil { + return *m.Div + } + return 0 +} + +func (m *Cl5PolyObj) GetMod() int32 { + if m != nil && m.Mod != nil { + return *m.Mod + } + return 0 +} + +// 有状态规则分段信息 +type Cl5SectObj struct { + ModId *int32 `protobuf:"varint,1,req,name=mod_id" json:"mod_id,omitempty"` + From *int32 `protobuf:"varint,2,req,name=from" json:"from,omitempty"` + To *int32 `protobuf:"varint,3,req,name=to" json:"to,omitempty"` + CmdId *int32 `protobuf:"varint,4,req,name=cmd_id" json:"cmd_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Cl5SectObj) Reset() { *m = Cl5SectObj{} } +func (m *Cl5SectObj) String() string { return proto.CompactTextString(m) } +func (*Cl5SectObj) ProtoMessage() {} +func (*Cl5SectObj) Descriptor() ([]byte, []int) { + return fileDescriptor_cl5_5845103484782c18, []int{11} +} +func (m *Cl5SectObj) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Cl5SectObj.Unmarshal(m, b) +} +func (m *Cl5SectObj) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Cl5SectObj.Marshal(b, m, deterministic) +} +func (dst *Cl5SectObj) XXX_Merge(src proto.Message) { + xxx_messageInfo_Cl5SectObj.Merge(dst, src) +} +func (m *Cl5SectObj) XXX_Size() int { + return xxx_messageInfo_Cl5SectObj.Size(m) +} +func (m *Cl5SectObj) XXX_DiscardUnknown() { + xxx_messageInfo_Cl5SectObj.DiscardUnknown(m) +} + +var xxx_messageInfo_Cl5SectObj proto.InternalMessageInfo + +func (m *Cl5SectObj) GetModId() int32 { + if m != nil && m.ModId != nil { + return *m.ModId + } + return 0 +} + +func (m *Cl5SectObj) GetFrom() int32 { + if m != nil && m.From != nil { + return *m.From + } + return 0 +} + +func (m *Cl5SectObj) GetTo() int32 { + if m != nil && m.To != nil { + return *m.To + } + return 0 +} + +func (m *Cl5SectObj) GetCmdId() int32 { + if m != nil && m.CmdId != nil { + return *m.CmdId + } + return 0 +} + +// 有状态规则列表 +type Cl5RuleList struct { + Poly []*Cl5PolyObj `protobuf:"bytes,1,rep,name=poly" json:"poly,omitempty"` + Sect []*Cl5SectObj `protobuf:"bytes,2,rep,name=sect" json:"sect,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Cl5RuleList) Reset() { *m = Cl5RuleList{} } +func (m *Cl5RuleList) String() string { return proto.CompactTextString(m) } +func (*Cl5RuleList) ProtoMessage() {} +func (*Cl5RuleList) Descriptor() ([]byte, []int) { + return fileDescriptor_cl5_5845103484782c18, []int{12} +} +func (m *Cl5RuleList) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Cl5RuleList.Unmarshal(m, b) +} +func (m *Cl5RuleList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Cl5RuleList.Marshal(b, m, deterministic) +} +func (dst *Cl5RuleList) XXX_Merge(src proto.Message) { + xxx_messageInfo_Cl5RuleList.Merge(dst, src) +} +func (m *Cl5RuleList) XXX_Size() int { + return xxx_messageInfo_Cl5RuleList.Size(m) +} +func (m *Cl5RuleList) XXX_DiscardUnknown() { + xxx_messageInfo_Cl5RuleList.DiscardUnknown(m) +} + +var xxx_messageInfo_Cl5RuleList proto.InternalMessageInfo + +func (m *Cl5RuleList) GetPoly() []*Cl5PolyObj { + if m != nil { + return m.Poly + } + return nil +} + +func (m *Cl5RuleList) GetSect() []*Cl5SectObj { + if m != nil { + return m.Sect + } + return nil +} + +// CL5_SYNC_BY_AGENT_CMD请求包 +type Cl5SyncByAgentCmd struct { + AgentIp *int32 `protobuf:"varint,1,req,name=agent_ip" json:"agent_ip,omitempty"` + SyncFlow *int32 `protobuf:"varint,2,req,name=sync_flow" json:"sync_flow,omitempty"` + OptList *Cl5OptList `protobuf:"bytes,3,opt,name=opt_list" json:"opt_list,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Cl5SyncByAgentCmd) Reset() { *m = Cl5SyncByAgentCmd{} } +func (m *Cl5SyncByAgentCmd) String() string { return proto.CompactTextString(m) } +func (*Cl5SyncByAgentCmd) ProtoMessage() {} +func (*Cl5SyncByAgentCmd) Descriptor() ([]byte, []int) { + return fileDescriptor_cl5_5845103484782c18, []int{13} +} +func (m *Cl5SyncByAgentCmd) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Cl5SyncByAgentCmd.Unmarshal(m, b) +} +func (m *Cl5SyncByAgentCmd) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Cl5SyncByAgentCmd.Marshal(b, m, deterministic) +} +func (dst *Cl5SyncByAgentCmd) XXX_Merge(src proto.Message) { + xxx_messageInfo_Cl5SyncByAgentCmd.Merge(dst, src) +} +func (m *Cl5SyncByAgentCmd) XXX_Size() int { + return xxx_messageInfo_Cl5SyncByAgentCmd.Size(m) +} +func (m *Cl5SyncByAgentCmd) XXX_DiscardUnknown() { + xxx_messageInfo_Cl5SyncByAgentCmd.DiscardUnknown(m) +} + +var xxx_messageInfo_Cl5SyncByAgentCmd proto.InternalMessageInfo + +func (m *Cl5SyncByAgentCmd) GetAgentIp() int32 { + if m != nil && m.AgentIp != nil { + return *m.AgentIp + } + return 0 +} + +func (m *Cl5SyncByAgentCmd) GetSyncFlow() int32 { + if m != nil && m.SyncFlow != nil { + return *m.SyncFlow + } + return 0 +} + +func (m *Cl5SyncByAgentCmd) GetOptList() *Cl5OptList { + if m != nil { + return m.OptList + } + return nil +} + +// CL5_SYNC_BY_AGENT_CMD应答包 +type Cl5SyncByAgentAckCmd struct { + AgentIp *int32 `protobuf:"varint,1,req,name=agent_ip" json:"agent_ip,omitempty"` + SyncFlow *int32 `protobuf:"varint,2,req,name=sync_flow" json:"sync_flow,omitempty"` + ServList *Cl5ServList `protobuf:"bytes,3,opt,name=serv_list" json:"serv_list,omitempty"` + SidList *Cl5SidList `protobuf:"bytes,4,opt,name=sid_list" json:"sid_list,omitempty"` + IpcList *Cl5IpcList `protobuf:"bytes,5,opt,name=ipc_list" json:"ipc_list,omitempty"` + RuleList *Cl5RuleList `protobuf:"bytes,6,opt,name=rule_list" json:"rule_list,omitempty"` + L5SvrList *Cl5L5SvrList `protobuf:"bytes,7,opt,name=l5svr_list" json:"l5svr_list,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Cl5SyncByAgentAckCmd) Reset() { *m = Cl5SyncByAgentAckCmd{} } +func (m *Cl5SyncByAgentAckCmd) String() string { return proto.CompactTextString(m) } +func (*Cl5SyncByAgentAckCmd) ProtoMessage() {} +func (*Cl5SyncByAgentAckCmd) Descriptor() ([]byte, []int) { + return fileDescriptor_cl5_5845103484782c18, []int{14} +} +func (m *Cl5SyncByAgentAckCmd) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Cl5SyncByAgentAckCmd.Unmarshal(m, b) +} +func (m *Cl5SyncByAgentAckCmd) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Cl5SyncByAgentAckCmd.Marshal(b, m, deterministic) +} +func (dst *Cl5SyncByAgentAckCmd) XXX_Merge(src proto.Message) { + xxx_messageInfo_Cl5SyncByAgentAckCmd.Merge(dst, src) +} +func (m *Cl5SyncByAgentAckCmd) XXX_Size() int { + return xxx_messageInfo_Cl5SyncByAgentAckCmd.Size(m) +} +func (m *Cl5SyncByAgentAckCmd) XXX_DiscardUnknown() { + xxx_messageInfo_Cl5SyncByAgentAckCmd.DiscardUnknown(m) +} + +var xxx_messageInfo_Cl5SyncByAgentAckCmd proto.InternalMessageInfo + +func (m *Cl5SyncByAgentAckCmd) GetAgentIp() int32 { + if m != nil && m.AgentIp != nil { + return *m.AgentIp + } + return 0 +} + +func (m *Cl5SyncByAgentAckCmd) GetSyncFlow() int32 { + if m != nil && m.SyncFlow != nil { + return *m.SyncFlow + } + return 0 +} + +func (m *Cl5SyncByAgentAckCmd) GetServList() *Cl5ServList { + if m != nil { + return m.ServList + } + return nil +} + +func (m *Cl5SyncByAgentAckCmd) GetSidList() *Cl5SidList { + if m != nil { + return m.SidList + } + return nil +} + +func (m *Cl5SyncByAgentAckCmd) GetIpcList() *Cl5IpcList { + if m != nil { + return m.IpcList + } + return nil +} + +func (m *Cl5SyncByAgentAckCmd) GetRuleList() *Cl5RuleList { + if m != nil { + return m.RuleList + } + return nil +} + +func (m *Cl5SyncByAgentAckCmd) GetL5SvrList() *Cl5L5SvrList { + if m != nil { + return m.L5SvrList + } + return nil +} + +// 已经废弃 +type Cl5RegisterByIdCmd struct { + CallerIp *int32 `protobuf:"varint,1,req,name=caller_ip" json:"caller_ip,omitempty"` + OptList *Cl5OptList `protobuf:"bytes,2,opt,name=opt_list" json:"opt_list,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Cl5RegisterByIdCmd) Reset() { *m = Cl5RegisterByIdCmd{} } +func (m *Cl5RegisterByIdCmd) String() string { return proto.CompactTextString(m) } +func (*Cl5RegisterByIdCmd) ProtoMessage() {} +func (*Cl5RegisterByIdCmd) Descriptor() ([]byte, []int) { + return fileDescriptor_cl5_5845103484782c18, []int{15} +} +func (m *Cl5RegisterByIdCmd) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Cl5RegisterByIdCmd.Unmarshal(m, b) +} +func (m *Cl5RegisterByIdCmd) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Cl5RegisterByIdCmd.Marshal(b, m, deterministic) +} +func (dst *Cl5RegisterByIdCmd) XXX_Merge(src proto.Message) { + xxx_messageInfo_Cl5RegisterByIdCmd.Merge(dst, src) +} +func (m *Cl5RegisterByIdCmd) XXX_Size() int { + return xxx_messageInfo_Cl5RegisterByIdCmd.Size(m) +} +func (m *Cl5RegisterByIdCmd) XXX_DiscardUnknown() { + xxx_messageInfo_Cl5RegisterByIdCmd.DiscardUnknown(m) +} + +var xxx_messageInfo_Cl5RegisterByIdCmd proto.InternalMessageInfo + +func (m *Cl5RegisterByIdCmd) GetCallerIp() int32 { + if m != nil && m.CallerIp != nil { + return *m.CallerIp + } + return 0 +} + +func (m *Cl5RegisterByIdCmd) GetOptList() *Cl5OptList { + if m != nil { + return m.OptList + } + return nil +} + +// 已经废弃 +type Cl5RegisterByIdAckCmd struct { + CallerIp *int32 `protobuf:"varint,1,req,name=caller_ip" json:"caller_ip,omitempty"` + ServList *Cl5ServList `protobuf:"bytes,2,opt,name=serv_list" json:"serv_list,omitempty"` + SidList *Cl5SidList `protobuf:"bytes,3,opt,name=sid_list" json:"sid_list,omitempty"` + IpcList *Cl5IpcList `protobuf:"bytes,4,opt,name=ipc_list" json:"ipc_list,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Cl5RegisterByIdAckCmd) Reset() { *m = Cl5RegisterByIdAckCmd{} } +func (m *Cl5RegisterByIdAckCmd) String() string { return proto.CompactTextString(m) } +func (*Cl5RegisterByIdAckCmd) ProtoMessage() {} +func (*Cl5RegisterByIdAckCmd) Descriptor() ([]byte, []int) { + return fileDescriptor_cl5_5845103484782c18, []int{16} +} +func (m *Cl5RegisterByIdAckCmd) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Cl5RegisterByIdAckCmd.Unmarshal(m, b) +} +func (m *Cl5RegisterByIdAckCmd) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Cl5RegisterByIdAckCmd.Marshal(b, m, deterministic) +} +func (dst *Cl5RegisterByIdAckCmd) XXX_Merge(src proto.Message) { + xxx_messageInfo_Cl5RegisterByIdAckCmd.Merge(dst, src) +} +func (m *Cl5RegisterByIdAckCmd) XXX_Size() int { + return xxx_messageInfo_Cl5RegisterByIdAckCmd.Size(m) +} +func (m *Cl5RegisterByIdAckCmd) XXX_DiscardUnknown() { + xxx_messageInfo_Cl5RegisterByIdAckCmd.DiscardUnknown(m) +} + +var xxx_messageInfo_Cl5RegisterByIdAckCmd proto.InternalMessageInfo + +func (m *Cl5RegisterByIdAckCmd) GetCallerIp() int32 { + if m != nil && m.CallerIp != nil { + return *m.CallerIp + } + return 0 +} + +func (m *Cl5RegisterByIdAckCmd) GetServList() *Cl5ServList { + if m != nil { + return m.ServList + } + return nil +} + +func (m *Cl5RegisterByIdAckCmd) GetSidList() *Cl5SidList { + if m != nil { + return m.SidList + } + return nil +} + +func (m *Cl5RegisterByIdAckCmd) GetIpcList() *Cl5IpcList { + if m != nil { + return m.IpcList + } + return nil +} + +// CL5_REGISTER_BY_NAME_CMD 请求包 +type Cl5RegisterByNameCmd struct { + CallerIp *int32 `protobuf:"varint,1,req,name=caller_ip" json:"caller_ip,omitempty"` + NameList *Cl5NameList `protobuf:"bytes,2,opt,name=name_list" json:"name_list,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Cl5RegisterByNameCmd) Reset() { *m = Cl5RegisterByNameCmd{} } +func (m *Cl5RegisterByNameCmd) String() string { return proto.CompactTextString(m) } +func (*Cl5RegisterByNameCmd) ProtoMessage() {} +func (*Cl5RegisterByNameCmd) Descriptor() ([]byte, []int) { + return fileDescriptor_cl5_5845103484782c18, []int{17} +} +func (m *Cl5RegisterByNameCmd) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Cl5RegisterByNameCmd.Unmarshal(m, b) +} +func (m *Cl5RegisterByNameCmd) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Cl5RegisterByNameCmd.Marshal(b, m, deterministic) +} +func (dst *Cl5RegisterByNameCmd) XXX_Merge(src proto.Message) { + xxx_messageInfo_Cl5RegisterByNameCmd.Merge(dst, src) +} +func (m *Cl5RegisterByNameCmd) XXX_Size() int { + return xxx_messageInfo_Cl5RegisterByNameCmd.Size(m) +} +func (m *Cl5RegisterByNameCmd) XXX_DiscardUnknown() { + xxx_messageInfo_Cl5RegisterByNameCmd.DiscardUnknown(m) +} + +var xxx_messageInfo_Cl5RegisterByNameCmd proto.InternalMessageInfo + +func (m *Cl5RegisterByNameCmd) GetCallerIp() int32 { + if m != nil && m.CallerIp != nil { + return *m.CallerIp + } + return 0 +} + +func (m *Cl5RegisterByNameCmd) GetNameList() *Cl5NameList { + if m != nil { + return m.NameList + } + return nil +} + +// CL5_REGISTER_BY_NAME_ACK_CMD 应答包 +type Cl5RegisterByNameAckCmd struct { + CallerIp *int32 `protobuf:"varint,1,req,name=caller_ip" json:"caller_ip,omitempty"` + ServList *Cl5ServList `protobuf:"bytes,2,opt,name=serv_list" json:"serv_list,omitempty"` + SidList *Cl5SidList `protobuf:"bytes,3,opt,name=sid_list" json:"sid_list,omitempty"` + IpcList *Cl5IpcList `protobuf:"bytes,4,opt,name=ipc_list" json:"ipc_list,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Cl5RegisterByNameAckCmd) Reset() { *m = Cl5RegisterByNameAckCmd{} } +func (m *Cl5RegisterByNameAckCmd) String() string { return proto.CompactTextString(m) } +func (*Cl5RegisterByNameAckCmd) ProtoMessage() {} +func (*Cl5RegisterByNameAckCmd) Descriptor() ([]byte, []int) { + return fileDescriptor_cl5_5845103484782c18, []int{18} +} +func (m *Cl5RegisterByNameAckCmd) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Cl5RegisterByNameAckCmd.Unmarshal(m, b) +} +func (m *Cl5RegisterByNameAckCmd) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Cl5RegisterByNameAckCmd.Marshal(b, m, deterministic) +} +func (dst *Cl5RegisterByNameAckCmd) XXX_Merge(src proto.Message) { + xxx_messageInfo_Cl5RegisterByNameAckCmd.Merge(dst, src) +} +func (m *Cl5RegisterByNameAckCmd) XXX_Size() int { + return xxx_messageInfo_Cl5RegisterByNameAckCmd.Size(m) +} +func (m *Cl5RegisterByNameAckCmd) XXX_DiscardUnknown() { + xxx_messageInfo_Cl5RegisterByNameAckCmd.DiscardUnknown(m) +} + +var xxx_messageInfo_Cl5RegisterByNameAckCmd proto.InternalMessageInfo + +func (m *Cl5RegisterByNameAckCmd) GetCallerIp() int32 { + if m != nil && m.CallerIp != nil { + return *m.CallerIp + } + return 0 +} + +func (m *Cl5RegisterByNameAckCmd) GetServList() *Cl5ServList { + if m != nil { + return m.ServList + } + return nil +} + +func (m *Cl5RegisterByNameAckCmd) GetSidList() *Cl5SidList { + if m != nil { + return m.SidList + } + return nil +} + +func (m *Cl5RegisterByNameAckCmd) GetIpcList() *Cl5IpcList { + if m != nil { + return m.IpcList + } + return nil +} + +// 包格式 +type Cl5Pkg struct { + Seqno *int32 `protobuf:"varint,1,req,name=seqno" json:"seqno,omitempty"` + Cmd *int32 `protobuf:"varint,2,req,name=cmd" json:"cmd,omitempty"` + Result *int32 `protobuf:"varint,3,opt,name=result" json:"result,omitempty"` + Error *string `protobuf:"bytes,4,opt,name=error" json:"error,omitempty"` + Ip *int32 `protobuf:"varint,5,opt,name=ip" json:"ip,omitempty"` + SyncByAgentCmd *Cl5SyncByAgentCmd `protobuf:"bytes,10,opt,name=sync_by_agent_cmd" json:"sync_by_agent_cmd,omitempty"` + SyncByAgentAckCmd *Cl5SyncByAgentAckCmd `protobuf:"bytes,11,opt,name=sync_by_agent_ack_cmd" json:"sync_by_agent_ack_cmd,omitempty"` + RegisterByIdCmd *Cl5RegisterByIdCmd `protobuf:"bytes,12,opt,name=register_by_id_cmd" json:"register_by_id_cmd,omitempty"` + RegisterByIdAckCmd *Cl5RegisterByIdAckCmd `protobuf:"bytes,13,opt,name=register_by_id_ack_cmd" json:"register_by_id_ack_cmd,omitempty"` + RegisterByNameCmd *Cl5RegisterByNameCmd `protobuf:"bytes,14,opt,name=register_by_name_cmd" json:"register_by_name_cmd,omitempty"` + RegisterByNameAckCmd *Cl5RegisterByNameAckCmd `protobuf:"bytes,15,opt,name=register_by_name_ack_cmd" json:"register_by_name_ack_cmd,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Cl5Pkg) Reset() { *m = Cl5Pkg{} } +func (m *Cl5Pkg) String() string { return proto.CompactTextString(m) } +func (*Cl5Pkg) ProtoMessage() {} +func (*Cl5Pkg) Descriptor() ([]byte, []int) { + return fileDescriptor_cl5_5845103484782c18, []int{19} +} +func (m *Cl5Pkg) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Cl5Pkg.Unmarshal(m, b) +} +func (m *Cl5Pkg) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Cl5Pkg.Marshal(b, m, deterministic) +} +func (dst *Cl5Pkg) XXX_Merge(src proto.Message) { + xxx_messageInfo_Cl5Pkg.Merge(dst, src) +} +func (m *Cl5Pkg) XXX_Size() int { + return xxx_messageInfo_Cl5Pkg.Size(m) +} +func (m *Cl5Pkg) XXX_DiscardUnknown() { + xxx_messageInfo_Cl5Pkg.DiscardUnknown(m) +} + +var xxx_messageInfo_Cl5Pkg proto.InternalMessageInfo + +func (m *Cl5Pkg) GetSeqno() int32 { + if m != nil && m.Seqno != nil { + return *m.Seqno + } + return 0 +} + +func (m *Cl5Pkg) GetCmd() int32 { + if m != nil && m.Cmd != nil { + return *m.Cmd + } + return 0 +} + +func (m *Cl5Pkg) GetResult() int32 { + if m != nil && m.Result != nil { + return *m.Result + } + return 0 +} + +func (m *Cl5Pkg) GetError() string { + if m != nil && m.Error != nil { + return *m.Error + } + return "" +} + +func (m *Cl5Pkg) GetIp() int32 { + if m != nil && m.Ip != nil { + return *m.Ip + } + return 0 +} + +func (m *Cl5Pkg) GetSyncByAgentCmd() *Cl5SyncByAgentCmd { + if m != nil { + return m.SyncByAgentCmd + } + return nil +} + +func (m *Cl5Pkg) GetSyncByAgentAckCmd() *Cl5SyncByAgentAckCmd { + if m != nil { + return m.SyncByAgentAckCmd + } + return nil +} + +func (m *Cl5Pkg) GetRegisterByIdCmd() *Cl5RegisterByIdCmd { + if m != nil { + return m.RegisterByIdCmd + } + return nil +} + +func (m *Cl5Pkg) GetRegisterByIdAckCmd() *Cl5RegisterByIdAckCmd { + if m != nil { + return m.RegisterByIdAckCmd + } + return nil +} + +func (m *Cl5Pkg) GetRegisterByNameCmd() *Cl5RegisterByNameCmd { + if m != nil { + return m.RegisterByNameCmd + } + return nil +} + +func (m *Cl5Pkg) GetRegisterByNameAckCmd() *Cl5RegisterByNameAckCmd { + if m != nil { + return m.RegisterByNameAckCmd + } + return nil +} + +func init() { + proto.RegisterType((*Cl5IpcObj)(nil), "l5.cl5_ipc_obj") + proto.RegisterType((*Cl5IpcList)(nil), "l5.cl5_ipc_list") + proto.RegisterType((*Cl5SidObj)(nil), "l5.cl5_sid_obj") + proto.RegisterType((*Cl5SidList)(nil), "l5.cl5_sid_list") + proto.RegisterType((*Cl5OptObj)(nil), "l5.cl5_opt_obj") + proto.RegisterType((*Cl5OptList)(nil), "l5.cl5_opt_list") + proto.RegisterType((*Cl5NameList)(nil), "l5.cl5_name_list") + proto.RegisterType((*Cl5L5SvrList)(nil), "l5.cl5_l5svr_list") + proto.RegisterType((*Cl5ServObj)(nil), "l5.cl5_serv_obj") + proto.RegisterType((*Cl5ServList)(nil), "l5.cl5_serv_list") + proto.RegisterType((*Cl5PolyObj)(nil), "l5.cl5_poly_obj") + proto.RegisterType((*Cl5SectObj)(nil), "l5.cl5_sect_obj") + proto.RegisterType((*Cl5RuleList)(nil), "l5.cl5_rule_list") + proto.RegisterType((*Cl5SyncByAgentCmd)(nil), "l5.cl5_sync_by_agent_cmd") + proto.RegisterType((*Cl5SyncByAgentAckCmd)(nil), "l5.cl5_sync_by_agent_ack_cmd") + proto.RegisterType((*Cl5RegisterByIdCmd)(nil), "l5.cl5_register_by_id_cmd") + proto.RegisterType((*Cl5RegisterByIdAckCmd)(nil), "l5.cl5_register_by_id_ack_cmd") + proto.RegisterType((*Cl5RegisterByNameCmd)(nil), "l5.cl5_register_by_name_cmd") + proto.RegisterType((*Cl5RegisterByNameAckCmd)(nil), "l5.cl5_register_by_name_ack_cmd") + proto.RegisterType((*Cl5Pkg)(nil), "l5.cl5_pkg") + proto.RegisterEnum("l5.CL5_CMD", CL5_CMD_name, CL5_CMD_value) +} + +func init() { proto.RegisterFile("cl5.proto", fileDescriptor_cl5_5845103484782c18) } + +var fileDescriptor_cl5_5845103484782c18 = []byte{ + // 771 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x54, 0x4d, 0x6b, 0xdb, 0x40, + 0x10, 0x45, 0x96, 0xbf, 0x34, 0x76, 0x9c, 0x58, 0x34, 0x61, 0x6d, 0x9c, 0x60, 0x96, 0x50, 0x4c, + 0x69, 0x53, 0x28, 0x75, 0x29, 0xa5, 0x14, 0x12, 0xc7, 0xa4, 0x21, 0x4d, 0x02, 0x49, 0x0e, 0xcd, + 0x49, 0x75, 0x56, 0x8a, 0xb3, 0x8d, 0xec, 0x55, 0x25, 0x25, 0xc1, 0x3f, 0xa6, 0xf4, 0x8f, 0x14, + 0xfa, 0xc7, 0x7a, 0x28, 0x3b, 0xd2, 0x4a, 0xfe, 0x90, 0xc1, 0xc7, 0xde, 0xb4, 0xfb, 0x66, 0xe6, + 0xbd, 0x79, 0x9a, 0x59, 0x30, 0x98, 0xdb, 0xdd, 0xf3, 0x7c, 0x11, 0x0a, 0x33, 0xe7, 0x76, 0xe9, + 0x09, 0x54, 0x98, 0xdb, 0xb5, 0xb8, 0xc7, 0x2c, 0x71, 0xf3, 0xdd, 0x04, 0xc8, 0x71, 0x8f, 0x68, + 0xed, 0x5c, 0xa7, 0x60, 0xae, 0x43, 0x69, 0xe0, 0x3b, 0x03, 0x8b, 0xdb, 0x24, 0xa7, 0x2e, 0x18, + 0x0f, 0x27, 0xf2, 0x42, 0xc7, 0x8b, 0x1a, 0x14, 0xb9, 0xcd, 0xe4, 0x39, 0x2f, 0xcf, 0xf4, 0x25, + 0x54, 0x55, 0x31, 0x97, 0x07, 0xa1, 0xd9, 0x02, 0x9d, 0x7b, 0x8c, 0x68, 0x6d, 0xbd, 0x53, 0x79, + 0xb3, 0xbe, 0xe7, 0x76, 0xf7, 0xa6, 0xb8, 0x14, 0x75, 0xc0, 0x6d, 0xa4, 0xae, 0x41, 0x71, 0x24, + 0x6c, 0x59, 0x4c, 0x53, 0xc5, 0xd9, 0xc8, 0x4e, 0xd9, 0xab, 0x90, 0x1f, 0x0f, 0x46, 0x0e, 0xd1, + 0xdb, 0x5a, 0xc7, 0x90, 0xa8, 0x27, 0x5c, 0xce, 0x26, 0x24, 0xdf, 0xd6, 0x52, 0x6a, 0x59, 0x4c, + 0x51, 0x07, 0x58, 0x6a, 0x86, 0x3a, 0xe6, 0xa2, 0xaf, 0x22, 0x6a, 0xe1, 0x85, 0xab, 0x50, 0xab, + 0xe2, 0x32, 0x5c, 0x15, 0x17, 0x5e, 0x38, 0x5f, 0x3c, 0xae, 0x46, 0xb7, 0x61, 0x4d, 0x1e, 0xa5, + 0xd8, 0x28, 0x5c, 0x29, 0x97, 0xf1, 0x06, 0x6d, 0x41, 0x4d, 0xc2, 0x6e, 0x37, 0x78, 0xf4, 0x23, + 0x5c, 0x99, 0xae, 0x77, 0x0a, 0xf4, 0x6b, 0xdc, 0x87, 0xe3, 0x3f, 0xae, 0xe4, 0x4a, 0x94, 0xab, + 0x2b, 0x87, 0x3c, 0xe1, 0x87, 0xd1, 0xcf, 0x90, 0x91, 0x4f, 0x0e, 0x1f, 0xde, 0x85, 0xa4, 0x80, + 0x4d, 0xbc, 0x8e, 0x64, 0x61, 0x65, 0xa4, 0xdd, 0x81, 0xbc, 0x3c, 0xc4, 0x6d, 0x6c, 0x24, 0x1e, + 0xc5, 0xd4, 0xf4, 0x7d, 0x24, 0xc5, 0x13, 0xee, 0x24, 0x53, 0x4a, 0x05, 0x74, 0x9b, 0x3f, 0xc6, + 0x3a, 0x2a, 0xa0, 0x8f, 0x44, 0x3c, 0x17, 0xf4, 0xb3, 0x6a, 0x82, 0x65, 0xfb, 0x5b, 0x85, 0xfc, + 0xad, 0x2f, 0x46, 0x69, 0x0b, 0xa1, 0x48, 0x27, 0x2a, 0x6e, 0x2f, 0x9a, 0xa8, 0xf3, 0x48, 0xb4, + 0xff, 0xe0, 0x3a, 0x89, 0x68, 0x29, 0x68, 0x5e, 0x74, 0x22, 0x12, 0x9b, 0x62, 0x21, 0xc9, 0xcd, + 0x37, 0x15, 0x49, 0xa1, 0xdf, 0x60, 0x13, 0xcf, 0x93, 0x31, 0xb3, 0x6e, 0x26, 0xd6, 0x60, 0xe8, + 0x8c, 0x43, 0x8b, 0x8d, 0x6c, 0x73, 0x03, 0xca, 0xd1, 0x21, 0x99, 0xff, 0x3a, 0x18, 0x18, 0x76, + 0xeb, 0x8a, 0xa7, 0x58, 0x2a, 0x85, 0xb2, 0x1a, 0x02, 0x9c, 0xc3, 0x29, 0x06, 0x75, 0x4f, 0xff, + 0x6a, 0xd0, 0x58, 0xa4, 0x18, 0xb0, 0xfb, 0xd5, 0x69, 0x76, 0xc1, 0x48, 0x7e, 0x53, 0xcc, 0x53, + 0x9f, 0xf9, 0x3d, 0x68, 0x05, 0x85, 0xb2, 0x1a, 0x77, 0x5c, 0x82, 0xe9, 0x76, 0xd5, 0x1a, 0x50, + 0x28, 0xab, 0x6d, 0x24, 0x85, 0xd9, 0x98, 0x64, 0x4b, 0x77, 0xc1, 0x48, 0xfc, 0x25, 0xc5, 0x59, + 0xb6, 0xd4, 0xf8, 0xe7, 0x00, 0xe9, 0xc8, 0x92, 0x12, 0x86, 0x99, 0x2a, 0x2c, 0x45, 0xe8, 0x39, + 0x6c, 0x61, 0xa2, 0x33, 0xe4, 0x41, 0xe8, 0xf8, 0xd2, 0x01, 0x6e, 0x63, 0xeb, 0x75, 0x30, 0xd8, + 0xc0, 0x75, 0x1d, 0x3f, 0xed, 0x7d, 0xda, 0xcf, 0xdc, 0x12, 0x3f, 0x7f, 0x6a, 0xd0, 0xcc, 0xa8, + 0xa8, 0x0c, 0xcd, 0xa8, 0x3a, 0x63, 0x5f, 0x6e, 0x15, 0xfb, 0xf4, 0x15, 0xec, 0xcb, 0x67, 0xdb, + 0x47, 0x2f, 0x81, 0xcc, 0xcb, 0xc3, 0xd5, 0x5f, 0x2e, 0x2e, 0x79, 0x19, 0xe6, 0xc5, 0x25, 0x00, + 0xfd, 0xa5, 0x41, 0x2b, 0xb3, 0xea, 0x7f, 0xd3, 0xf6, 0x6f, 0x1d, 0x4a, 0xb8, 0x79, 0xf7, 0x43, + 0x73, 0x0d, 0x0a, 0x81, 0xf3, 0x63, 0x2c, 0xd2, 0x87, 0x81, 0x8d, 0xd4, 0x03, 0x55, 0x83, 0xa2, + 0xef, 0x04, 0x0f, 0x6e, 0xc4, 0x56, 0x90, 0xb1, 0x8e, 0xef, 0x0b, 0x1f, 0x0b, 0x1b, 0xf1, 0xfb, + 0x55, 0x40, 0xe8, 0x2d, 0xd4, 0x17, 0xf6, 0x92, 0x00, 0xf2, 0x37, 0x12, 0x8d, 0x0b, 0x8b, 0xfb, + 0x11, 0x36, 0x33, 0x57, 0x8d, 0x54, 0x30, 0x73, 0x3b, 0x3b, 0x53, 0xf9, 0xf8, 0x0e, 0xcc, 0xc5, + 0x51, 0x25, 0x55, 0x4c, 0x6d, 0x26, 0x5b, 0xb0, 0x38, 0xcc, 0x9f, 0x60, 0x2b, 0x7b, 0x20, 0xc9, + 0x1a, 0xe6, 0xee, 0x2c, 0xc9, 0x55, 0xbc, 0x1f, 0xe0, 0x59, 0xd6, 0xc4, 0x90, 0x1a, 0x66, 0xb7, + 0xb2, 0xb2, 0x93, 0xa9, 0x3a, 0x00, 0xb2, 0x6c, 0x2e, 0xc8, 0x3a, 0xe6, 0xb7, 0x97, 0xe6, 0xc7, + 0x71, 0x2f, 0xfe, 0x68, 0x50, 0xea, 0x7d, 0xe9, 0x5a, 0xbd, 0xd3, 0x43, 0xb3, 0x01, 0x9b, 0xf2, + 0xf3, 0xf2, 0xfa, 0xac, 0x67, 0x1d, 0x5c, 0x5b, 0xfb, 0x47, 0xfd, 0xb3, 0x2b, 0x09, 0x6c, 0xd8, + 0xe6, 0x36, 0x34, 0x16, 0xa1, 0xfd, 0xde, 0x09, 0xc2, 0x8e, 0xd9, 0x84, 0x2d, 0x09, 0x5f, 0xf4, + 0x8f, 0x8e, 0x2f, 0xaf, 0xfa, 0x17, 0x32, 0xe4, 0xf8, 0x10, 0xb1, 0x5b, 0x73, 0x07, 0x9a, 0x19, + 0x98, 0xca, 0x1d, 0x9a, 0x2d, 0x20, 0xf3, 0xf8, 0xd9, 0xfe, 0x69, 0x1f, 0xd1, 0x3b, 0xb3, 0x0d, + 0xad, 0x4c, 0x54, 0xe5, 0xf3, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x09, 0xe0, 0x81, 0x2c, 0xc9, + 0x08, 0x00, 0x00, +} diff --git a/common/api/l5/cl5.proto b/common/api/l5/cl5.proto new file mode 100644 index 000000000..1cb457043 --- /dev/null +++ b/common/api/l5/cl5.proto @@ -0,0 +1,179 @@ + +syntax = "proto2"; + +package l5; + +enum CL5_CMD +{ +//------------------------------------------------ + CL5_SYNC_BY_AGENT_CMD = 100; // 该命令号标示请求需要根据agent IP和Sid列表同步相关信息 + CL5_SYNC_BY_AGENT_ACK_CMD = 101; // 请求CL5_SYNC_BY_AGENT_CMD对应的应答包命令号 + CL5_REGISTER_BY_ID_CMD = 102; // 已经废弃 + CL5_REGISTER_BY_ID_ACK_CMD = 103; // 已经废弃 + CL5_REGISTER_BY_NAME_CMD = 104; // 该命令号的请求表示需要根据Name获取对应的Sid信息 + CL5_REGISTER_BY_NAME_ACK_CMD = 105; // CL5_REGISTER_BY_NAME_CMD对应的应答包命令号 +} + +// IP配置信息,用于实现就近访问。L5 server从表t_ip_config读取,由OSS从CMDB查询IP相关信息写入表中 +message cl5_ipc_obj +{ + required int32 ip = 1; + required int32 area_id = 2; // 区域编号 + required int32 city_id = 3; // 城市编号 + required int32 idc_id = 4; // IDC编号 +} + +// IP配置信息列表 +message cl5_ipc_list +{ + repeated cl5_ipc_obj ipc = 1; +} + +// Sid属性 +message cl5_sid_obj +{ + required int32 mod_id = 1; + required int32 cmd_id = 2; + optional string name = 3; // 名字,可用于实现通过name查询sid + optional int32 policy = 4; // 就近访问标志, 0:不开启就近访问; 1:开启就近访问 +} + +// Sid属性列表 +message cl5_sid_list +{ + repeated cl5_sid_obj sid = 1; +} + +// Sid +message cl5_opt_obj +{ + required int32 mod_id = 1; + required int32 cmd_id = 2; +} + +// Sid列表,用于CL5_SYNC_BY_AGENT_CMD请求包 +message cl5_opt_list +{ + repeated cl5_opt_obj opt = 1; +} + +// Sid名字列表,用于CL5_REGISTER_BY_NAME_CMD请求包 +message cl5_name_list +{ + repeated string name = 1; +} + +// l5 server列表,用于agent实现在连接l5 server失败时重试其他l5 server +message cl5_l5svr_list +{ + repeated int32 ip = 1; +} + +// 被调server +message cl5_serv_obj +{ + required int32 mod_id = 1; + required int32 cmd_id = 2; + required int32 ip = 3; + required int32 port = 4; + required int32 weight = 5; +} + +// 被调server列表 +message cl5_serv_list +{ + repeated cl5_serv_obj serv = 1; +} + +// 有状态规则配置 +message cl5_poly_obj +{ + required int32 mod_id = 1; + required int32 div = 2; + required int32 mod = 3; +} + +// 有状态规则分段信息 +message cl5_sect_obj +{ + required int32 mod_id = 1; + required int32 from = 2; + required int32 to = 3; + required int32 cmd_id = 4; +} + +// 有状态规则列表 +message cl5_rule_list +{ + repeated cl5_poly_obj poly = 1; + repeated cl5_sect_obj sect = 2; +} + +// CL5_SYNC_BY_AGENT_CMD请求包 +message cl5_sync_by_agent_cmd +{ + required int32 agent_ip = 1; // agent ip,在就近访问开启时,会在cl5_ipc_list中返回agent的ip配置信息 + required int32 sync_flow = 2; // 流水号 + optional cl5_opt_list opt_list = 3; // 需要同步信息的Sid列表 +} + +// CL5_SYNC_BY_AGENT_CMD应答包 +message cl5_sync_by_agent_ack_cmd +{ + required int32 agent_ip = 1; + required int32 sync_flow = 2; + optional cl5_serv_list serv_list = 3; // 被调信息列表 + optional cl5_sid_list sid_list = 4; // sid信息列表 + optional cl5_ipc_list ipc_list = 5; // ip信息列表 + optional cl5_rule_list rule_list = 6; // 有状态规则列表 + optional cl5_l5svr_list l5svr_list =7; // l5 server列表 +} + +// 已经废弃 +message cl5_register_by_id_cmd +{ + required int32 caller_ip = 1; + optional cl5_opt_list opt_list = 2; +} + +// 已经废弃 +message cl5_register_by_id_ack_cmd +{ + required int32 caller_ip = 1; + optional cl5_serv_list serv_list = 2; + optional cl5_sid_list sid_list = 3; + optional cl5_ipc_list ipc_list = 4; +} + +// CL5_REGISTER_BY_NAME_CMD 请求包 +message cl5_register_by_name_cmd +{ + required int32 caller_ip = 1; + optional cl5_name_list name_list = 2; +} + +// CL5_REGISTER_BY_NAME_ACK_CMD 应答包 +message cl5_register_by_name_ack_cmd +{ + required int32 caller_ip = 1; + optional cl5_serv_list serv_list = 2; // 被调信息列表 + optional cl5_sid_list sid_list = 3; // sid信息列表 + optional cl5_ipc_list ipc_list = 4; // ip信息列表 +} + +// 包格式 +message cl5_pkg +{ + required int32 seqno =1; // 序列号 + required int32 cmd =2; // 命令字 + optional int32 result =3; // 结果 + optional string error =4; // 调用 + optional int32 ip =5; // 调用者IP + + optional cl5_sync_by_agent_cmd sync_by_agent_cmd =10; + optional cl5_sync_by_agent_ack_cmd sync_by_agent_ack_cmd =11; + optional cl5_register_by_id_cmd register_by_id_cmd =12; + optional cl5_register_by_id_ack_cmd register_by_id_ack_cmd =13; + optional cl5_register_by_name_cmd register_by_name_cmd =14; + optional cl5_register_by_name_ack_cmd register_by_name_ack_cmd =15; +} diff --git a/common/api/protoc/bin/protoc b/common/api/protoc/bin/protoc new file mode 100644 index 000000000..3e2c6f39a Binary files /dev/null and b/common/api/protoc/bin/protoc differ diff --git a/common/api/protoc/bin/protoc-gen-go b/common/api/protoc/bin/protoc-gen-go new file mode 100644 index 000000000..46f3586b4 Binary files /dev/null and b/common/api/protoc/bin/protoc-gen-go differ diff --git a/common/api/protoc/include/google/protobuf/any.proto b/common/api/protoc/include/google/protobuf/any.proto new file mode 100644 index 000000000..c9be85416 --- /dev/null +++ b/common/api/protoc/include/google/protobuf/any.proto @@ -0,0 +1,155 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option go_package = "github.com/golang/protobuf/ptypes/any"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "AnyProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; + +// `Any` contains an arbitrary serialized protocol buffer message along with a +// URL that describes the type of the serialized message. +// +// Protobuf library provides support to pack/unpack Any values in the form +// of utility functions or additional generated methods of the Any type. +// +// Example 1: Pack and unpack a message in C++. +// +// Foo foo = ...; +// Any any; +// any.PackFrom(foo); +// ... +// if (any.UnpackTo(&foo)) { +// ... +// } +// +// Example 2: Pack and unpack a message in Java. +// +// Foo foo = ...; +// Any any = Any.pack(foo); +// ... +// if (any.is(Foo.class)) { +// foo = any.unpack(Foo.class); +// } +// +// Example 3: Pack and unpack a message in Python. +// +// foo = Foo(...) +// any = Any() +// any.Pack(foo) +// ... +// if any.Is(Foo.DESCRIPTOR): +// any.Unpack(foo) +// ... +// +// Example 4: Pack and unpack a message in Go +// +// foo := &pb.Foo{...} +// any, err := ptypes.MarshalAny(foo) +// ... +// foo := &pb.Foo{} +// if err := ptypes.UnmarshalAny(any, foo); err != nil { +// ... +// } +// +// The pack methods provided by protobuf library will by default use +// 'type.googleapis.com/full.type.name' as the type URL and the unpack +// methods only use the fully qualified type name after the last '/' +// in the type URL, for example "foo.bar.com/x/y.z" will yield type +// name "y.z". +// +// +// JSON +// ==== +// The JSON representation of an `Any` value uses the regular +// representation of the deserialized, embedded message, with an +// additional field `@type` which contains the type URL. Example: +// +// package google.profile; +// message Person { +// string first_name = 1; +// string last_name = 2; +// } +// +// { +// "@type": "type.googleapis.com/google.profile.Person", +// "firstName": , +// "lastName": +// } +// +// If the embedded message type is well-known and has a custom JSON +// representation, that representation will be embedded adding a field +// `value` which holds the custom JSON in addition to the `@type` +// field. Example (for message [google.protobuf.Duration][]): +// +// { +// "@type": "type.googleapis.com/google.protobuf.Duration", +// "value": "1.212s" +// } +// +message Any { + // A URL/resource name that uniquely identifies the type of the serialized + // protocol buffer message. This string must contain at least + // one "/" character. The last segment of the URL's path must represent + // the fully qualified name of the type (as in + // `path/google.protobuf.Duration`). The name should be in a canonical form + // (e.g., leading "." is not accepted). + // + // In practice, teams usually precompile into the binary all types that they + // expect it to use in the context of Any. However, for URLs which use the + // scheme `http`, `https`, or no scheme, one can optionally set up a type + // server that maps type URLs to message definitions as follows: + // + // * If no scheme is provided, `https` is assumed. + // * An HTTP GET on the URL must yield a [google.protobuf.Type][] + // value in binary format, or produce an error. + // * Applications are allowed to cache lookup results based on the + // URL, or have them precompiled into a binary to avoid any + // lookup. Therefore, binary compatibility needs to be preserved + // on changes to types. (Use versioned type names to manage + // breaking changes.) + // + // Note: this functionality is not currently available in the official + // protobuf release, and it is not used for type URLs beginning with + // type.googleapis.com. + // + // Schemes other than `http`, `https` (or the empty scheme) might be + // used with implementation specific semantics. + // + string type_url = 1; + + // Must be a valid serialized protocol buffer of the above specified type. + bytes value = 2; +} diff --git a/common/api/protoc/include/google/protobuf/api.proto b/common/api/protoc/include/google/protobuf/api.proto new file mode 100644 index 000000000..bd6beed3e --- /dev/null +++ b/common/api/protoc/include/google/protobuf/api.proto @@ -0,0 +1,210 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +import "google/protobuf/source_context.proto"; +import "google/protobuf/type.proto"; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "ApiProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option go_package = "google.golang.org/genproto/protobuf/api;api"; + +// Api is a light-weight descriptor for an API Interface. +// +// Interfaces are also described as "protocol buffer services" in some contexts, +// such as by the "service" keyword in a .proto file, but they are different +// from API Services, which represent a concrete implementation of an interface +// as opposed to simply a description of methods and bindings. They are also +// sometimes simply referred to as "APIs" in other contexts, such as the name of +// this message itself. See https://cloud.google.com/apis/design/glossary for +// detailed terminology. +message Api { + + // The fully qualified name of this interface, including package name + // followed by the interface's simple name. + string name = 1; + + // The methods of this interface, in unspecified order. + repeated Method methods = 2; + + // Any metadata attached to the interface. + repeated Option options = 3; + + // A version string for this interface. If specified, must have the form + // `major-version.minor-version`, as in `1.10`. If the minor version is + // omitted, it defaults to zero. If the entire version field is empty, the + // major version is derived from the package name, as outlined below. If the + // field is not empty, the version in the package name will be verified to be + // consistent with what is provided here. + // + // The versioning schema uses [semantic + // versioning](http://semver.org) where the major version number + // indicates a breaking change and the minor version an additive, + // non-breaking change. Both version numbers are signals to users + // what to expect from different versions, and should be carefully + // chosen based on the product plan. + // + // The major version is also reflected in the package name of the + // interface, which must end in `v`, as in + // `google.feature.v1`. For major versions 0 and 1, the suffix can + // be omitted. Zero major versions must only be used for + // experimental, non-GA interfaces. + // + // + string version = 4; + + // Source context for the protocol buffer service represented by this + // message. + SourceContext source_context = 5; + + // Included interfaces. See [Mixin][]. + repeated Mixin mixins = 6; + + // The source syntax of the service. + Syntax syntax = 7; +} + +// Method represents a method of an API interface. +message Method { + + // The simple name of this method. + string name = 1; + + // A URL of the input message type. + string request_type_url = 2; + + // If true, the request is streamed. + bool request_streaming = 3; + + // The URL of the output message type. + string response_type_url = 4; + + // If true, the response is streamed. + bool response_streaming = 5; + + // Any metadata attached to the method. + repeated Option options = 6; + + // The source syntax of this method. + Syntax syntax = 7; +} + +// Declares an API Interface to be included in this interface. The including +// interface must redeclare all the methods from the included interface, but +// documentation and options are inherited as follows: +// +// - If after comment and whitespace stripping, the documentation +// string of the redeclared method is empty, it will be inherited +// from the original method. +// +// - Each annotation belonging to the service config (http, +// visibility) which is not set in the redeclared method will be +// inherited. +// +// - If an http annotation is inherited, the path pattern will be +// modified as follows. Any version prefix will be replaced by the +// version of the including interface plus the [root][] path if +// specified. +// +// Example of a simple mixin: +// +// package google.acl.v1; +// service AccessControl { +// // Get the underlying ACL object. +// rpc GetAcl(GetAclRequest) returns (Acl) { +// option (google.api.http).get = "/v1/{resource=**}:getAcl"; +// } +// } +// +// package google.storage.v1; +// service Storage { +// rpc GetAcl(GetAclRequest) returns (Acl); +// +// // Get a data record. +// rpc GetData(GetDataRequest) returns (Data) { +// option (google.api.http).get = "/v1/{resource=**}"; +// } +// } +// +// Example of a mixin configuration: +// +// apis: +// - name: google.storage.v1.Storage +// mixins: +// - name: google.acl.v1.AccessControl +// +// The mixin construct implies that all methods in `AccessControl` are +// also declared with same name and request/response types in +// `Storage`. A documentation generator or annotation processor will +// see the effective `Storage.GetAcl` method after inherting +// documentation and annotations as follows: +// +// service Storage { +// // Get the underlying ACL object. +// rpc GetAcl(GetAclRequest) returns (Acl) { +// option (google.api.http).get = "/v1/{resource=**}:getAcl"; +// } +// ... +// } +// +// Note how the version in the path pattern changed from `v1` to `v1`. +// +// If the `root` field in the mixin is specified, it should be a +// relative path under which inherited HTTP paths are placed. Example: +// +// apis: +// - name: google.storage.v1.Storage +// mixins: +// - name: google.acl.v1.AccessControl +// root: acls +// +// This implies the following inherited HTTP annotation: +// +// service Storage { +// // Get the underlying ACL object. +// rpc GetAcl(GetAclRequest) returns (Acl) { +// option (google.api.http).get = "/v1/acls/{resource=**}:getAcl"; +// } +// ... +// } +message Mixin { + // The fully qualified name of the interface which is included. + string name = 1; + + // If non-empty specifies a path under which inherited HTTP paths + // are rooted. + string root = 2; +} diff --git a/common/api/protoc/include/google/protobuf/compiler/plugin.proto b/common/api/protoc/include/google/protobuf/compiler/plugin.proto new file mode 100644 index 000000000..665e5a725 --- /dev/null +++ b/common/api/protoc/include/google/protobuf/compiler/plugin.proto @@ -0,0 +1,168 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: kenton@google.com (Kenton Varda) +// +// WARNING: The plugin interface is currently EXPERIMENTAL and is subject to +// change. +// +// protoc (aka the Protocol Compiler) can be extended via plugins. A plugin is +// just a program that reads a CodeGeneratorRequest from stdin and writes a +// CodeGeneratorResponse to stdout. +// +// Plugins written using C++ can use google/protobuf/compiler/plugin.h instead +// of dealing with the raw protocol defined here. +// +// A plugin executable needs only to be placed somewhere in the path. The +// plugin should be named "protoc-gen-$NAME", and will then be used when the +// flag "--${NAME}_out" is passed to protoc. + +syntax = "proto2"; + +package google.protobuf.compiler; +option java_package = "com.google.protobuf.compiler"; +option java_outer_classname = "PluginProtos"; + +option go_package = "github.com/golang/protobuf/protoc-gen-go/plugin;plugin_go"; + +import "google/protobuf/descriptor.proto"; + +// The version number of protocol compiler. +message Version { + optional int32 major = 1; + optional int32 minor = 2; + optional int32 patch = 3; + // A suffix for alpha, beta or rc release, e.g., "alpha-1", "rc2". It should + // be empty for mainline stable releases. + optional string suffix = 4; +} + +// An encoded CodeGeneratorRequest is written to the plugin's stdin. +message CodeGeneratorRequest { + // The .proto files that were explicitly listed on the command-line. The + // code generator should generate code only for these files. Each file's + // descriptor will be included in proto_file, below. + repeated string file_to_generate = 1; + + // The generator parameter passed on the command-line. + optional string parameter = 2; + + // FileDescriptorProtos for all files in files_to_generate and everything + // they import. The files will appear in topological order, so each file + // appears before any file that imports it. + // + // protoc guarantees that all proto_files will be written after + // the fields above, even though this is not technically guaranteed by the + // protobuf wire format. This theoretically could allow a plugin to stream + // in the FileDescriptorProtos and handle them one by one rather than read + // the entire set into memory at once. However, as of this writing, this + // is not similarly optimized on protoc's end -- it will store all fields in + // memory at once before sending them to the plugin. + // + // Type names of fields and extensions in the FileDescriptorProto are always + // fully qualified. + repeated FileDescriptorProto proto_file = 15; + + // The version number of protocol compiler. + optional Version compiler_version = 3; + +} + +// The plugin writes an encoded CodeGeneratorResponse to stdout. +message CodeGeneratorResponse { + // Error message. If non-empty, code generation failed. The plugin process + // should exit with status code zero even if it reports an error in this way. + // + // This should be used to indicate errors in .proto files which prevent the + // code generator from generating correct code. Errors which indicate a + // problem in protoc itself -- such as the input CodeGeneratorRequest being + // unparseable -- should be reported by writing a message to stderr and + // exiting with a non-zero status code. + optional string error = 1; + + // Represents a single generated file. + message File { + // The file name, relative to the output directory. The name must not + // contain "." or ".." components and must be relative, not be absolute (so, + // the file cannot lie outside the output directory). "/" must be used as + // the path separator, not "\". + // + // If the name is omitted, the content will be appended to the previous + // file. This allows the generator to break large files into small chunks, + // and allows the generated text to be streamed back to protoc so that large + // files need not reside completely in memory at one time. Note that as of + // this writing protoc does not optimize for this -- it will read the entire + // CodeGeneratorResponse before writing files to disk. + optional string name = 1; + + // If non-empty, indicates that the named file should already exist, and the + // content here is to be inserted into that file at a defined insertion + // point. This feature allows a code generator to extend the output + // produced by another code generator. The original generator may provide + // insertion points by placing special annotations in the file that look + // like: + // @@protoc_insertion_point(NAME) + // The annotation can have arbitrary text before and after it on the line, + // which allows it to be placed in a comment. NAME should be replaced with + // an identifier naming the point -- this is what other generators will use + // as the insertion_point. Code inserted at this point will be placed + // immediately above the line containing the insertion point (thus multiple + // insertions to the same point will come out in the order they were added). + // The double-@ is intended to make it unlikely that the generated code + // could contain things that look like insertion points by accident. + // + // For example, the C++ code generator places the following line in the + // .pb.h files that it generates: + // // @@protoc_insertion_point(namespace_scope) + // This line appears within the scope of the file's package namespace, but + // outside of any particular class. Another plugin can then specify the + // insertion_point "namespace_scope" to generate additional classes or + // other declarations that should be placed in this scope. + // + // Note that if the line containing the insertion point begins with + // whitespace, the same whitespace will be added to every line of the + // inserted text. This is useful for languages like Python, where + // indentation matters. In these languages, the insertion point comment + // should be indented the same amount as any inserted code will need to be + // in order to work correctly in that context. + // + // The code generator that generates the initial file and the one which + // inserts into it must both run as part of a single invocation of protoc. + // Code generators are executed in the order in which they appear on the + // command line. + // + // If |insertion_point| is present, |name| must also be present. + optional string insertion_point = 2; + + // The file contents. + optional string content = 15; + } + repeated File file = 15; +} diff --git a/common/api/protoc/include/google/protobuf/descriptor.proto b/common/api/protoc/include/google/protobuf/descriptor.proto new file mode 100644 index 000000000..a2102d7aa --- /dev/null +++ b/common/api/protoc/include/google/protobuf/descriptor.proto @@ -0,0 +1,885 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: kenton@google.com (Kenton Varda) +// Based on original Protocol Buffers design by +// Sanjay Ghemawat, Jeff Dean, and others. +// +// The messages in this file describe the definitions found in .proto files. +// A valid .proto file can be translated directly to a FileDescriptorProto +// without any other information (e.g. without reading its imports). + + +syntax = "proto2"; + +package google.protobuf; + +option go_package = "github.com/golang/protobuf/protoc-gen-go/descriptor;descriptor"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "DescriptorProtos"; +option csharp_namespace = "Google.Protobuf.Reflection"; +option objc_class_prefix = "GPB"; +option cc_enable_arenas = true; + +// descriptor.proto must be optimized for speed because reflection-based +// algorithms don't work during bootstrapping. +option optimize_for = SPEED; + +// The protocol compiler can output a FileDescriptorSet containing the .proto +// files it parses. +message FileDescriptorSet { + repeated FileDescriptorProto file = 1; +} + +// Describes a complete .proto file. +message FileDescriptorProto { + optional string name = 1; // file name, relative to root of source tree + optional string package = 2; // e.g. "foo", "foo.bar", etc. + + // Names of files imported by this file. + repeated string dependency = 3; + // Indexes of the public imported files in the dependency list above. + repeated int32 public_dependency = 10; + // Indexes of the weak imported files in the dependency list. + // For Google-internal migration only. Do not use. + repeated int32 weak_dependency = 11; + + // All top-level definitions in this file. + repeated DescriptorProto message_type = 4; + repeated EnumDescriptorProto enum_type = 5; + repeated ServiceDescriptorProto service = 6; + repeated FieldDescriptorProto extension = 7; + + optional FileOptions options = 8; + + // This field contains optional information about the original source code. + // You may safely remove this entire field without harming runtime + // functionality of the descriptors -- the information is needed only by + // development tools. + optional SourceCodeInfo source_code_info = 9; + + // The syntax of the proto file. + // The supported values are "proto2" and "proto3". + optional string syntax = 12; +} + +// Describes a message type. +message DescriptorProto { + optional string name = 1; + + repeated FieldDescriptorProto field = 2; + repeated FieldDescriptorProto extension = 6; + + repeated DescriptorProto nested_type = 3; + repeated EnumDescriptorProto enum_type = 4; + + message ExtensionRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Exclusive. + + optional ExtensionRangeOptions options = 3; + } + repeated ExtensionRange extension_range = 5; + + repeated OneofDescriptorProto oneof_decl = 8; + + optional MessageOptions options = 7; + + // Range of reserved tag numbers. Reserved tag numbers may not be used by + // fields or extension ranges in the same message. Reserved ranges may + // not overlap. + message ReservedRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Exclusive. + } + repeated ReservedRange reserved_range = 9; + // Reserved field names, which may not be used by fields in the same message. + // A given name may only be reserved once. + repeated string reserved_name = 10; +} + +message ExtensionRangeOptions { + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +// Describes a field within a message. +message FieldDescriptorProto { + enum Type { + // 0 is reserved for errors. + // Order is weird for historical reasons. + TYPE_DOUBLE = 1; + TYPE_FLOAT = 2; + // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if + // negative values are likely. + TYPE_INT64 = 3; + TYPE_UINT64 = 4; + // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if + // negative values are likely. + TYPE_INT32 = 5; + TYPE_FIXED64 = 6; + TYPE_FIXED32 = 7; + TYPE_BOOL = 8; + TYPE_STRING = 9; + // Tag-delimited aggregate. + // Group type is deprecated and not supported in proto3. However, Proto3 + // implementations should still be able to parse the group wire format and + // treat group fields as unknown fields. + TYPE_GROUP = 10; + TYPE_MESSAGE = 11; // Length-delimited aggregate. + + // New in version 2. + TYPE_BYTES = 12; + TYPE_UINT32 = 13; + TYPE_ENUM = 14; + TYPE_SFIXED32 = 15; + TYPE_SFIXED64 = 16; + TYPE_SINT32 = 17; // Uses ZigZag encoding. + TYPE_SINT64 = 18; // Uses ZigZag encoding. + } + + enum Label { + // 0 is reserved for errors + LABEL_OPTIONAL = 1; + LABEL_REQUIRED = 2; + LABEL_REPEATED = 3; + } + + optional string name = 1; + optional int32 number = 3; + optional Label label = 4; + + // If type_name is set, this need not be set. If both this and type_name + // are set, this must be one of TYPE_ENUM, TYPE_MESSAGE or TYPE_GROUP. + optional Type type = 5; + + // For message and enum types, this is the name of the type. If the name + // starts with a '.', it is fully-qualified. Otherwise, C++-like scoping + // rules are used to find the type (i.e. first the nested types within this + // message are searched, then within the parent, on up to the root + // namespace). + optional string type_name = 6; + + // For extensions, this is the name of the type being extended. It is + // resolved in the same manner as type_name. + optional string extendee = 2; + + // For numeric types, contains the original text representation of the value. + // For booleans, "true" or "false". + // For strings, contains the default text contents (not escaped in any way). + // For bytes, contains the C escaped value. All bytes >= 128 are escaped. + // TODO(kenton): Base-64 encode? + optional string default_value = 7; + + // If set, gives the index of a oneof in the containing type's oneof_decl + // list. This field is a member of that oneof. + optional int32 oneof_index = 9; + + // JSON name of this field. The value is set by protocol compiler. If the + // user has set a "json_name" option on this field, that option's value + // will be used. Otherwise, it's deduced from the field's name by converting + // it to camelCase. + optional string json_name = 10; + + optional FieldOptions options = 8; +} + +// Describes a oneof. +message OneofDescriptorProto { + optional string name = 1; + optional OneofOptions options = 2; +} + +// Describes an enum type. +message EnumDescriptorProto { + optional string name = 1; + + repeated EnumValueDescriptorProto value = 2; + + optional EnumOptions options = 3; + + // Range of reserved numeric values. Reserved values may not be used by + // entries in the same enum. Reserved ranges may not overlap. + // + // Note that this is distinct from DescriptorProto.ReservedRange in that it + // is inclusive such that it can appropriately represent the entire int32 + // domain. + message EnumReservedRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Inclusive. + } + + // Range of reserved numeric values. Reserved numeric values may not be used + // by enum values in the same enum declaration. Reserved ranges may not + // overlap. + repeated EnumReservedRange reserved_range = 4; + + // Reserved enum value names, which may not be reused. A given name may only + // be reserved once. + repeated string reserved_name = 5; +} + +// Describes a value within an enum. +message EnumValueDescriptorProto { + optional string name = 1; + optional int32 number = 2; + + optional EnumValueOptions options = 3; +} + +// Describes a service. +message ServiceDescriptorProto { + optional string name = 1; + repeated MethodDescriptorProto method = 2; + + optional ServiceOptions options = 3; +} + +// Describes a method of a service. +message MethodDescriptorProto { + optional string name = 1; + + // Input and output type names. These are resolved in the same way as + // FieldDescriptorProto.type_name, but must refer to a message type. + optional string input_type = 2; + optional string output_type = 3; + + optional MethodOptions options = 4; + + // Identifies if client streams multiple client messages + optional bool client_streaming = 5 [default = false]; + // Identifies if server streams multiple server messages + optional bool server_streaming = 6 [default = false]; +} + + +// =================================================================== +// Options + +// Each of the definitions above may have "options" attached. These are +// just annotations which may cause code to be generated slightly differently +// or may contain hints for code that manipulates protocol messages. +// +// Clients may define custom options as extensions of the *Options messages. +// These extensions may not yet be known at parsing time, so the parser cannot +// store the values in them. Instead it stores them in a field in the *Options +// message called uninterpreted_option. This field must have the same name +// across all *Options messages. We then use this field to populate the +// extensions when we build a descriptor, at which point all protos have been +// parsed and so all extensions are known. +// +// Extension numbers for custom options may be chosen as follows: +// * For options which will only be used within a single application or +// organization, or for experimental options, use field numbers 50000 +// through 99999. It is up to you to ensure that you do not use the +// same number for multiple options. +// * For options which will be published and used publicly by multiple +// independent entities, e-mail protobuf-global-extension-registry@google.com +// to reserve extension numbers. Simply provide your project name (e.g. +// Objective-C plugin) and your project website (if available) -- there's no +// need to explain how you intend to use them. Usually you only need one +// extension number. You can declare multiple options with only one extension +// number by putting them in a sub-message. See the Custom Options section of +// the docs for examples: +// https://developers.google.com/protocol-buffers/docs/proto#options +// If this turns out to be popular, a web service will be set up +// to automatically assign option numbers. + +message FileOptions { + + // Sets the Java package where classes generated from this .proto will be + // placed. By default, the proto package is used, but this is often + // inappropriate because proto packages do not normally start with backwards + // domain names. + optional string java_package = 1; + + + // If set, all the classes from the .proto file are wrapped in a single + // outer class with the given name. This applies to both Proto1 + // (equivalent to the old "--one_java_file" option) and Proto2 (where + // a .proto always translates to a single class, but you may want to + // explicitly choose the class name). + optional string java_outer_classname = 8; + + // If set true, then the Java code generator will generate a separate .java + // file for each top-level message, enum, and service defined in the .proto + // file. Thus, these types will *not* be nested inside the outer class + // named by java_outer_classname. However, the outer class will still be + // generated to contain the file's getDescriptor() method as well as any + // top-level extensions defined in the file. + optional bool java_multiple_files = 10 [default = false]; + + // This option does nothing. + optional bool java_generate_equals_and_hash = 20 [deprecated=true]; + + // If set true, then the Java2 code generator will generate code that + // throws an exception whenever an attempt is made to assign a non-UTF-8 + // byte sequence to a string field. + // Message reflection will do the same. + // However, an extension field still accepts non-UTF-8 byte sequences. + // This option has no effect on when used with the lite runtime. + optional bool java_string_check_utf8 = 27 [default = false]; + + + // Generated classes can be optimized for speed or code size. + enum OptimizeMode { + SPEED = 1; // Generate complete code for parsing, serialization, + // etc. + CODE_SIZE = 2; // Use ReflectionOps to implement these methods. + LITE_RUNTIME = 3; // Generate code using MessageLite and the lite runtime. + } + optional OptimizeMode optimize_for = 9 [default = SPEED]; + + // Sets the Go package where structs generated from this .proto will be + // placed. If omitted, the Go package will be derived from the following: + // - The basename of the package import path, if provided. + // - Otherwise, the package statement in the .proto file, if present. + // - Otherwise, the basename of the .proto file, without extension. + optional string go_package = 11; + + + + + // Should generic services be generated in each language? "Generic" services + // are not specific to any particular RPC system. They are generated by the + // main code generators in each language (without additional plugins). + // Generic services were the only kind of service generation supported by + // early versions of google.protobuf. + // + // Generic services are now considered deprecated in favor of using plugins + // that generate code specific to your particular RPC system. Therefore, + // these default to false. Old code which depends on generic services should + // explicitly set them to true. + optional bool cc_generic_services = 16 [default = false]; + optional bool java_generic_services = 17 [default = false]; + optional bool py_generic_services = 18 [default = false]; + optional bool php_generic_services = 42 [default = false]; + + // Is this file deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for everything in the file, or it will be completely ignored; in the very + // least, this is a formalization for deprecating files. + optional bool deprecated = 23 [default = false]; + + // Enables the use of arenas for the proto messages in this file. This applies + // only to generated classes for C++. + optional bool cc_enable_arenas = 31 [default = false]; + + + // Sets the objective c class prefix which is prepended to all objective c + // generated classes from this .proto. There is no default. + optional string objc_class_prefix = 36; + + // Namespace for generated classes; defaults to the package. + optional string csharp_namespace = 37; + + // By default Swift generators will take the proto package and CamelCase it + // replacing '.' with underscore and use that to prefix the types/symbols + // defined. When this options is provided, they will use this value instead + // to prefix the types/symbols defined. + optional string swift_prefix = 39; + + // Sets the php class prefix which is prepended to all php generated classes + // from this .proto. Default is empty. + optional string php_class_prefix = 40; + + // Use this option to change the namespace of php generated classes. Default + // is empty. When this option is empty, the package name will be used for + // determining the namespace. + optional string php_namespace = 41; + + // Use this option to change the namespace of php generated metadata classes. + // Default is empty. When this option is empty, the proto file name will be + // used for determining the namespace. + optional string php_metadata_namespace = 44; + + // Use this option to change the package of ruby generated classes. Default + // is empty. When this option is not set, the package name will be used for + // determining the ruby package. + optional string ruby_package = 45; + + + // The parser stores options it doesn't recognize here. + // See the documentation for the "Options" section above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. + // See the documentation for the "Options" section above. + extensions 1000 to max; + + reserved 38; +} + +message MessageOptions { + // Set true to use the old proto1 MessageSet wire format for extensions. + // This is provided for backwards-compatibility with the MessageSet wire + // format. You should not use this for any other reason: It's less + // efficient, has fewer features, and is more complicated. + // + // The message must be defined exactly as follows: + // message Foo { + // option message_set_wire_format = true; + // extensions 4 to max; + // } + // Note that the message cannot have any defined fields; MessageSets only + // have extensions. + // + // All extensions of your type must be singular messages; e.g. they cannot + // be int32s, enums, or repeated messages. + // + // Because this is an option, the above two restrictions are not enforced by + // the protocol compiler. + optional bool message_set_wire_format = 1 [default = false]; + + // Disables the generation of the standard "descriptor()" accessor, which can + // conflict with a field of the same name. This is meant to make migration + // from proto1 easier; new code should avoid fields named "descriptor". + optional bool no_standard_descriptor_accessor = 2 [default = false]; + + // Is this message deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the message, or it will be completely ignored; in the very least, + // this is a formalization for deprecating messages. + optional bool deprecated = 3 [default = false]; + + // Whether the message is an automatically generated map entry type for the + // maps field. + // + // For maps fields: + // map map_field = 1; + // The parsed descriptor looks like: + // message MapFieldEntry { + // option map_entry = true; + // optional KeyType key = 1; + // optional ValueType value = 2; + // } + // repeated MapFieldEntry map_field = 1; + // + // Implementations may choose not to generate the map_entry=true message, but + // use a native map in the target language to hold the keys and values. + // The reflection APIs in such implementations still need to work as + // if the field is a repeated message field. + // + // NOTE: Do not set the option in .proto files. Always use the maps syntax + // instead. The option should only be implicitly set by the proto compiler + // parser. + optional bool map_entry = 7; + + reserved 8; // javalite_serializable + reserved 9; // javanano_as_lite + + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message FieldOptions { + // The ctype option instructs the C++ code generator to use a different + // representation of the field than it normally would. See the specific + // options below. This option is not yet implemented in the open source + // release -- sorry, we'll try to include it in a future version! + optional CType ctype = 1 [default = STRING]; + enum CType { + // Default mode. + STRING = 0; + + CORD = 1; + + STRING_PIECE = 2; + } + // The packed option can be enabled for repeated primitive fields to enable + // a more efficient representation on the wire. Rather than repeatedly + // writing the tag and type for each element, the entire array is encoded as + // a single length-delimited blob. In proto3, only explicit setting it to + // false will avoid using packed encoding. + optional bool packed = 2; + + // The jstype option determines the JavaScript type used for values of the + // field. The option is permitted only for 64 bit integral and fixed types + // (int64, uint64, sint64, fixed64, sfixed64). A field with jstype JS_STRING + // is represented as JavaScript string, which avoids loss of precision that + // can happen when a large value is converted to a floating point JavaScript. + // Specifying JS_NUMBER for the jstype causes the generated JavaScript code to + // use the JavaScript "number" type. The behavior of the default option + // JS_NORMAL is implementation dependent. + // + // This option is an enum to permit additional types to be added, e.g. + // goog.math.Integer. + optional JSType jstype = 6 [default = JS_NORMAL]; + enum JSType { + // Use the default type. + JS_NORMAL = 0; + + // Use JavaScript strings. + JS_STRING = 1; + + // Use JavaScript numbers. + JS_NUMBER = 2; + } + + // Should this field be parsed lazily? Lazy applies only to message-type + // fields. It means that when the outer message is initially parsed, the + // inner message's contents will not be parsed but instead stored in encoded + // form. The inner message will actually be parsed when it is first accessed. + // + // This is only a hint. Implementations are free to choose whether to use + // eager or lazy parsing regardless of the value of this option. However, + // setting this option true suggests that the protocol author believes that + // using lazy parsing on this field is worth the additional bookkeeping + // overhead typically needed to implement it. + // + // This option does not affect the public interface of any generated code; + // all method signatures remain the same. Furthermore, thread-safety of the + // interface is not affected by this option; const methods remain safe to + // call from multiple threads concurrently, while non-const methods continue + // to require exclusive access. + // + // + // Note that implementations may choose not to check required fields within + // a lazy sub-message. That is, calling IsInitialized() on the outer message + // may return true even if the inner message has missing required fields. + // This is necessary because otherwise the inner message would have to be + // parsed in order to perform the check, defeating the purpose of lazy + // parsing. An implementation which chooses not to check required fields + // must be consistent about it. That is, for any particular sub-message, the + // implementation must either *always* check its required fields, or *never* + // check its required fields, regardless of whether or not the message has + // been parsed. + optional bool lazy = 5 [default = false]; + + // Is this field deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for accessors, or it will be completely ignored; in the very least, this + // is a formalization for deprecating fields. + optional bool deprecated = 3 [default = false]; + + // For Google-internal migration only. Do not use. + optional bool weak = 10 [default = false]; + + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; + + reserved 4; // removed jtype +} + +message OneofOptions { + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message EnumOptions { + + // Set this option to true to allow mapping different tag names to the same + // value. + optional bool allow_alias = 2; + + // Is this enum deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the enum, or it will be completely ignored; in the very least, this + // is a formalization for deprecating enums. + optional bool deprecated = 3 [default = false]; + + reserved 5; // javanano_as_lite + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message EnumValueOptions { + // Is this enum value deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the enum value, or it will be completely ignored; in the very least, + // this is a formalization for deprecating enum values. + optional bool deprecated = 1 [default = false]; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message ServiceOptions { + + // Note: Field numbers 1 through 32 are reserved for Google's internal RPC + // framework. We apologize for hoarding these numbers to ourselves, but + // we were already using them long before we decided to release Protocol + // Buffers. + + // Is this service deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the service, or it will be completely ignored; in the very least, + // this is a formalization for deprecating services. + optional bool deprecated = 33 [default = false]; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message MethodOptions { + + // Note: Field numbers 1 through 32 are reserved for Google's internal RPC + // framework. We apologize for hoarding these numbers to ourselves, but + // we were already using them long before we decided to release Protocol + // Buffers. + + // Is this method deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the method, or it will be completely ignored; in the very least, + // this is a formalization for deprecating methods. + optional bool deprecated = 33 [default = false]; + + // Is this method side-effect-free (or safe in HTTP parlance), or idempotent, + // or neither? HTTP based RPC implementation may choose GET verb for safe + // methods, and PUT verb for idempotent methods instead of the default POST. + enum IdempotencyLevel { + IDEMPOTENCY_UNKNOWN = 0; + NO_SIDE_EFFECTS = 1; // implies idempotent + IDEMPOTENT = 2; // idempotent, but may have side effects + } + optional IdempotencyLevel idempotency_level = 34 + [default = IDEMPOTENCY_UNKNOWN]; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + + +// A message representing a option the parser does not recognize. This only +// appears in options protos created by the compiler::Parser class. +// DescriptorPool resolves these when building Descriptor objects. Therefore, +// options protos in descriptor objects (e.g. returned by Descriptor::options(), +// or produced by Descriptor::CopyTo()) will never have UninterpretedOptions +// in them. +message UninterpretedOption { + // The name of the uninterpreted option. Each string represents a segment in + // a dot-separated name. is_extension is true iff a segment represents an + // extension (denoted with parentheses in options specs in .proto files). + // E.g.,{ ["foo", false], ["bar.baz", true], ["qux", false] } represents + // "foo.(bar.baz).qux". + message NamePart { + required string name_part = 1; + required bool is_extension = 2; + } + repeated NamePart name = 2; + + // The value of the uninterpreted option, in whatever type the tokenizer + // identified it as during parsing. Exactly one of these should be set. + optional string identifier_value = 3; + optional uint64 positive_int_value = 4; + optional int64 negative_int_value = 5; + optional double double_value = 6; + optional bytes string_value = 7; + optional string aggregate_value = 8; +} + +// =================================================================== +// Optional source code info + +// Encapsulates information about the original source file from which a +// FileDescriptorProto was generated. +message SourceCodeInfo { + // A Location identifies a piece of source code in a .proto file which + // corresponds to a particular definition. This information is intended + // to be useful to IDEs, code indexers, documentation generators, and similar + // tools. + // + // For example, say we have a file like: + // message Foo { + // optional string foo = 1; + // } + // Let's look at just the field definition: + // optional string foo = 1; + // ^ ^^ ^^ ^ ^^^ + // a bc de f ghi + // We have the following locations: + // span path represents + // [a,i) [ 4, 0, 2, 0 ] The whole field definition. + // [a,b) [ 4, 0, 2, 0, 4 ] The label (optional). + // [c,d) [ 4, 0, 2, 0, 5 ] The type (string). + // [e,f) [ 4, 0, 2, 0, 1 ] The name (foo). + // [g,h) [ 4, 0, 2, 0, 3 ] The number (1). + // + // Notes: + // - A location may refer to a repeated field itself (i.e. not to any + // particular index within it). This is used whenever a set of elements are + // logically enclosed in a single code segment. For example, an entire + // extend block (possibly containing multiple extension definitions) will + // have an outer location whose path refers to the "extensions" repeated + // field without an index. + // - Multiple locations may have the same path. This happens when a single + // logical declaration is spread out across multiple places. The most + // obvious example is the "extend" block again -- there may be multiple + // extend blocks in the same scope, each of which will have the same path. + // - A location's span is not always a subset of its parent's span. For + // example, the "extendee" of an extension declaration appears at the + // beginning of the "extend" block and is shared by all extensions within + // the block. + // - Just because a location's span is a subset of some other location's span + // does not mean that it is a descendant. For example, a "group" defines + // both a type and a field in a single declaration. Thus, the locations + // corresponding to the type and field and their components will overlap. + // - Code which tries to interpret locations should probably be designed to + // ignore those that it doesn't understand, as more types of locations could + // be recorded in the future. + repeated Location location = 1; + message Location { + // Identifies which part of the FileDescriptorProto was defined at this + // location. + // + // Each element is a field number or an index. They form a path from + // the root FileDescriptorProto to the place where the definition. For + // example, this path: + // [ 4, 3, 2, 7, 1 ] + // refers to: + // file.message_type(3) // 4, 3 + // .field(7) // 2, 7 + // .name() // 1 + // This is because FileDescriptorProto.message_type has field number 4: + // repeated DescriptorProto message_type = 4; + // and DescriptorProto.field has field number 2: + // repeated FieldDescriptorProto field = 2; + // and FieldDescriptorProto.name has field number 1: + // optional string name = 1; + // + // Thus, the above path gives the location of a field name. If we removed + // the last element: + // [ 4, 3, 2, 7 ] + // this path refers to the whole field declaration (from the beginning + // of the label to the terminating semicolon). + repeated int32 path = 1 [packed = true]; + + // Always has exactly three or four elements: start line, start column, + // end line (optional, otherwise assumed same as start line), end column. + // These are packed into a single field for efficiency. Note that line + // and column numbers are zero-based -- typically you will want to add + // 1 to each before displaying to a user. + repeated int32 span = 2 [packed = true]; + + // If this SourceCodeInfo represents a complete declaration, these are any + // comments appearing before and after the declaration which appear to be + // attached to the declaration. + // + // A series of line comments appearing on consecutive lines, with no other + // tokens appearing on those lines, will be treated as a single comment. + // + // leading_detached_comments will keep paragraphs of comments that appear + // before (but not connected to) the current element. Each paragraph, + // separated by empty lines, will be one comment element in the repeated + // field. + // + // Only the comment content is provided; comment markers (e.g. //) are + // stripped out. For block comments, leading whitespace and an asterisk + // will be stripped from the beginning of each line other than the first. + // Newlines are included in the output. + // + // Examples: + // + // optional int32 foo = 1; // Comment attached to foo. + // // Comment attached to bar. + // optional int32 bar = 2; + // + // optional string baz = 3; + // // Comment attached to baz. + // // Another line attached to baz. + // + // // Comment attached to qux. + // // + // // Another line attached to qux. + // optional double qux = 4; + // + // // Detached comment for corge. This is not leading or trailing comments + // // to qux or corge because there are blank lines separating it from + // // both. + // + // // Detached comment for corge paragraph 2. + // + // optional string corge = 5; + // /* Block comment attached + // * to corge. Leading asterisks + // * will be removed. */ + // /* Block comment attached to + // * grault. */ + // optional int32 grault = 6; + // + // // ignored detached comments. + optional string leading_comments = 3; + optional string trailing_comments = 4; + repeated string leading_detached_comments = 6; + } +} + +// Describes the relationship between generated code and its original source +// file. A GeneratedCodeInfo message is associated with only one generated +// source file, but may contain references to different source .proto files. +message GeneratedCodeInfo { + // An Annotation connects some span of text in generated code to an element + // of its generating .proto file. + repeated Annotation annotation = 1; + message Annotation { + // Identifies the element in the original source .proto file. This field + // is formatted the same as SourceCodeInfo.Location.path. + repeated int32 path = 1 [packed = true]; + + // Identifies the filesystem path to the original source .proto. + optional string source_file = 2; + + // Identifies the starting offset in bytes in the generated code + // that relates to the identified object. + optional int32 begin = 3; + + // Identifies the ending offset in bytes in the generated code that + // relates to the identified offset. The end offset should be one past + // the last relevant byte (so the length of the text = end - begin). + optional int32 end = 4; + } +} diff --git a/common/api/protoc/include/google/protobuf/duration.proto b/common/api/protoc/include/google/protobuf/duration.proto new file mode 100644 index 000000000..9d8a52de2 --- /dev/null +++ b/common/api/protoc/include/google/protobuf/duration.proto @@ -0,0 +1,116 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option cc_enable_arenas = true; +option go_package = "github.com/golang/protobuf/ptypes/duration"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "DurationProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; + +// A Duration represents a signed, fixed-length span of time represented +// as a count of seconds and fractions of seconds at nanosecond +// resolution. It is independent of any calendar and concepts like "day" +// or "month". It is related to Timestamp in that the difference between +// two Timestamp values is a Duration and it can be added or subtracted +// from a Timestamp. Range is approximately +-10,000 years. +// +// # Examples +// +// Example 1: Compute Duration from two Timestamps in pseudo code. +// +// Timestamp start = ...; +// Timestamp end = ...; +// Duration duration = ...; +// +// duration.seconds = end.seconds - start.seconds; +// duration.nanos = end.nanos - start.nanos; +// +// if (duration.seconds < 0 && duration.nanos > 0) { +// duration.seconds += 1; +// duration.nanos -= 1000000000; +// } else if (durations.seconds > 0 && duration.nanos < 0) { +// duration.seconds -= 1; +// duration.nanos += 1000000000; +// } +// +// Example 2: Compute Timestamp from Timestamp + Duration in pseudo code. +// +// Timestamp start = ...; +// Duration duration = ...; +// Timestamp end = ...; +// +// end.seconds = start.seconds + duration.seconds; +// end.nanos = start.nanos + duration.nanos; +// +// if (end.nanos < 0) { +// end.seconds -= 1; +// end.nanos += 1000000000; +// } else if (end.nanos >= 1000000000) { +// end.seconds += 1; +// end.nanos -= 1000000000; +// } +// +// Example 3: Compute Duration from datetime.timedelta in Python. +// +// td = datetime.timedelta(days=3, minutes=10) +// duration = Duration() +// duration.FromTimedelta(td) +// +// # JSON Mapping +// +// In JSON format, the Duration type is encoded as a string rather than an +// object, where the string ends in the suffix "s" (indicating seconds) and +// is preceded by the number of seconds, with nanoseconds expressed as +// fractional seconds. For example, 3 seconds with 0 nanoseconds should be +// encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should +// be expressed in JSON format as "3.000000001s", and 3 seconds and 1 +// microsecond should be expressed in JSON format as "3.000001s". +// +// +message Duration { + // Signed seconds of the span of time. Must be from -315,576,000,000 + // to +315,576,000,000 inclusive. Note: these bounds are computed from: + // 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years + int64 seconds = 1; + + // Signed fractions of a second at nanosecond resolution of the span + // of time. Durations less than one second are represented with a 0 + // `seconds` field and a positive or negative `nanos` field. For durations + // of one second or more, a non-zero value for the `nanos` field must be + // of the same sign as the `seconds` field. Must be from -999,999,999 + // to +999,999,999 inclusive. + int32 nanos = 2; +} diff --git a/common/api/protoc/include/google/protobuf/empty.proto b/common/api/protoc/include/google/protobuf/empty.proto new file mode 100644 index 000000000..03cacd233 --- /dev/null +++ b/common/api/protoc/include/google/protobuf/empty.proto @@ -0,0 +1,52 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option go_package = "github.com/golang/protobuf/ptypes/empty"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "EmptyProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option cc_enable_arenas = true; + +// A generic empty message that you can re-use to avoid defining duplicated +// empty messages in your APIs. A typical example is to use it as the request +// or the response type of an API method. For instance: +// +// service Foo { +// rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); +// } +// +// The JSON representation for `Empty` is empty JSON object `{}`. +message Empty {} diff --git a/common/api/protoc/include/google/protobuf/field_mask.proto b/common/api/protoc/include/google/protobuf/field_mask.proto new file mode 100644 index 000000000..4015b1a3e --- /dev/null +++ b/common/api/protoc/include/google/protobuf/field_mask.proto @@ -0,0 +1,245 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "FieldMaskProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option go_package = "google.golang.org/genproto/protobuf/field_mask;field_mask"; +option cc_enable_arenas = true; + +// `FieldMask` represents a set of symbolic field paths, for example: +// +// paths: "f.a" +// paths: "f.b.d" +// +// Here `f` represents a field in some root message, `a` and `b` +// fields in the message found in `f`, and `d` a field found in the +// message in `f.b`. +// +// Field masks are used to specify a subset of fields that should be +// returned by a get operation or modified by an update operation. +// Field masks also have a custom JSON encoding (see below). +// +// # Field Masks in Projections +// +// When used in the context of a projection, a response message or +// sub-message is filtered by the API to only contain those fields as +// specified in the mask. For example, if the mask in the previous +// example is applied to a response message as follows: +// +// f { +// a : 22 +// b { +// d : 1 +// x : 2 +// } +// y : 13 +// } +// z: 8 +// +// The result will not contain specific values for fields x,y and z +// (their value will be set to the default, and omitted in proto text +// output): +// +// +// f { +// a : 22 +// b { +// d : 1 +// } +// } +// +// A repeated field is not allowed except at the last position of a +// paths string. +// +// If a FieldMask object is not present in a get operation, the +// operation applies to all fields (as if a FieldMask of all fields +// had been specified). +// +// Note that a field mask does not necessarily apply to the +// top-level response message. In case of a REST get operation, the +// field mask applies directly to the response, but in case of a REST +// list operation, the mask instead applies to each individual message +// in the returned resource list. In case of a REST custom method, +// other definitions may be used. Where the mask applies will be +// clearly documented together with its declaration in the API. In +// any case, the effect on the returned resource/resources is required +// behavior for APIs. +// +// # Field Masks in Update Operations +// +// A field mask in update operations specifies which fields of the +// targeted resource are going to be updated. The API is required +// to only change the values of the fields as specified in the mask +// and leave the others untouched. If a resource is passed in to +// describe the updated values, the API ignores the values of all +// fields not covered by the mask. +// +// If a repeated field is specified for an update operation, new values will +// be appended to the existing repeated field in the target resource. Note that +// a repeated field is only allowed in the last position of a `paths` string. +// +// If a sub-message is specified in the last position of the field mask for an +// update operation, then new value will be merged into the existing sub-message +// in the target resource. +// +// For example, given the target message: +// +// f { +// b { +// d: 1 +// x: 2 +// } +// c: [1] +// } +// +// And an update message: +// +// f { +// b { +// d: 10 +// } +// c: [2] +// } +// +// then if the field mask is: +// +// paths: ["f.b", "f.c"] +// +// then the result will be: +// +// f { +// b { +// d: 10 +// x: 2 +// } +// c: [1, 2] +// } +// +// An implementation may provide options to override this default behavior for +// repeated and message fields. +// +// In order to reset a field's value to the default, the field must +// be in the mask and set to the default value in the provided resource. +// Hence, in order to reset all fields of a resource, provide a default +// instance of the resource and set all fields in the mask, or do +// not provide a mask as described below. +// +// If a field mask is not present on update, the operation applies to +// all fields (as if a field mask of all fields has been specified). +// Note that in the presence of schema evolution, this may mean that +// fields the client does not know and has therefore not filled into +// the request will be reset to their default. If this is unwanted +// behavior, a specific service may require a client to always specify +// a field mask, producing an error if not. +// +// As with get operations, the location of the resource which +// describes the updated values in the request message depends on the +// operation kind. In any case, the effect of the field mask is +// required to be honored by the API. +// +// ## Considerations for HTTP REST +// +// The HTTP kind of an update operation which uses a field mask must +// be set to PATCH instead of PUT in order to satisfy HTTP semantics +// (PUT must only be used for full updates). +// +// # JSON Encoding of Field Masks +// +// In JSON, a field mask is encoded as a single string where paths are +// separated by a comma. Fields name in each path are converted +// to/from lower-camel naming conventions. +// +// As an example, consider the following message declarations: +// +// message Profile { +// User user = 1; +// Photo photo = 2; +// } +// message User { +// string display_name = 1; +// string address = 2; +// } +// +// In proto a field mask for `Profile` may look as such: +// +// mask { +// paths: "user.display_name" +// paths: "photo" +// } +// +// In JSON, the same mask is represented as below: +// +// { +// mask: "user.displayName,photo" +// } +// +// # Field Masks and Oneof Fields +// +// Field masks treat fields in oneofs just as regular fields. Consider the +// following message: +// +// message SampleMessage { +// oneof test_oneof { +// string name = 4; +// SubMessage sub_message = 9; +// } +// } +// +// The field mask can be: +// +// mask { +// paths: "name" +// } +// +// Or: +// +// mask { +// paths: "sub_message" +// } +// +// Note that oneof type names ("test_oneof" in this case) cannot be used in +// paths. +// +// ## Field Mask Verification +// +// The implementation of any API method which has a FieldMask type field in the +// request should verify the included field paths, and return an +// `INVALID_ARGUMENT` error if any path is duplicated or unmappable. +message FieldMask { + // The set of field mask paths. + repeated string paths = 1; +} diff --git a/common/api/protoc/include/google/protobuf/source_context.proto b/common/api/protoc/include/google/protobuf/source_context.proto new file mode 100644 index 000000000..f3b2c9668 --- /dev/null +++ b/common/api/protoc/include/google/protobuf/source_context.proto @@ -0,0 +1,48 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "SourceContextProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option go_package = "google.golang.org/genproto/protobuf/source_context;source_context"; + +// `SourceContext` represents information about the source of a +// protobuf element, like the file in which it is defined. +message SourceContext { + // The path-qualified name of the .proto file that contained the associated + // protobuf element. For example: `"google/protobuf/source_context.proto"`. + string file_name = 1; +} diff --git a/common/api/protoc/include/google/protobuf/struct.proto b/common/api/protoc/include/google/protobuf/struct.proto new file mode 100644 index 000000000..ed990e31d --- /dev/null +++ b/common/api/protoc/include/google/protobuf/struct.proto @@ -0,0 +1,95 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option cc_enable_arenas = true; +option go_package = "github.com/golang/protobuf/ptypes/struct;structpb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "StructProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; + +// `Struct` represents a structured data value, consisting of fields +// which map to dynamically typed values. In some languages, `Struct` +// might be supported by a native representation. For example, in +// scripting languages like JS a struct is represented as an +// object. The details of that representation are described together +// with the proto support for the language. +// +// The JSON representation for `Struct` is JSON object. +message Struct { + // Unordered map of dynamically typed values. + map fields = 1; +} + +// `Value` represents a dynamically typed value which can be either +// null, a number, a string, a boolean, a recursive struct value, or a +// list of values. A producer of value is expected to set one of that +// variants, absence of any variant indicates an error. +// +// The JSON representation for `Value` is JSON value. +message Value { + // The kind of value. + oneof kind { + // Represents a null value. + NullValue null_value = 1; + // Represents a double value. + double number_value = 2; + // Represents a string value. + string string_value = 3; + // Represents a boolean value. + bool bool_value = 4; + // Represents a structured value. + Struct struct_value = 5; + // Represents a repeated `Value`. + ListValue list_value = 6; + } +} + +// `NullValue` is a singleton enumeration to represent the null value for the +// `Value` type union. +// +// The JSON representation for `NullValue` is JSON `null`. +enum NullValue { + // Null value. + NULL_VALUE = 0; +} + +// `ListValue` is a wrapper around a repeated field of values. +// +// The JSON representation for `ListValue` is JSON array. +message ListValue { + // Repeated field of dynamically typed values. + repeated Value values = 1; +} diff --git a/common/api/protoc/include/google/protobuf/timestamp.proto b/common/api/protoc/include/google/protobuf/timestamp.proto new file mode 100644 index 000000000..cd357864a --- /dev/null +++ b/common/api/protoc/include/google/protobuf/timestamp.proto @@ -0,0 +1,138 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option cc_enable_arenas = true; +option go_package = "github.com/golang/protobuf/ptypes/timestamp"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "TimestampProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; + +// A Timestamp represents a point in time independent of any time zone or local +// calendar, encoded as a count of seconds and fractions of seconds at +// nanosecond resolution. The count is relative to an epoch at UTC midnight on +// January 1, 1970, in the proleptic Gregorian calendar which extends the +// Gregorian calendar backwards to year one. +// +// All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap +// second table is needed for interpretation, using a [24-hour linear +// smear](https://developers.google.com/time/smear). +// +// The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By +// restricting to that range, we ensure that we can convert to and from [RFC +// 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. +// +// # Examples +// +// Example 1: Compute Timestamp from POSIX `time()`. +// +// Timestamp timestamp; +// timestamp.set_seconds(time(NULL)); +// timestamp.set_nanos(0); +// +// Example 2: Compute Timestamp from POSIX `gettimeofday()`. +// +// struct timeval tv; +// gettimeofday(&tv, NULL); +// +// Timestamp timestamp; +// timestamp.set_seconds(tv.tv_sec); +// timestamp.set_nanos(tv.tv_usec * 1000); +// +// Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. +// +// FILETIME ft; +// GetSystemTimeAsFileTime(&ft); +// UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; +// +// // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z +// // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. +// Timestamp timestamp; +// timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); +// timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); +// +// Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. +// +// long millis = System.currentTimeMillis(); +// +// Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) +// .setNanos((int) ((millis % 1000) * 1000000)).build(); +// +// +// Example 5: Compute Timestamp from current time in Python. +// +// timestamp = Timestamp() +// timestamp.GetCurrentTime() +// +// # JSON Mapping +// +// In JSON format, the Timestamp type is encoded as a string in the +// [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the +// format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" +// where {year} is always expressed using four digits while {month}, {day}, +// {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional +// seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), +// are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone +// is required. A proto3 JSON serializer should always use UTC (as indicated by +// "Z") when printing the Timestamp type and a proto3 JSON parser should be +// able to accept both UTC and other timezones (as indicated by an offset). +// +// For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past +// 01:30 UTC on January 15, 2017. +// +// In JavaScript, one can convert a Date object to this format using the +// standard +// [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) +// method. In Python, a standard `datetime.datetime` object can be converted +// to this format using +// [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with +// the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use +// the Joda Time's [`ISODateTimeFormat.dateTime()`]( +// http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D +// ) to obtain a formatter capable of generating timestamps in this format. +// +// +message Timestamp { + // Represents seconds of UTC time since Unix epoch + // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to + // 9999-12-31T23:59:59Z inclusive. + int64 seconds = 1; + + // Non-negative fractions of a second at nanosecond resolution. Negative + // second values with fractions must still have non-negative nanos values + // that count forward in time. Must be from 0 to 999,999,999 + // inclusive. + int32 nanos = 2; +} diff --git a/common/api/protoc/include/google/protobuf/type.proto b/common/api/protoc/include/google/protobuf/type.proto new file mode 100644 index 000000000..e4b1d3a4a --- /dev/null +++ b/common/api/protoc/include/google/protobuf/type.proto @@ -0,0 +1,187 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +import "google/protobuf/any.proto"; +import "google/protobuf/source_context.proto"; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option cc_enable_arenas = true; +option java_package = "com.google.protobuf"; +option java_outer_classname = "TypeProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option go_package = "google.golang.org/genproto/protobuf/ptype;ptype"; + +// A protocol buffer message type. +message Type { + // The fully qualified message name. + string name = 1; + // The list of fields. + repeated Field fields = 2; + // The list of types appearing in `oneof` definitions in this type. + repeated string oneofs = 3; + // The protocol buffer options. + repeated Option options = 4; + // The source context. + SourceContext source_context = 5; + // The source syntax. + Syntax syntax = 6; +} + +// A single field of a message type. +message Field { + // Basic field types. + enum Kind { + // Field type unknown. + TYPE_UNKNOWN = 0; + // Field type double. + TYPE_DOUBLE = 1; + // Field type float. + TYPE_FLOAT = 2; + // Field type int64. + TYPE_INT64 = 3; + // Field type uint64. + TYPE_UINT64 = 4; + // Field type int32. + TYPE_INT32 = 5; + // Field type fixed64. + TYPE_FIXED64 = 6; + // Field type fixed32. + TYPE_FIXED32 = 7; + // Field type bool. + TYPE_BOOL = 8; + // Field type string. + TYPE_STRING = 9; + // Field type group. Proto2 syntax only, and deprecated. + TYPE_GROUP = 10; + // Field type message. + TYPE_MESSAGE = 11; + // Field type bytes. + TYPE_BYTES = 12; + // Field type uint32. + TYPE_UINT32 = 13; + // Field type enum. + TYPE_ENUM = 14; + // Field type sfixed32. + TYPE_SFIXED32 = 15; + // Field type sfixed64. + TYPE_SFIXED64 = 16; + // Field type sint32. + TYPE_SINT32 = 17; + // Field type sint64. + TYPE_SINT64 = 18; + } + + // Whether a field is optional, required, or repeated. + enum Cardinality { + // For fields with unknown cardinality. + CARDINALITY_UNKNOWN = 0; + // For optional fields. + CARDINALITY_OPTIONAL = 1; + // For required fields. Proto2 syntax only. + CARDINALITY_REQUIRED = 2; + // For repeated fields. + CARDINALITY_REPEATED = 3; + }; + + // The field type. + Kind kind = 1; + // The field cardinality. + Cardinality cardinality = 2; + // The field number. + int32 number = 3; + // The field name. + string name = 4; + // The field type URL, without the scheme, for message or enumeration + // types. Example: `"type.googleapis.com/google.protobuf.Timestamp"`. + string type_url = 6; + // The index of the field type in `Type.oneofs`, for message or enumeration + // types. The first type has index 1; zero means the type is not in the list. + int32 oneof_index = 7; + // Whether to use alternative packed wire representation. + bool packed = 8; + // The protocol buffer options. + repeated Option options = 9; + // The field JSON name. + string json_name = 10; + // The string value of the default value of this field. Proto2 syntax only. + string default_value = 11; +} + +// Enum type definition. +message Enum { + // Enum type name. + string name = 1; + // Enum value definitions. + repeated EnumValue enumvalue = 2; + // Protocol buffer options. + repeated Option options = 3; + // The source context. + SourceContext source_context = 4; + // The source syntax. + Syntax syntax = 5; +} + +// Enum value definition. +message EnumValue { + // Enum value name. + string name = 1; + // Enum value number. + int32 number = 2; + // Protocol buffer options. + repeated Option options = 3; +} + +// A protocol buffer option, which can be attached to a message, field, +// enumeration, etc. +message Option { + // The option's name. For protobuf built-in options (options defined in + // descriptor.proto), this is the short name. For example, `"map_entry"`. + // For custom options, it should be the fully-qualified name. For example, + // `"google.api.http"`. + string name = 1; + // The option's value packed in an Any message. If the value is a primitive, + // the corresponding wrapper type defined in google/protobuf/wrappers.proto + // should be used. If the value is an enum, it should be stored as an int32 + // value using the google.protobuf.Int32Value type. + Any value = 2; +} + +// The syntax in which a protocol buffer element is defined. +enum Syntax { + // Syntax `proto2`. + SYNTAX_PROTO2 = 0; + // Syntax `proto3`. + SYNTAX_PROTO3 = 1; +} diff --git a/common/api/protoc/include/google/protobuf/wrappers.proto b/common/api/protoc/include/google/protobuf/wrappers.proto new file mode 100644 index 000000000..9ee41e384 --- /dev/null +++ b/common/api/protoc/include/google/protobuf/wrappers.proto @@ -0,0 +1,123 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Wrappers for primitive (non-message) types. These types are useful +// for embedding primitives in the `google.protobuf.Any` type and for places +// where we need to distinguish between the absence of a primitive +// typed field and its default value. +// +// These wrappers have no meaningful use within repeated fields as they lack +// the ability to detect presence on individual elements. +// These wrappers have no meaningful use within a map or a oneof since +// individual entries of a map or fields of a oneof can already detect presence. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option cc_enable_arenas = true; +option go_package = "github.com/golang/protobuf/ptypes/wrappers"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "WrappersProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; + +// Wrapper message for `double`. +// +// The JSON representation for `DoubleValue` is JSON number. +message DoubleValue { + // The double value. + double value = 1; +} + +// Wrapper message for `float`. +// +// The JSON representation for `FloatValue` is JSON number. +message FloatValue { + // The float value. + float value = 1; +} + +// Wrapper message for `int64`. +// +// The JSON representation for `Int64Value` is JSON string. +message Int64Value { + // The int64 value. + int64 value = 1; +} + +// Wrapper message for `uint64`. +// +// The JSON representation for `UInt64Value` is JSON string. +message UInt64Value { + // The uint64 value. + uint64 value = 1; +} + +// Wrapper message for `int32`. +// +// The JSON representation for `Int32Value` is JSON number. +message Int32Value { + // The int32 value. + int32 value = 1; +} + +// Wrapper message for `uint32`. +// +// The JSON representation for `UInt32Value` is JSON number. +message UInt32Value { + // The uint32 value. + uint32 value = 1; +} + +// Wrapper message for `bool`. +// +// The JSON representation for `BoolValue` is JSON `true` and `false`. +message BoolValue { + // The bool value. + bool value = 1; +} + +// Wrapper message for `string`. +// +// The JSON representation for `StringValue` is JSON string. +message StringValue { + // The string value. + string value = 1; +} + +// Wrapper message for `bytes`. +// +// The JSON representation for `BytesValue` is JSON string. +message BytesValue { + // The bytes value. + bytes value = 1; +} diff --git a/common/api/protoc/readme.txt b/common/api/protoc/readme.txt new file mode 100644 index 000000000..b6c9f9b48 --- /dev/null +++ b/common/api/protoc/readme.txt @@ -0,0 +1,15 @@ +Protocol Buffers - Google's data interchange format +Copyright 2008 Google Inc. +https://developers.google.com/protocol-buffers/ + +This package contains a precompiled binary version of the protocol buffer +compiler (protoc). This binary is intended for users who want to use Protocol +Buffers in languages other than C++ but do not want to compile protoc +themselves. To install, simply place this binary somewhere in your PATH. + +If you intend to use the included well known types then don't forget to +copy the contents of the 'include' directory somewhere as well, for example +into '/usr/local/include/'. + +Please refer to our official github site for more installation instructions: + https://github.com/protocolbuffers/protobuf diff --git a/common/api/v1/build.sh b/common/api/v1/build.sh new file mode 100644 index 000000000..72c82fa61 --- /dev/null +++ b/common/api/v1/build.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# 安装protoc和protoc-gen-go插件 +# +# 注意: +# grpc包引入github.com/golang/protobuf/proto v1.2.0 +# protoc-gen-go插件和引入proto包的版本必须保持一致 +# +# github.com/golang/protobuf/ +# protoc-gen-go:在pb.go文件中插入proto.ProtoPackageIsVersionX +# proto:在lib.go中定义ProtoPackageIsVersionX +# +# ProtoPackageIsVersion并非表示proto2/proto3 + +PROTOC=../protoc + +${PROTOC}/bin/protoc \ +--plugin=protoc-gen-go=${PROTOC}/bin/protoc-gen-go \ +--go_out=plugins=grpc:. \ +--proto_path=${PROTOC}/include \ +--proto_path=. \ +model.proto client.proto service.proto routing.proto ratelimit.proto circuitbreaker.proto configrelease.proto \ +platform.proto request.proto response.proto grpcapi.proto \ No newline at end of file diff --git a/common/api/v1/circuitbreaker.pb.go b/common/api/v1/circuitbreaker.pb.go new file mode 100644 index 000000000..f9141c7dd --- /dev/null +++ b/common/api/v1/circuitbreaker.pb.go @@ -0,0 +1,1081 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: circuitbreaker.proto + +package v1 + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import duration "github.com/golang/protobuf/ptypes/duration" +import wrappers "github.com/golang/protobuf/ptypes/wrappers" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +// 主动探测配置 +type RecoverConfig_OutlierDetectWhen int32 + +const ( + // 不开启监控探测 + RecoverConfig_NEVER RecoverConfig_OutlierDetectWhen = 0 + // 只有在熔断恢复时才开启健康探测 + RecoverConfig_ON_RECOVER RecoverConfig_OutlierDetectWhen = 1 + // 一直开启健康探测 + RecoverConfig_ALWAYS RecoverConfig_OutlierDetectWhen = 2 +) + +var RecoverConfig_OutlierDetectWhen_name = map[int32]string{ + 0: "NEVER", + 1: "ON_RECOVER", + 2: "ALWAYS", +} +var RecoverConfig_OutlierDetectWhen_value = map[string]int32{ + "NEVER": 0, + "ON_RECOVER": 1, + "ALWAYS": 2, +} + +func (x RecoverConfig_OutlierDetectWhen) String() string { + return proto.EnumName(RecoverConfig_OutlierDetectWhen_name, int32(x)) +} +func (RecoverConfig_OutlierDetectWhen) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_circuitbreaker_893772a31588c4ee, []int{2, 0} +} + +// 需要进行熔断的资源 +// 支持SUBSET(子集群),以及INSTANCE(单个实例),默认为SUBSET +type DestinationSet_Resource int32 + +const ( + // 针对实例分组进行熔断 + DestinationSet_SUBSET DestinationSet_Resource = 0 + // 针对实例进行熔断 + DestinationSet_INSTANCE DestinationSet_Resource = 1 +) + +var DestinationSet_Resource_name = map[int32]string{ + 0: "SUBSET", + 1: "INSTANCE", +} +var DestinationSet_Resource_value = map[string]int32{ + "SUBSET": 0, + "INSTANCE": 1, +} + +func (x DestinationSet_Resource) String() string { + return proto.EnumName(DestinationSet_Resource_name, int32(x)) +} +func (DestinationSet_Resource) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_circuitbreaker_893772a31588c4ee, []int{4, 0} +} + +// 熔断决策类型,支持GLOBAL(分布式决策)以及LOCAL(本地决策),默认GLOBAL +// 当指定为GLOBAL时,则会定期上报统计数据并根据汇总数据进行熔断决策 +type DestinationSet_Type int32 + +const ( + DestinationSet_GLOBAL DestinationSet_Type = 0 + DestinationSet_LOCAL DestinationSet_Type = 1 +) + +var DestinationSet_Type_name = map[int32]string{ + 0: "GLOBAL", + 1: "LOCAL", +} +var DestinationSet_Type_value = map[string]int32{ + "GLOBAL": 0, + "LOCAL": 1, +} + +func (x DestinationSet_Type) String() string { + return proto.EnumName(DestinationSet_Type_name, int32(x)) +} +func (DestinationSet_Type) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_circuitbreaker_893772a31588c4ee, []int{4, 1} +} + +// 熔断范围,是否扩散针对相同服务下所有接口进行熔断 +type DestinationSet_Scope int32 + +const ( + // 触发熔断条件,扩散熔断所有接口 + DestinationSet_ALL DestinationSet_Scope = 0 + // 触发熔断条件,只熔断当前接口 + DestinationSet_CURRENT DestinationSet_Scope = 1 +) + +var DestinationSet_Scope_name = map[int32]string{ + 0: "ALL", + 1: "CURRENT", +} +var DestinationSet_Scope_value = map[string]int32{ + "ALL": 0, + "CURRENT": 1, +} + +func (x DestinationSet_Scope) String() string { + return proto.EnumName(DestinationSet_Scope_name, int32(x)) +} +func (DestinationSet_Scope) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_circuitbreaker_893772a31588c4ee, []int{4, 2} +} + +// 单个熔断规则定义 +type CircuitBreaker struct { + Id *wrappers.StringValue `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // 规则版本 + Version *wrappers.StringValue `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` + // 规则名 + Name *wrappers.StringValue `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + // 规则命名空间 + Namespace *wrappers.StringValue `protobuf:"bytes,4,opt,name=namespace,proto3" json:"namespace,omitempty"` + // 规则所属服务 + Service *wrappers.StringValue `protobuf:"bytes,5,opt,name=service,proto3" json:"service,omitempty"` + ServiceNamespace *wrappers.StringValue `protobuf:"bytes,6,opt,name=service_namespace,json=serviceNamespace,proto3" json:"service_namespace,omitempty"` + // 熔断规则可以分为被调规则和主调规则 + // 被调规则针对所有的指定主调生效,假如不指定则对所有的主调生效 + // 主调规则为当前主调方的规则,假如不指定则针对所有被调生效 + Inbounds []*CbRule `protobuf:"bytes,7,rep,name=inbounds,proto3" json:"inbounds,omitempty"` + Outbounds []*CbRule `protobuf:"bytes,8,rep,name=outbounds,proto3" json:"outbounds,omitempty"` + Token *wrappers.StringValue `protobuf:"bytes,9,opt,name=token,proto3" json:"token,omitempty"` + Owners *wrappers.StringValue `protobuf:"bytes,10,opt,name=owners,proto3" json:"owners,omitempty"` + // 业务 + Business *wrappers.StringValue `protobuf:"bytes,11,opt,name=business,proto3" json:"business,omitempty"` + // 部门 + Department *wrappers.StringValue `protobuf:"bytes,12,opt,name=department,proto3" json:"department,omitempty"` + // 规则描述 + Comment *wrappers.StringValue `protobuf:"bytes,13,opt,name=comment,proto3" json:"comment,omitempty"` + Ctime *wrappers.StringValue `protobuf:"bytes,14,opt,name=ctime,proto3" json:"ctime,omitempty"` + Mtime *wrappers.StringValue `protobuf:"bytes,15,opt,name=mtime,proto3" json:"mtime,omitempty"` + Revision *wrappers.StringValue `protobuf:"bytes,16,opt,name=revision,proto3" json:"revision,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CircuitBreaker) Reset() { *m = CircuitBreaker{} } +func (m *CircuitBreaker) String() string { return proto.CompactTextString(m) } +func (*CircuitBreaker) ProtoMessage() {} +func (*CircuitBreaker) Descriptor() ([]byte, []int) { + return fileDescriptor_circuitbreaker_893772a31588c4ee, []int{0} +} +func (m *CircuitBreaker) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CircuitBreaker.Unmarshal(m, b) +} +func (m *CircuitBreaker) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CircuitBreaker.Marshal(b, m, deterministic) +} +func (dst *CircuitBreaker) XXX_Merge(src proto.Message) { + xxx_messageInfo_CircuitBreaker.Merge(dst, src) +} +func (m *CircuitBreaker) XXX_Size() int { + return xxx_messageInfo_CircuitBreaker.Size(m) +} +func (m *CircuitBreaker) XXX_DiscardUnknown() { + xxx_messageInfo_CircuitBreaker.DiscardUnknown(m) +} + +var xxx_messageInfo_CircuitBreaker proto.InternalMessageInfo + +func (m *CircuitBreaker) GetId() *wrappers.StringValue { + if m != nil { + return m.Id + } + return nil +} + +func (m *CircuitBreaker) GetVersion() *wrappers.StringValue { + if m != nil { + return m.Version + } + return nil +} + +func (m *CircuitBreaker) GetName() *wrappers.StringValue { + if m != nil { + return m.Name + } + return nil +} + +func (m *CircuitBreaker) GetNamespace() *wrappers.StringValue { + if m != nil { + return m.Namespace + } + return nil +} + +func (m *CircuitBreaker) GetService() *wrappers.StringValue { + if m != nil { + return m.Service + } + return nil +} + +func (m *CircuitBreaker) GetServiceNamespace() *wrappers.StringValue { + if m != nil { + return m.ServiceNamespace + } + return nil +} + +func (m *CircuitBreaker) GetInbounds() []*CbRule { + if m != nil { + return m.Inbounds + } + return nil +} + +func (m *CircuitBreaker) GetOutbounds() []*CbRule { + if m != nil { + return m.Outbounds + } + return nil +} + +func (m *CircuitBreaker) GetToken() *wrappers.StringValue { + if m != nil { + return m.Token + } + return nil +} + +func (m *CircuitBreaker) GetOwners() *wrappers.StringValue { + if m != nil { + return m.Owners + } + return nil +} + +func (m *CircuitBreaker) GetBusiness() *wrappers.StringValue { + if m != nil { + return m.Business + } + return nil +} + +func (m *CircuitBreaker) GetDepartment() *wrappers.StringValue { + if m != nil { + return m.Department + } + return nil +} + +func (m *CircuitBreaker) GetComment() *wrappers.StringValue { + if m != nil { + return m.Comment + } + return nil +} + +func (m *CircuitBreaker) GetCtime() *wrappers.StringValue { + if m != nil { + return m.Ctime + } + return nil +} + +func (m *CircuitBreaker) GetMtime() *wrappers.StringValue { + if m != nil { + return m.Mtime + } + return nil +} + +func (m *CircuitBreaker) GetRevision() *wrappers.StringValue { + if m != nil { + return m.Revision + } + return nil +} + +// 主调匹配规则 +type SourceMatcher struct { + // 主调命名空间以及服务名,可以为*,代表全匹配 + Service *wrappers.StringValue `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` + Namespace *wrappers.StringValue `protobuf:"bytes,2,opt,name=namespace,proto3" json:"namespace,omitempty"` + // 可选,主调业务标签,用于匹配是否使用该熔断规则,可放置用户的接口信息等 + Labels map[string]*MatchString `protobuf:"bytes,3,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SourceMatcher) Reset() { *m = SourceMatcher{} } +func (m *SourceMatcher) String() string { return proto.CompactTextString(m) } +func (*SourceMatcher) ProtoMessage() {} +func (*SourceMatcher) Descriptor() ([]byte, []int) { + return fileDescriptor_circuitbreaker_893772a31588c4ee, []int{1} +} +func (m *SourceMatcher) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SourceMatcher.Unmarshal(m, b) +} +func (m *SourceMatcher) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SourceMatcher.Marshal(b, m, deterministic) +} +func (dst *SourceMatcher) XXX_Merge(src proto.Message) { + xxx_messageInfo_SourceMatcher.Merge(dst, src) +} +func (m *SourceMatcher) XXX_Size() int { + return xxx_messageInfo_SourceMatcher.Size(m) +} +func (m *SourceMatcher) XXX_DiscardUnknown() { + xxx_messageInfo_SourceMatcher.DiscardUnknown(m) +} + +var xxx_messageInfo_SourceMatcher proto.InternalMessageInfo + +func (m *SourceMatcher) GetService() *wrappers.StringValue { + if m != nil { + return m.Service + } + return nil +} + +func (m *SourceMatcher) GetNamespace() *wrappers.StringValue { + if m != nil { + return m.Namespace + } + return nil +} + +func (m *SourceMatcher) GetLabels() map[string]*MatchString { + if m != nil { + return m.Labels + } + return nil +} + +// 熔断恢复配置 +type RecoverConfig struct { + // 触发熔断后到半开状态之间的等待间隔 + SleepWindow *duration.Duration `protobuf:"bytes,1,opt,name=sleepWindow,proto3" json:"sleepWindow,omitempty"` + // 半开后,最多重试多少次恢复 + MaxRetryAfterHalfOpen *wrappers.UInt32Value `protobuf:"bytes,2,opt,name=maxRetryAfterHalfOpen,proto3" json:"maxRetryAfterHalfOpen,omitempty"` + // 半开后放量的最大百分比 + RequestRateAfterHalfOpen []*wrappers.UInt32Value `protobuf:"bytes,3,rep,name=requestRateAfterHalfOpen,proto3" json:"requestRateAfterHalfOpen,omitempty"` + // 熔断器半开到关闭所必须的最少成功率,默认100% + SuccessRateToClose *wrappers.UInt32Value `protobuf:"bytes,4,opt,name=successRateToClose,proto3" json:"successRateToClose,omitempty"` + // 半开后最大放量数(用户不配置最大百分比时默认使用该配置) + RequestCountAfterHalfOpen *wrappers.UInt32Value `protobuf:"bytes,5,opt,name=requestCountAfterHalfOpen,proto3" json:"requestCountAfterHalfOpen,omitempty"` + OutlierDetectWhen RecoverConfig_OutlierDetectWhen `protobuf:"varint,6,opt,name=outlierDetectWhen,proto3,enum=v1.RecoverConfig_OutlierDetectWhen" json:"outlierDetectWhen,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *RecoverConfig) Reset() { *m = RecoverConfig{} } +func (m *RecoverConfig) String() string { return proto.CompactTextString(m) } +func (*RecoverConfig) ProtoMessage() {} +func (*RecoverConfig) Descriptor() ([]byte, []int) { + return fileDescriptor_circuitbreaker_893772a31588c4ee, []int{2} +} +func (m *RecoverConfig) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_RecoverConfig.Unmarshal(m, b) +} +func (m *RecoverConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_RecoverConfig.Marshal(b, m, deterministic) +} +func (dst *RecoverConfig) XXX_Merge(src proto.Message) { + xxx_messageInfo_RecoverConfig.Merge(dst, src) +} +func (m *RecoverConfig) XXX_Size() int { + return xxx_messageInfo_RecoverConfig.Size(m) +} +func (m *RecoverConfig) XXX_DiscardUnknown() { + xxx_messageInfo_RecoverConfig.DiscardUnknown(m) +} + +var xxx_messageInfo_RecoverConfig proto.InternalMessageInfo + +func (m *RecoverConfig) GetSleepWindow() *duration.Duration { + if m != nil { + return m.SleepWindow + } + return nil +} + +func (m *RecoverConfig) GetMaxRetryAfterHalfOpen() *wrappers.UInt32Value { + if m != nil { + return m.MaxRetryAfterHalfOpen + } + return nil +} + +func (m *RecoverConfig) GetRequestRateAfterHalfOpen() []*wrappers.UInt32Value { + if m != nil { + return m.RequestRateAfterHalfOpen + } + return nil +} + +func (m *RecoverConfig) GetSuccessRateToClose() *wrappers.UInt32Value { + if m != nil { + return m.SuccessRateToClose + } + return nil +} + +func (m *RecoverConfig) GetRequestCountAfterHalfOpen() *wrappers.UInt32Value { + if m != nil { + return m.RequestCountAfterHalfOpen + } + return nil +} + +func (m *RecoverConfig) GetOutlierDetectWhen() RecoverConfig_OutlierDetectWhen { + if m != nil { + return m.OutlierDetectWhen + } + return RecoverConfig_NEVER +} + +// 熔断策略 +type CbPolicy struct { + ErrorRate *CbPolicy_ErrRateConfig `protobuf:"bytes,1,opt,name=errorRate,proto3" json:"errorRate,omitempty"` + SlowRate *CbPolicy_SlowRateConfig `protobuf:"bytes,2,opt,name=slowRate,proto3" json:"slowRate,omitempty"` + // 熔断的决策周期,多久触发一次熔断决策 + JudgeDuration *duration.Duration `protobuf:"bytes,3,opt,name=judgeDuration,proto3" json:"judgeDuration,omitempty"` + // 最大熔断比例,超过多少比例后不会继续熔断 + MaxEjectionPercent *wrappers.UInt32Value `protobuf:"bytes,4,opt,name=maxEjectionPercent,proto3" json:"maxEjectionPercent,omitempty"` + Consecutive *CbPolicy_ConsecutiveErrConfig `protobuf:"bytes,5,opt,name=consecutive,proto3" json:"consecutive,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CbPolicy) Reset() { *m = CbPolicy{} } +func (m *CbPolicy) String() string { return proto.CompactTextString(m) } +func (*CbPolicy) ProtoMessage() {} +func (*CbPolicy) Descriptor() ([]byte, []int) { + return fileDescriptor_circuitbreaker_893772a31588c4ee, []int{3} +} +func (m *CbPolicy) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CbPolicy.Unmarshal(m, b) +} +func (m *CbPolicy) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CbPolicy.Marshal(b, m, deterministic) +} +func (dst *CbPolicy) XXX_Merge(src proto.Message) { + xxx_messageInfo_CbPolicy.Merge(dst, src) +} +func (m *CbPolicy) XXX_Size() int { + return xxx_messageInfo_CbPolicy.Size(m) +} +func (m *CbPolicy) XXX_DiscardUnknown() { + xxx_messageInfo_CbPolicy.DiscardUnknown(m) +} + +var xxx_messageInfo_CbPolicy proto.InternalMessageInfo + +func (m *CbPolicy) GetErrorRate() *CbPolicy_ErrRateConfig { + if m != nil { + return m.ErrorRate + } + return nil +} + +func (m *CbPolicy) GetSlowRate() *CbPolicy_SlowRateConfig { + if m != nil { + return m.SlowRate + } + return nil +} + +func (m *CbPolicy) GetJudgeDuration() *duration.Duration { + if m != nil { + return m.JudgeDuration + } + return nil +} + +func (m *CbPolicy) GetMaxEjectionPercent() *wrappers.UInt32Value { + if m != nil { + return m.MaxEjectionPercent + } + return nil +} + +func (m *CbPolicy) GetConsecutive() *CbPolicy_ConsecutiveErrConfig { + if m != nil { + return m.Consecutive + } + return nil +} + +// 错误率熔断配置 +type CbPolicy_ErrRateConfig struct { + // 是否启用错误率配置 + Enable *wrappers.BoolValue `protobuf:"bytes,1,opt,name=enable,proto3" json:"enable,omitempty"` + // 触发错误率熔断的最低请求阈值 + RequestVolumeThreshold *wrappers.UInt32Value `protobuf:"bytes,2,opt,name=requestVolumeThreshold,proto3" json:"requestVolumeThreshold,omitempty"` + // 可选。触发保持状态的错误率阈值,假如不配置,则默认不会进入Preserved状态 + ErrorRateToPreserved *wrappers.UInt32Value `protobuf:"bytes,3,opt,name=errorRateToPreserved,proto3" json:"errorRateToPreserved,omitempty"` + // 触发熔断的错误率阈值 + ErrorRateToOpen *wrappers.UInt32Value `protobuf:"bytes,4,opt,name=errorRateToOpen,proto3" json:"errorRateToOpen,omitempty"` + Specials []*CbPolicy_ErrRateConfig_SpecialConfig `protobuf:"bytes,5,rep,name=specials,proto3" json:"specials,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CbPolicy_ErrRateConfig) Reset() { *m = CbPolicy_ErrRateConfig{} } +func (m *CbPolicy_ErrRateConfig) String() string { return proto.CompactTextString(m) } +func (*CbPolicy_ErrRateConfig) ProtoMessage() {} +func (*CbPolicy_ErrRateConfig) Descriptor() ([]byte, []int) { + return fileDescriptor_circuitbreaker_893772a31588c4ee, []int{3, 0} +} +func (m *CbPolicy_ErrRateConfig) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CbPolicy_ErrRateConfig.Unmarshal(m, b) +} +func (m *CbPolicy_ErrRateConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CbPolicy_ErrRateConfig.Marshal(b, m, deterministic) +} +func (dst *CbPolicy_ErrRateConfig) XXX_Merge(src proto.Message) { + xxx_messageInfo_CbPolicy_ErrRateConfig.Merge(dst, src) +} +func (m *CbPolicy_ErrRateConfig) XXX_Size() int { + return xxx_messageInfo_CbPolicy_ErrRateConfig.Size(m) +} +func (m *CbPolicy_ErrRateConfig) XXX_DiscardUnknown() { + xxx_messageInfo_CbPolicy_ErrRateConfig.DiscardUnknown(m) +} + +var xxx_messageInfo_CbPolicy_ErrRateConfig proto.InternalMessageInfo + +func (m *CbPolicy_ErrRateConfig) GetEnable() *wrappers.BoolValue { + if m != nil { + return m.Enable + } + return nil +} + +func (m *CbPolicy_ErrRateConfig) GetRequestVolumeThreshold() *wrappers.UInt32Value { + if m != nil { + return m.RequestVolumeThreshold + } + return nil +} + +func (m *CbPolicy_ErrRateConfig) GetErrorRateToPreserved() *wrappers.UInt32Value { + if m != nil { + return m.ErrorRateToPreserved + } + return nil +} + +func (m *CbPolicy_ErrRateConfig) GetErrorRateToOpen() *wrappers.UInt32Value { + if m != nil { + return m.ErrorRateToOpen + } + return nil +} + +func (m *CbPolicy_ErrRateConfig) GetSpecials() []*CbPolicy_ErrRateConfig_SpecialConfig { + if m != nil { + return m.Specials + } + return nil +} + +// 错误码相关特定配置 +type CbPolicy_ErrRateConfig_SpecialConfig struct { + // 熔断关心的错误类型,用户可以自己定义 + Type *wrappers.StringValue `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + ErrorCodes []*wrappers.Int64Value `protobuf:"bytes,2,rep,name=errorCodes,proto3" json:"errorCodes,omitempty"` + ErrorRateToPreserved *wrappers.UInt32Value `protobuf:"bytes,3,opt,name=errorRateToPreserved,proto3" json:"errorRateToPreserved,omitempty"` + ErrorRateToOpen *wrappers.UInt32Value `protobuf:"bytes,4,opt,name=errorRateToOpen,proto3" json:"errorRateToOpen,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CbPolicy_ErrRateConfig_SpecialConfig) Reset() { *m = CbPolicy_ErrRateConfig_SpecialConfig{} } +func (m *CbPolicy_ErrRateConfig_SpecialConfig) String() string { return proto.CompactTextString(m) } +func (*CbPolicy_ErrRateConfig_SpecialConfig) ProtoMessage() {} +func (*CbPolicy_ErrRateConfig_SpecialConfig) Descriptor() ([]byte, []int) { + return fileDescriptor_circuitbreaker_893772a31588c4ee, []int{3, 0, 0} +} +func (m *CbPolicy_ErrRateConfig_SpecialConfig) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CbPolicy_ErrRateConfig_SpecialConfig.Unmarshal(m, b) +} +func (m *CbPolicy_ErrRateConfig_SpecialConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CbPolicy_ErrRateConfig_SpecialConfig.Marshal(b, m, deterministic) +} +func (dst *CbPolicy_ErrRateConfig_SpecialConfig) XXX_Merge(src proto.Message) { + xxx_messageInfo_CbPolicy_ErrRateConfig_SpecialConfig.Merge(dst, src) +} +func (m *CbPolicy_ErrRateConfig_SpecialConfig) XXX_Size() int { + return xxx_messageInfo_CbPolicy_ErrRateConfig_SpecialConfig.Size(m) +} +func (m *CbPolicy_ErrRateConfig_SpecialConfig) XXX_DiscardUnknown() { + xxx_messageInfo_CbPolicy_ErrRateConfig_SpecialConfig.DiscardUnknown(m) +} + +var xxx_messageInfo_CbPolicy_ErrRateConfig_SpecialConfig proto.InternalMessageInfo + +func (m *CbPolicy_ErrRateConfig_SpecialConfig) GetType() *wrappers.StringValue { + if m != nil { + return m.Type + } + return nil +} + +func (m *CbPolicy_ErrRateConfig_SpecialConfig) GetErrorCodes() []*wrappers.Int64Value { + if m != nil { + return m.ErrorCodes + } + return nil +} + +func (m *CbPolicy_ErrRateConfig_SpecialConfig) GetErrorRateToPreserved() *wrappers.UInt32Value { + if m != nil { + return m.ErrorRateToPreserved + } + return nil +} + +func (m *CbPolicy_ErrRateConfig_SpecialConfig) GetErrorRateToOpen() *wrappers.UInt32Value { + if m != nil { + return m.ErrorRateToOpen + } + return nil +} + +// 慢调用率熔断策略配置 +type CbPolicy_SlowRateConfig struct { + // 是否启用慢调用率配置 + Enable *wrappers.BoolValue `protobuf:"bytes,1,opt,name=enable,proto3" json:"enable,omitempty"` + // 最大响应时间,超过该时间属于慢调用请求 + MaxRt *duration.Duration `protobuf:"bytes,2,opt,name=maxRt,proto3" json:"maxRt,omitempty"` + // 可选。触发保持状态的超时率阈值,假如不配置,则默认不会进入Preserved状态 + SlowRateToPreserved *wrappers.UInt32Value `protobuf:"bytes,3,opt,name=slowRateToPreserved,proto3" json:"slowRateToPreserved,omitempty"` + // 触发熔断的超时率阈值 + SlowRateToOpen *wrappers.UInt32Value `protobuf:"bytes,4,opt,name=slowRateToOpen,proto3" json:"slowRateToOpen,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CbPolicy_SlowRateConfig) Reset() { *m = CbPolicy_SlowRateConfig{} } +func (m *CbPolicy_SlowRateConfig) String() string { return proto.CompactTextString(m) } +func (*CbPolicy_SlowRateConfig) ProtoMessage() {} +func (*CbPolicy_SlowRateConfig) Descriptor() ([]byte, []int) { + return fileDescriptor_circuitbreaker_893772a31588c4ee, []int{3, 1} +} +func (m *CbPolicy_SlowRateConfig) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CbPolicy_SlowRateConfig.Unmarshal(m, b) +} +func (m *CbPolicy_SlowRateConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CbPolicy_SlowRateConfig.Marshal(b, m, deterministic) +} +func (dst *CbPolicy_SlowRateConfig) XXX_Merge(src proto.Message) { + xxx_messageInfo_CbPolicy_SlowRateConfig.Merge(dst, src) +} +func (m *CbPolicy_SlowRateConfig) XXX_Size() int { + return xxx_messageInfo_CbPolicy_SlowRateConfig.Size(m) +} +func (m *CbPolicy_SlowRateConfig) XXX_DiscardUnknown() { + xxx_messageInfo_CbPolicy_SlowRateConfig.DiscardUnknown(m) +} + +var xxx_messageInfo_CbPolicy_SlowRateConfig proto.InternalMessageInfo + +func (m *CbPolicy_SlowRateConfig) GetEnable() *wrappers.BoolValue { + if m != nil { + return m.Enable + } + return nil +} + +func (m *CbPolicy_SlowRateConfig) GetMaxRt() *duration.Duration { + if m != nil { + return m.MaxRt + } + return nil +} + +func (m *CbPolicy_SlowRateConfig) GetSlowRateToPreserved() *wrappers.UInt32Value { + if m != nil { + return m.SlowRateToPreserved + } + return nil +} + +func (m *CbPolicy_SlowRateConfig) GetSlowRateToOpen() *wrappers.UInt32Value { + if m != nil { + return m.SlowRateToOpen + } + return nil +} + +// 连续错误数熔断配置 +type CbPolicy_ConsecutiveErrConfig struct { + // 是否启用连续错误数配置 + Enable *wrappers.BoolValue `protobuf:"bytes,1,opt,name=enable,proto3" json:"enable,omitempty"` + // 连续错误数阈值,进入Preserved状态 + ConsecutiveErrorToPreserved *wrappers.UInt32Value `protobuf:"bytes,2,opt,name=consecutiveErrorToPreserved,proto3" json:"consecutiveErrorToPreserved,omitempty"` + // 连续错误数阈值,进入Open状态 + ConsecutiveErrorToOpen *wrappers.UInt32Value `protobuf:"bytes,3,opt,name=consecutiveErrorToOpen,proto3" json:"consecutiveErrorToOpen,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CbPolicy_ConsecutiveErrConfig) Reset() { *m = CbPolicy_ConsecutiveErrConfig{} } +func (m *CbPolicy_ConsecutiveErrConfig) String() string { return proto.CompactTextString(m) } +func (*CbPolicy_ConsecutiveErrConfig) ProtoMessage() {} +func (*CbPolicy_ConsecutiveErrConfig) Descriptor() ([]byte, []int) { + return fileDescriptor_circuitbreaker_893772a31588c4ee, []int{3, 2} +} +func (m *CbPolicy_ConsecutiveErrConfig) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CbPolicy_ConsecutiveErrConfig.Unmarshal(m, b) +} +func (m *CbPolicy_ConsecutiveErrConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CbPolicy_ConsecutiveErrConfig.Marshal(b, m, deterministic) +} +func (dst *CbPolicy_ConsecutiveErrConfig) XXX_Merge(src proto.Message) { + xxx_messageInfo_CbPolicy_ConsecutiveErrConfig.Merge(dst, src) +} +func (m *CbPolicy_ConsecutiveErrConfig) XXX_Size() int { + return xxx_messageInfo_CbPolicy_ConsecutiveErrConfig.Size(m) +} +func (m *CbPolicy_ConsecutiveErrConfig) XXX_DiscardUnknown() { + xxx_messageInfo_CbPolicy_ConsecutiveErrConfig.DiscardUnknown(m) +} + +var xxx_messageInfo_CbPolicy_ConsecutiveErrConfig proto.InternalMessageInfo + +func (m *CbPolicy_ConsecutiveErrConfig) GetEnable() *wrappers.BoolValue { + if m != nil { + return m.Enable + } + return nil +} + +func (m *CbPolicy_ConsecutiveErrConfig) GetConsecutiveErrorToPreserved() *wrappers.UInt32Value { + if m != nil { + return m.ConsecutiveErrorToPreserved + } + return nil +} + +func (m *CbPolicy_ConsecutiveErrConfig) GetConsecutiveErrorToOpen() *wrappers.UInt32Value { + if m != nil { + return m.ConsecutiveErrorToOpen + } + return nil +} + +// 目标set的规则 +type DestinationSet struct { + // 被调命名空间以及服务名,可以为*,代表全匹配 + Service *wrappers.StringValue `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` + Namespace *wrappers.StringValue `protobuf:"bytes,2,opt,name=namespace,proto3" json:"namespace,omitempty"` + // 可选,SUBSET标识 + Metadata map[string]*MatchString `protobuf:"bytes,3,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Resource DestinationSet_Resource `protobuf:"varint,4,opt,name=resource,proto3,enum=v1.DestinationSet_Resource" json:"resource,omitempty"` + Type DestinationSet_Type `protobuf:"varint,5,opt,name=type,proto3,enum=v1.DestinationSet_Type" json:"type,omitempty"` + Scope DestinationSet_Scope `protobuf:"varint,6,opt,name=scope,proto3,enum=v1.DestinationSet_Scope" json:"scope,omitempty"` + // 熔断数据度量周期 + // 所有的阈值指标按此周期进行统计 + MetricWindow *duration.Duration `protobuf:"bytes,7,opt,name=metricWindow,proto3" json:"metricWindow,omitempty"` + // 熔断数据统计精度,决定数据度量的最小周期 + // 度量滑窗的步长=window/precision + MetricPrecision *wrappers.UInt32Value `protobuf:"bytes,8,opt,name=metricPrecision,proto3" json:"metricPrecision,omitempty"` + // 熔断数据上报周期,对分布式熔断有效 + UpdateInterval *duration.Duration `protobuf:"bytes,9,opt,name=updateInterval,proto3" json:"updateInterval,omitempty"` + // 触发熔断后恢复配置 + Recover *RecoverConfig `protobuf:"bytes,10,opt,name=recover,proto3" json:"recover,omitempty"` + // 熔断策略 + Policy *CbPolicy `protobuf:"bytes,11,opt,name=policy,proto3" json:"policy,omitempty"` + // 被调的接口信息,指定哪些接口会使用该规则 + Method *MatchString `protobuf:"bytes,12,opt,name=method,proto3" json:"method,omitempty"` + // 返回码,指定哪些返回码会使用该规则 + ErrorCodes []*wrappers.Int64Value `protobuf:"bytes,13,rep,name=errorCodes,proto3" json:"errorCodes,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *DestinationSet) Reset() { *m = DestinationSet{} } +func (m *DestinationSet) String() string { return proto.CompactTextString(m) } +func (*DestinationSet) ProtoMessage() {} +func (*DestinationSet) Descriptor() ([]byte, []int) { + return fileDescriptor_circuitbreaker_893772a31588c4ee, []int{4} +} +func (m *DestinationSet) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_DestinationSet.Unmarshal(m, b) +} +func (m *DestinationSet) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_DestinationSet.Marshal(b, m, deterministic) +} +func (dst *DestinationSet) XXX_Merge(src proto.Message) { + xxx_messageInfo_DestinationSet.Merge(dst, src) +} +func (m *DestinationSet) XXX_Size() int { + return xxx_messageInfo_DestinationSet.Size(m) +} +func (m *DestinationSet) XXX_DiscardUnknown() { + xxx_messageInfo_DestinationSet.DiscardUnknown(m) +} + +var xxx_messageInfo_DestinationSet proto.InternalMessageInfo + +func (m *DestinationSet) GetService() *wrappers.StringValue { + if m != nil { + return m.Service + } + return nil +} + +func (m *DestinationSet) GetNamespace() *wrappers.StringValue { + if m != nil { + return m.Namespace + } + return nil +} + +func (m *DestinationSet) GetMetadata() map[string]*MatchString { + if m != nil { + return m.Metadata + } + return nil +} + +func (m *DestinationSet) GetResource() DestinationSet_Resource { + if m != nil { + return m.Resource + } + return DestinationSet_SUBSET +} + +func (m *DestinationSet) GetType() DestinationSet_Type { + if m != nil { + return m.Type + } + return DestinationSet_GLOBAL +} + +func (m *DestinationSet) GetScope() DestinationSet_Scope { + if m != nil { + return m.Scope + } + return DestinationSet_ALL +} + +func (m *DestinationSet) GetMetricWindow() *duration.Duration { + if m != nil { + return m.MetricWindow + } + return nil +} + +func (m *DestinationSet) GetMetricPrecision() *wrappers.UInt32Value { + if m != nil { + return m.MetricPrecision + } + return nil +} + +func (m *DestinationSet) GetUpdateInterval() *duration.Duration { + if m != nil { + return m.UpdateInterval + } + return nil +} + +func (m *DestinationSet) GetRecover() *RecoverConfig { + if m != nil { + return m.Recover + } + return nil +} + +func (m *DestinationSet) GetPolicy() *CbPolicy { + if m != nil { + return m.Policy + } + return nil +} + +func (m *DestinationSet) GetMethod() *MatchString { + if m != nil { + return m.Method + } + return nil +} + +func (m *DestinationSet) GetErrorCodes() []*wrappers.Int64Value { + if m != nil { + return m.ErrorCodes + } + return nil +} + +// 具体熔断规则 +type CbRule struct { + // 如果匹配Source规则,按照Destination进行熔断 + // 多个Source之间的关系为或 + Sources []*SourceMatcher `protobuf:"bytes,1,rep,name=sources,proto3" json:"sources,omitempty"` + Destinations []*DestinationSet `protobuf:"bytes,2,rep,name=destinations,proto3" json:"destinations,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CbRule) Reset() { *m = CbRule{} } +func (m *CbRule) String() string { return proto.CompactTextString(m) } +func (*CbRule) ProtoMessage() {} +func (*CbRule) Descriptor() ([]byte, []int) { + return fileDescriptor_circuitbreaker_893772a31588c4ee, []int{5} +} +func (m *CbRule) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CbRule.Unmarshal(m, b) +} +func (m *CbRule) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CbRule.Marshal(b, m, deterministic) +} +func (dst *CbRule) XXX_Merge(src proto.Message) { + xxx_messageInfo_CbRule.Merge(dst, src) +} +func (m *CbRule) XXX_Size() int { + return xxx_messageInfo_CbRule.Size(m) +} +func (m *CbRule) XXX_DiscardUnknown() { + xxx_messageInfo_CbRule.DiscardUnknown(m) +} + +var xxx_messageInfo_CbRule proto.InternalMessageInfo + +func (m *CbRule) GetSources() []*SourceMatcher { + if m != nil { + return m.Sources + } + return nil +} + +func (m *CbRule) GetDestinations() []*DestinationSet { + if m != nil { + return m.Destinations + } + return nil +} + +func init() { + proto.RegisterType((*CircuitBreaker)(nil), "v1.CircuitBreaker") + proto.RegisterType((*SourceMatcher)(nil), "v1.SourceMatcher") + proto.RegisterMapType((map[string]*MatchString)(nil), "v1.SourceMatcher.LabelsEntry") + proto.RegisterType((*RecoverConfig)(nil), "v1.RecoverConfig") + proto.RegisterType((*CbPolicy)(nil), "v1.CbPolicy") + proto.RegisterType((*CbPolicy_ErrRateConfig)(nil), "v1.CbPolicy.ErrRateConfig") + proto.RegisterType((*CbPolicy_ErrRateConfig_SpecialConfig)(nil), "v1.CbPolicy.ErrRateConfig.SpecialConfig") + proto.RegisterType((*CbPolicy_SlowRateConfig)(nil), "v1.CbPolicy.SlowRateConfig") + proto.RegisterType((*CbPolicy_ConsecutiveErrConfig)(nil), "v1.CbPolicy.ConsecutiveErrConfig") + proto.RegisterType((*DestinationSet)(nil), "v1.DestinationSet") + proto.RegisterMapType((map[string]*MatchString)(nil), "v1.DestinationSet.MetadataEntry") + proto.RegisterType((*CbRule)(nil), "v1.CbRule") + proto.RegisterEnum("v1.RecoverConfig_OutlierDetectWhen", RecoverConfig_OutlierDetectWhen_name, RecoverConfig_OutlierDetectWhen_value) + proto.RegisterEnum("v1.DestinationSet_Resource", DestinationSet_Resource_name, DestinationSet_Resource_value) + proto.RegisterEnum("v1.DestinationSet_Type", DestinationSet_Type_name, DestinationSet_Type_value) + proto.RegisterEnum("v1.DestinationSet_Scope", DestinationSet_Scope_name, DestinationSet_Scope_value) +} + +func init() { + proto.RegisterFile("circuitbreaker.proto", fileDescriptor_circuitbreaker_893772a31588c4ee) +} + +var fileDescriptor_circuitbreaker_893772a31588c4ee = []byte{ + // 1326 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x57, 0xcd, 0x73, 0x1b, 0xc5, + 0x12, 0x8f, 0x64, 0xeb, 0xc3, 0x2d, 0x4b, 0x91, 0xe7, 0xe5, 0xbd, 0xb7, 0x51, 0x5e, 0x5e, 0x05, + 0x11, 0xc0, 0x55, 0xa1, 0x14, 0xe2, 0x84, 0x90, 0x4a, 0x42, 0x51, 0xf2, 0x5a, 0x80, 0x29, 0x45, + 0x36, 0x2b, 0x25, 0x01, 0x0e, 0xa4, 0x56, 0xbb, 0x6d, 0x7b, 0x93, 0xdd, 0x9d, 0x65, 0x66, 0x56, + 0x8e, 0x6f, 0x9c, 0xb8, 0x50, 0xdc, 0x38, 0xf0, 0x7f, 0xf0, 0xb7, 0x71, 0xe0, 0x46, 0xcd, 0xec, + 0xe8, 0x63, 0xf5, 0x61, 0xd6, 0xe1, 0x00, 0x27, 0x69, 0xb7, 0x7f, 0xbf, 0x9e, 0xde, 0x9e, 0x5f, + 0xf7, 0xf4, 0xc0, 0x15, 0xc7, 0x63, 0x4e, 0xec, 0x89, 0x21, 0x43, 0xfb, 0x15, 0xb2, 0x56, 0xc4, + 0xa8, 0xa0, 0x24, 0x3f, 0xba, 0xd3, 0xf8, 0xff, 0x31, 0xa5, 0xc7, 0x3e, 0xde, 0x56, 0x6f, 0x86, + 0xf1, 0xd1, 0xed, 0x53, 0x66, 0x47, 0x11, 0x32, 0x9e, 0x60, 0x16, 0xed, 0x6e, 0xcc, 0x6c, 0xe1, + 0xd1, 0x50, 0xdb, 0x2b, 0x01, 0x75, 0xd1, 0x4f, 0x1e, 0x9a, 0xbf, 0x15, 0xa1, 0x66, 0x26, 0x2b, + 0xed, 0x26, 0x2b, 0x91, 0xf7, 0x21, 0xef, 0xb9, 0x46, 0xee, 0x46, 0x6e, 0xbb, 0xb2, 0xf3, 0xbf, + 0x56, 0xe2, 0xac, 0x35, 0x76, 0xd6, 0xea, 0x0b, 0xe6, 0x85, 0xc7, 0xcf, 0x6c, 0x3f, 0x46, 0x2b, + 0xef, 0xb9, 0xe4, 0x3e, 0x94, 0x46, 0xc8, 0xb8, 0x47, 0x43, 0x23, 0x9f, 0x81, 0x32, 0x06, 0x93, + 0x0f, 0x60, 0x3d, 0xb4, 0x03, 0x34, 0xd6, 0x32, 0x90, 0x14, 0x92, 0x3c, 0x84, 0x0d, 0xf9, 0xcb, + 0x23, 0xdb, 0x41, 0x63, 0x3d, 0x03, 0x6d, 0x0a, 0x97, 0x51, 0x72, 0x64, 0x23, 0xcf, 0x41, 0xa3, + 0x90, 0x25, 0x4a, 0x0d, 0x26, 0xfb, 0xb0, 0xa5, 0xff, 0xbe, 0x98, 0xae, 0x5d, 0xcc, 0xe0, 0xa1, + 0xae, 0x69, 0xbd, 0x49, 0x08, 0xef, 0x42, 0xd9, 0x0b, 0x87, 0x34, 0x0e, 0x5d, 0x6e, 0x94, 0x6e, + 0xac, 0x6d, 0x57, 0x76, 0xa0, 0x35, 0xba, 0xd3, 0x32, 0x87, 0x56, 0xec, 0xa3, 0x35, 0xb1, 0x91, + 0x6d, 0xd8, 0xa0, 0xb1, 0xd0, 0xc0, 0xf2, 0x02, 0x70, 0x6a, 0x24, 0x3b, 0x50, 0x10, 0xf4, 0x15, + 0x86, 0xc6, 0x46, 0x86, 0x80, 0x12, 0x28, 0xb9, 0x07, 0x45, 0x7a, 0x1a, 0x22, 0xe3, 0x06, 0x64, + 0x20, 0x69, 0x2c, 0x79, 0x00, 0xe5, 0x61, 0xcc, 0xbd, 0x10, 0x39, 0x37, 0x2a, 0x19, 0x78, 0x13, + 0x34, 0x79, 0x0c, 0xe0, 0x62, 0x64, 0x33, 0x11, 0x60, 0x28, 0x8c, 0xcd, 0x0c, 0xdc, 0x19, 0xbc, + 0xdc, 0x36, 0x87, 0x06, 0x8a, 0x5a, 0xcd, 0xb2, 0x6d, 0x1a, 0x2c, 0x33, 0xe3, 0x08, 0x2f, 0x40, + 0xa3, 0x96, 0x25, 0x33, 0x0a, 0x2a, 0x39, 0x81, 0xe2, 0x5c, 0xce, 0xc2, 0x51, 0x50, 0x99, 0x17, + 0x86, 0x23, 0x4f, 0xa9, 0xbf, 0x9e, 0x25, 0x2f, 0x63, 0x74, 0xf3, 0x87, 0x3c, 0x54, 0xfb, 0x34, + 0x66, 0x0e, 0x3e, 0xb1, 0x85, 0x73, 0x82, 0x6c, 0x56, 0xa2, 0xb9, 0x8b, 0x48, 0x34, 0x55, 0x16, + 0xf9, 0x8b, 0x95, 0xc5, 0x87, 0x50, 0xf4, 0xed, 0x21, 0xfa, 0xdc, 0x58, 0x53, 0x42, 0xbb, 0x2e, + 0x85, 0x96, 0x0a, 0xab, 0xd5, 0x55, 0xf6, 0x4e, 0x28, 0xd8, 0x99, 0xa5, 0xc1, 0x8d, 0x2f, 0xa0, + 0x32, 0xf3, 0x9a, 0xd4, 0x61, 0xed, 0x15, 0x9e, 0xa9, 0xa8, 0x37, 0x2c, 0xf9, 0x97, 0xbc, 0x03, + 0x85, 0x91, 0x5c, 0x4b, 0xc7, 0x73, 0x59, 0xba, 0x55, 0x0e, 0x93, 0x38, 0xac, 0xc4, 0xfa, 0x30, + 0xff, 0x20, 0xd7, 0xfc, 0x75, 0x1d, 0xaa, 0x16, 0x3a, 0x74, 0x84, 0xcc, 0xa4, 0xe1, 0x91, 0x77, + 0x4c, 0x1e, 0x41, 0x85, 0xfb, 0x88, 0xd1, 0x73, 0x2f, 0x74, 0xe9, 0xa9, 0x4e, 0xc6, 0xd5, 0x85, + 0x4f, 0xda, 0xd3, 0x5d, 0xcd, 0x9a, 0x45, 0x13, 0x0b, 0xfe, 0x1d, 0xd8, 0xaf, 0x2d, 0x14, 0xec, + 0xac, 0x7d, 0x24, 0x90, 0x7d, 0x6e, 0xfb, 0x47, 0x07, 0x11, 0xae, 0x6e, 0x4e, 0x4f, 0xf7, 0x43, + 0x71, 0x77, 0x27, 0xc9, 0xcc, 0x72, 0x2a, 0xf9, 0x0a, 0x0c, 0x86, 0xdf, 0xc5, 0xc8, 0x85, 0x65, + 0x0b, 0x4c, 0xbb, 0x4d, 0xf2, 0x76, 0xbe, 0xdb, 0x95, 0x6c, 0xd2, 0x05, 0xc2, 0x63, 0xc7, 0x41, + 0xce, 0xa5, 0x6d, 0x40, 0x4d, 0x9f, 0xf2, 0xd5, 0xbd, 0x6d, 0xd6, 0xe7, 0x12, 0x1e, 0xf9, 0x06, + 0xae, 0xea, 0x95, 0x4c, 0x1a, 0x87, 0x22, 0x1d, 0x68, 0x21, 0x83, 0xd3, 0xd5, 0x74, 0xf2, 0x25, + 0x6c, 0xd1, 0x58, 0xf8, 0x1e, 0xb2, 0x3d, 0x14, 0xe8, 0x88, 0xe7, 0x27, 0x18, 0xaa, 0x46, 0x58, + 0xdb, 0x79, 0x5b, 0xee, 0x6e, 0x6a, 0x0b, 0x5b, 0x07, 0xf3, 0x50, 0x6b, 0x91, 0xdd, 0x7c, 0x08, + 0x5b, 0x0b, 0x38, 0xb2, 0x01, 0x85, 0x5e, 0xe7, 0x59, 0xc7, 0xaa, 0x5f, 0x22, 0x35, 0x80, 0x83, + 0xde, 0x0b, 0xab, 0x63, 0x1e, 0xc8, 0xe7, 0x1c, 0x01, 0x28, 0xb6, 0xbb, 0xcf, 0xdb, 0x5f, 0xf7, + 0xeb, 0xf9, 0xe6, 0xef, 0x15, 0x28, 0x9b, 0xc3, 0x43, 0xea, 0x7b, 0xce, 0x19, 0x79, 0x00, 0x1b, + 0xc8, 0x18, 0x65, 0x32, 0x17, 0x5a, 0x2e, 0x8d, 0xa4, 0x63, 0x26, 0x80, 0x56, 0x87, 0x29, 0x5b, + 0x12, 0x9c, 0x35, 0x05, 0x93, 0x8f, 0xa0, 0xcc, 0x7d, 0x7a, 0xaa, 0x88, 0x89, 0x40, 0xae, 0xa5, + 0x88, 0x7d, 0x6d, 0xd4, 0xcc, 0x09, 0x98, 0x7c, 0x02, 0xd5, 0x97, 0xb1, 0x7b, 0x8c, 0x63, 0x11, + 0xea, 0x63, 0xec, 0x1c, 0x95, 0xa6, 0xf1, 0x72, 0xe7, 0x03, 0xfb, 0x75, 0xe7, 0x25, 0x3a, 0xf2, + 0xf1, 0x10, 0x99, 0x23, 0x9b, 0x5c, 0xa6, 0x9d, 0x5f, 0xe4, 0x11, 0x13, 0x2a, 0x0e, 0x0d, 0x39, + 0x3a, 0xb1, 0xf0, 0x46, 0xe3, 0x23, 0xee, 0xad, 0xd4, 0xa7, 0x98, 0x53, 0x7b, 0x87, 0xe9, 0x7d, + 0xb2, 0x66, 0x59, 0x8d, 0x9f, 0x0b, 0x50, 0x4d, 0x65, 0x8a, 0xec, 0x40, 0x11, 0x43, 0x7b, 0xe8, + 0x4f, 0xb3, 0x3a, 0x1f, 0xd8, 0x2e, 0xa5, 0xbe, 0x3e, 0x2a, 0x12, 0x24, 0x19, 0xc0, 0x7f, 0xb4, + 0x8a, 0x9e, 0x51, 0x3f, 0x0e, 0x70, 0x70, 0xc2, 0x90, 0x9f, 0x50, 0xdf, 0xcd, 0x54, 0x81, 0x2b, + 0xb8, 0xe4, 0x10, 0xae, 0x4c, 0x76, 0x6d, 0x40, 0x0f, 0x19, 0xca, 0xee, 0x87, 0xee, 0xca, 0xe9, + 0x61, 0xd6, 0xe7, 0x52, 0x26, 0xf9, 0x14, 0x2e, 0xcf, 0xbc, 0x57, 0x25, 0x92, 0x25, 0xfb, 0xf3, + 0x24, 0xb2, 0x07, 0x65, 0x1e, 0xa1, 0xe3, 0xd9, 0x3e, 0x37, 0x0a, 0xaa, 0x19, 0x6c, 0xaf, 0xd6, + 0x5e, 0xab, 0x9f, 0x40, 0x27, 0x7a, 0xd2, 0xcc, 0xc6, 0x2f, 0xf2, 0x38, 0x98, 0xb5, 0xc9, 0xf9, + 0x48, 0x9c, 0x45, 0xd9, 0xce, 0x02, 0x85, 0x24, 0x8f, 0x00, 0x54, 0x70, 0x26, 0x75, 0x91, 0x1b, + 0x79, 0x15, 0xcb, 0xb5, 0x05, 0xde, 0x7e, 0x28, 0xee, 0xdf, 0xd3, 0x27, 0xed, 0x14, 0xfe, 0xcf, + 0x4d, 0x70, 0xe3, 0xc7, 0x3c, 0xd4, 0xd2, 0x75, 0xf8, 0x46, 0xba, 0xbc, 0x0d, 0x05, 0xd9, 0xdd, + 0x85, 0x96, 0xe1, 0x39, 0x95, 0x9a, 0xe0, 0x48, 0x0f, 0xfe, 0x35, 0x2e, 0xf7, 0x8b, 0x26, 0x64, + 0x19, 0x91, 0xec, 0x41, 0x6d, 0xfa, 0x3a, 0x73, 0x3a, 0xe6, 0x38, 0x8d, 0xef, 0xf3, 0x70, 0x65, + 0x59, 0x29, 0xbf, 0x51, 0x4e, 0xbe, 0x85, 0x6b, 0x4e, 0xca, 0x17, 0x65, 0xb3, 0x9f, 0x9a, 0xa5, + 0x60, 0xcf, 0x73, 0x20, 0x7b, 0xc1, 0xa2, 0x59, 0x1f, 0x9b, 0x19, 0x7a, 0xc1, 0x72, 0x6e, 0xf3, + 0xa7, 0x12, 0xd4, 0xf6, 0x90, 0x0b, 0x2f, 0x54, 0xfb, 0xd5, 0x47, 0xf1, 0xb7, 0xcc, 0x4e, 0x8f, + 0xa1, 0x1c, 0xa0, 0xb0, 0x5d, 0x5b, 0xd8, 0x7a, 0x0a, 0xb8, 0x21, 0x0b, 0x3f, 0x1d, 0x59, 0xeb, + 0x89, 0x86, 0x24, 0x03, 0xd4, 0x84, 0x21, 0x4f, 0x1e, 0x86, 0x5c, 0x4d, 0x5a, 0x4a, 0x07, 0xb5, + 0xe4, 0xe4, 0x99, 0x63, 0x5b, 0x1a, 0x62, 0x4d, 0xc0, 0xe4, 0x96, 0xee, 0x0b, 0x05, 0x45, 0xfa, + 0xef, 0x12, 0xd2, 0xe0, 0x2c, 0x1a, 0xb7, 0x84, 0x16, 0x14, 0xb8, 0x43, 0x23, 0xd4, 0x27, 0xb5, + 0xb1, 0x04, 0xdd, 0x97, 0x76, 0x2b, 0x81, 0x91, 0x8f, 0x61, 0x33, 0x40, 0xc1, 0x3c, 0x47, 0xcf, + 0x5e, 0xa5, 0x3f, 0xab, 0x95, 0x14, 0x5c, 0x96, 0x7c, 0xf2, 0x7c, 0xc8, 0xd0, 0x49, 0xa6, 0xe2, + 0x72, 0x96, 0x92, 0x9f, 0x23, 0x91, 0x36, 0xd4, 0xe2, 0xc8, 0xb5, 0x05, 0xee, 0x87, 0x02, 0xd9, + 0xc8, 0xf6, 0xf5, 0x0d, 0xe7, 0x9c, 0x40, 0xe6, 0x08, 0xe4, 0x16, 0x94, 0x58, 0x32, 0x92, 0xe8, + 0x8b, 0xce, 0xd6, 0xc2, 0x94, 0x62, 0x8d, 0x11, 0xe4, 0x26, 0x14, 0x23, 0xd5, 0xad, 0xf5, 0xe5, + 0x66, 0x73, 0xb6, 0x83, 0x5b, 0xda, 0x46, 0xde, 0x83, 0x62, 0x80, 0xe2, 0x84, 0xba, 0xfa, 0x1a, + 0xb3, 0x30, 0xd5, 0x6a, 0xf3, 0x5c, 0x23, 0xae, 0x5e, 0xa8, 0x11, 0x37, 0xba, 0x50, 0x4d, 0x69, + 0xe6, 0xaf, 0x4d, 0xd7, 0x37, 0xa1, 0x3c, 0xd6, 0x90, 0x9c, 0x9f, 0xfa, 0x4f, 0x77, 0xfb, 0x9d, + 0x41, 0xfd, 0x12, 0xd9, 0x84, 0xf2, 0x7e, 0xaf, 0x3f, 0x68, 0xf7, 0xcc, 0x4e, 0x3d, 0xd7, 0xbc, + 0x0e, 0xeb, 0x52, 0x34, 0x12, 0xf1, 0x59, 0xf7, 0x60, 0xb7, 0xdd, 0xad, 0x5f, 0x92, 0x83, 0x58, + 0xf7, 0xc0, 0x6c, 0x77, 0x95, 0xb9, 0xa0, 0x54, 0x42, 0x4a, 0xb0, 0xd6, 0xee, 0x4a, 0x63, 0x05, + 0x4a, 0xe6, 0x53, 0xcb, 0xea, 0xf4, 0x06, 0xf5, 0x5c, 0x33, 0x80, 0x62, 0x72, 0x37, 0x95, 0x49, + 0x4f, 0xd6, 0xe2, 0x46, 0x4e, 0x7d, 0xf5, 0xd6, 0xc2, 0x7d, 0xc2, 0x1a, 0x23, 0xc8, 0x7d, 0xd8, + 0x74, 0xa7, 0x52, 0x1c, 0x1f, 0x58, 0x64, 0x51, 0xa2, 0x56, 0x0a, 0x37, 0x2c, 0xaa, 0x0c, 0xde, + 0xfd, 0x23, 0x00, 0x00, 0xff, 0xff, 0xbb, 0xb0, 0x2d, 0x9f, 0x21, 0x11, 0x00, 0x00, +} diff --git a/common/api/v1/circuitbreaker.proto b/common/api/v1/circuitbreaker.proto new file mode 100644 index 000000000..3051a37f9 --- /dev/null +++ b/common/api/v1/circuitbreaker.proto @@ -0,0 +1,191 @@ +syntax = "proto3"; + +package v1; + +import "google/protobuf/wrappers.proto"; +import "google/protobuf/duration.proto"; +import "model.proto"; + +//单个熔断规则定义 +message CircuitBreaker { + google.protobuf.StringValue id = 1; + // 规则版本 + google.protobuf.StringValue version = 2; + // 规则名 + google.protobuf.StringValue name = 3; + // 规则命名空间 + google.protobuf.StringValue namespace = 4; + + // 规则所属服务 + google.protobuf.StringValue service = 5; + google.protobuf.StringValue service_namespace = 6; + + //熔断规则可以分为被调规则和主调规则 + //被调规则针对所有的指定主调生效,假如不指定则对所有的主调生效 + //主调规则为当前主调方的规则,假如不指定则针对所有被调生效 + repeated CbRule inbounds = 7; + repeated CbRule outbounds = 8; + + google.protobuf.StringValue token = 9; + google.protobuf.StringValue owners = 10; + // 业务 + google.protobuf.StringValue business = 11; + // 部门 + google.protobuf.StringValue department = 12; + + // 规则描述 + google.protobuf.StringValue comment = 13; + google.protobuf.StringValue ctime = 14; + google.protobuf.StringValue mtime = 15; + google.protobuf.StringValue revision = 16; +} + +// 主调匹配规则 +message SourceMatcher { + // 主调命名空间以及服务名,可以为*,代表全匹配 + google.protobuf.StringValue service = 1; + google.protobuf.StringValue namespace = 2; + // 可选,主调业务标签,用于匹配是否使用该熔断规则,可放置用户的接口信息等 + map labels = 3; +} + +// 熔断恢复配置 +message RecoverConfig { + // 触发熔断后到半开状态之间的等待间隔 + google.protobuf.Duration sleepWindow = 1; + // 半开后,最多重试多少次恢复 + google.protobuf.UInt32Value maxRetryAfterHalfOpen = 2; + // 半开后放量的最大百分比 + repeated google.protobuf.UInt32Value requestRateAfterHalfOpen = 3; + // 熔断器半开到关闭所必须的最少成功率,默认100% + google.protobuf.UInt32Value successRateToClose = 4; + // 半开后最大放量数(用户不配置最大百分比时默认使用该配置) + google.protobuf.UInt32Value requestCountAfterHalfOpen = 5; + //主动探测配置 + enum OutlierDetectWhen { + //不开启监控探测 + NEVER = 0; + //只有在熔断恢复时才开启健康探测 + ON_RECOVER = 1; + //一直开启健康探测 + ALWAYS = 2; + } + OutlierDetectWhen outlierDetectWhen = 6; +} + +// 熔断策略 +message CbPolicy { + // 错误率熔断配置 + message ErrRateConfig { + //是否启用错误率配置 + google.protobuf.BoolValue enable = 1; + // 触发错误率熔断的最低请求阈值 + google.protobuf.UInt32Value requestVolumeThreshold = 2; + // 可选。触发保持状态的错误率阈值,假如不配置,则默认不会进入Preserved状态 + google.protobuf.UInt32Value errorRateToPreserved = 3; + // 触发熔断的错误率阈值 + google.protobuf.UInt32Value errorRateToOpen = 4; + //错误码相关特定配置 + message SpecialConfig { + // 熔断关心的错误类型,用户可以自己定义 + google.protobuf.StringValue type = 1; + repeated google.protobuf.Int64Value errorCodes = 2; + google.protobuf.UInt32Value errorRateToPreserved = 3; + google.protobuf.UInt32Value errorRateToOpen = 4; + } + repeated SpecialConfig specials = 5; + } + ErrRateConfig errorRate = 1; + // 慢调用率熔断策略配置 + message SlowRateConfig { + // 是否启用慢调用率配置 + google.protobuf.BoolValue enable = 1; + // 最大响应时间,超过该时间属于慢调用请求 + google.protobuf.Duration maxRt = 2; + // 可选。触发保持状态的超时率阈值,假如不配置,则默认不会进入Preserved状态 + google.protobuf.UInt32Value slowRateToPreserved = 3; + // 触发熔断的超时率阈值 + google.protobuf.UInt32Value slowRateToOpen = 4; + } + SlowRateConfig slowRate = 2; + // 熔断的决策周期,多久触发一次熔断决策 + google.protobuf.Duration judgeDuration = 3; + //最大熔断比例,超过多少比例后不会继续熔断 + google.protobuf.UInt32Value maxEjectionPercent = 4; + //连续错误数熔断配置 + message ConsecutiveErrConfig { + // 是否启用连续错误数配置 + google.protobuf.BoolValue enable = 1; + // 连续错误数阈值,进入Preserved状态 + google.protobuf.UInt32Value consecutiveErrorToPreserved = 2; + // 连续错误数阈值,进入Open状态 + google.protobuf.UInt32Value consecutiveErrorToOpen = 3; + } + ConsecutiveErrConfig consecutive = 5; +} + +// 目标set的规则 +message DestinationSet { + // 被调命名空间以及服务名,可以为*,代表全匹配 + google.protobuf.StringValue service = 1; + google.protobuf.StringValue namespace = 2; + // 可选,SUBSET标识 + map metadata = 3; + // 需要进行熔断的资源 + // 支持SUBSET(子集群),以及INSTANCE(单个实例),默认为SUBSET + enum Resource { + // 针对实例分组进行熔断 + SUBSET = 0; + // 针对实例进行熔断 + INSTANCE = 1; + } + Resource resource = 4; + // 熔断决策类型,支持GLOBAL(分布式决策)以及LOCAL(本地决策),默认GLOBAL + // 当指定为GLOBAL时,则会定期上报统计数据并根据汇总数据进行熔断决策 + enum Type { + GLOBAL = 0; + LOCAL = 1; + } + Type type = 5; + + //熔断范围,是否扩散针对相同服务下所有接口进行熔断 + enum Scope { + //触发熔断条件,扩散熔断所有接口 + ALL = 0; + //触发熔断条件,只熔断当前接口 + CURRENT = 1; + } + Scope scope = 6; + + // 熔断数据度量周期 + // 所有的阈值指标按此周期进行统计 + google.protobuf.Duration metricWindow = 7; + + // 熔断数据统计精度,决定数据度量的最小周期 + // 度量滑窗的步长=window/precision + google.protobuf.UInt32Value metricPrecision = 8; + + // 熔断数据上报周期,对分布式熔断有效 + google.protobuf.Duration updateInterval = 9; + + // 触发熔断后恢复配置 + RecoverConfig recover = 10; + + // 熔断策略 + CbPolicy policy = 11; + + // 被调的接口信息,指定哪些接口会使用该规则 + MatchString method = 12; + + // 返回码,指定哪些返回码会使用该规则 + repeated google.protobuf.Int64Value errorCodes = 13; +} + +// 具体熔断规则 +message CbRule { + // 如果匹配Source规则,按照Destination进行熔断 + // 多个Source之间的关系为或 + repeated SourceMatcher sources = 1; + repeated DestinationSet destinations = 2; +} + diff --git a/common/api/v1/client.pb.go b/common/api/v1/client.pb.go new file mode 100644 index 000000000..f9c740e47 --- /dev/null +++ b/common/api/v1/client.pb.go @@ -0,0 +1,134 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: client.proto + +package v1 + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import wrappers "github.com/golang/protobuf/ptypes/wrappers" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type Client_ClientType int32 + +const ( + Client_UNKNOWN Client_ClientType = 0 + Client_SDK Client_ClientType = 1 + Client_AGENT Client_ClientType = 2 +) + +var Client_ClientType_name = map[int32]string{ + 0: "UNKNOWN", + 1: "SDK", + 2: "AGENT", +} +var Client_ClientType_value = map[string]int32{ + "UNKNOWN": 0, + "SDK": 1, + "AGENT": 2, +} + +func (x Client_ClientType) String() string { + return proto.EnumName(Client_ClientType_name, int32(x)) +} +func (Client_ClientType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_client_7fce320934922288, []int{0, 0} +} + +type Client struct { + Host *wrappers.StringValue `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"` + Type Client_ClientType `protobuf:"varint,2,opt,name=type,proto3,enum=v1.Client_ClientType" json:"type,omitempty"` + Version *wrappers.StringValue `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"` + Location *Location `protobuf:"bytes,4,opt,name=location,proto3" json:"location,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Client) Reset() { *m = Client{} } +func (m *Client) String() string { return proto.CompactTextString(m) } +func (*Client) ProtoMessage() {} +func (*Client) Descriptor() ([]byte, []int) { + return fileDescriptor_client_7fce320934922288, []int{0} +} +func (m *Client) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Client.Unmarshal(m, b) +} +func (m *Client) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Client.Marshal(b, m, deterministic) +} +func (dst *Client) XXX_Merge(src proto.Message) { + xxx_messageInfo_Client.Merge(dst, src) +} +func (m *Client) XXX_Size() int { + return xxx_messageInfo_Client.Size(m) +} +func (m *Client) XXX_DiscardUnknown() { + xxx_messageInfo_Client.DiscardUnknown(m) +} + +var xxx_messageInfo_Client proto.InternalMessageInfo + +func (m *Client) GetHost() *wrappers.StringValue { + if m != nil { + return m.Host + } + return nil +} + +func (m *Client) GetType() Client_ClientType { + if m != nil { + return m.Type + } + return Client_UNKNOWN +} + +func (m *Client) GetVersion() *wrappers.StringValue { + if m != nil { + return m.Version + } + return nil +} + +func (m *Client) GetLocation() *Location { + if m != nil { + return m.Location + } + return nil +} + +func init() { + proto.RegisterType((*Client)(nil), "v1.Client") + proto.RegisterEnum("v1.Client_ClientType", Client_ClientType_name, Client_ClientType_value) +} + +func init() { proto.RegisterFile("client.proto", fileDescriptor_client_7fce320934922288) } + +var fileDescriptor_client_7fce320934922288 = []byte{ + // 237 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x49, 0xce, 0xc9, 0x4c, + 0xcd, 0x2b, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x2a, 0x33, 0x94, 0x92, 0x4b, 0xcf, + 0xcf, 0x4f, 0xcf, 0x49, 0xd5, 0x07, 0x8b, 0x24, 0x95, 0xa6, 0xe9, 0x97, 0x17, 0x25, 0x16, 0x14, + 0xa4, 0x16, 0x15, 0x43, 0xd4, 0x48, 0x71, 0xe7, 0xe6, 0xa7, 0xa4, 0xe6, 0x40, 0x38, 0x4a, 0xdf, + 0x18, 0xb9, 0xd8, 0x9c, 0xc1, 0x26, 0x08, 0x19, 0x70, 0xb1, 0x64, 0xe4, 0x17, 0x97, 0x48, 0x30, + 0x2a, 0x30, 0x6a, 0x70, 0x1b, 0xc9, 0xe8, 0x41, 0x8c, 0xd1, 0x83, 0x19, 0xa3, 0x17, 0x5c, 0x52, + 0x94, 0x99, 0x97, 0x1e, 0x96, 0x98, 0x53, 0x9a, 0x1a, 0x04, 0x56, 0x29, 0xa4, 0xc9, 0xc5, 0x52, + 0x52, 0x59, 0x90, 0x2a, 0xc1, 0xa4, 0xc0, 0xa8, 0xc1, 0x67, 0x24, 0xaa, 0x57, 0x66, 0xa8, 0x07, + 0x31, 0x0b, 0x4a, 0x85, 0x54, 0x16, 0xa4, 0x06, 0x81, 0x95, 0x08, 0x99, 0x71, 0xb1, 0x97, 0xa5, + 0x16, 0x15, 0x67, 0xe6, 0xe7, 0x49, 0x30, 0x13, 0x61, 0x3e, 0x4c, 0xb1, 0x90, 0x06, 0x17, 0x47, + 0x4e, 0x7e, 0x72, 0x62, 0x09, 0x48, 0x23, 0x0b, 0x58, 0x23, 0x0f, 0xc8, 0x1a, 0x1f, 0xa8, 0x58, + 0x10, 0x5c, 0x56, 0x49, 0x97, 0x8b, 0x0b, 0x61, 0xab, 0x10, 0x37, 0x17, 0x7b, 0xa8, 0x9f, 0xb7, + 0x9f, 0x7f, 0xb8, 0x9f, 0x00, 0x83, 0x10, 0x3b, 0x17, 0x73, 0xb0, 0x8b, 0xb7, 0x00, 0xa3, 0x10, + 0x27, 0x17, 0xab, 0xa3, 0xbb, 0xab, 0x5f, 0x88, 0x00, 0x53, 0x12, 0x1b, 0xd8, 0x5e, 0x63, 0x40, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x53, 0x03, 0xfc, 0x88, 0x40, 0x01, 0x00, 0x00, +} diff --git a/common/api/v1/client.proto b/common/api/v1/client.proto new file mode 100644 index 000000000..ea6a1be97 --- /dev/null +++ b/common/api/v1/client.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; + +package v1; + +import "google/protobuf/wrappers.proto"; +import "model.proto"; + +message Client { + google.protobuf.StringValue host = 1; + + enum ClientType { + UNKNOWN = 0; + SDK = 1; + AGENT = 2; + } + + ClientType type = 2; + + google.protobuf.StringValue version = 3; + + Location location = 4; +} diff --git a/common/api/v1/codeinfo.go b/common/api/v1/codeinfo.go new file mode 100644 index 000000000..5fd1ba861 --- /dev/null +++ b/common/api/v1/codeinfo.go @@ -0,0 +1,277 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package v1 + +// 北极星错误码 +// 六位构成,前面三位参照HTTP Status的标准 +// 后面三位,依据内部的具体错误自定义 +const ( + ExecuteSuccess uint32 = 200000 + DataNoChange = 200001 + NoNeedUpdate = 200002 + BadRequest = 400000 + ParseException = 400001 + EmptyRequest = 400002 + BatchSizeOverLimit = 400003 + InvalidDiscoverResource = 400004 + InvalidRequestID = 400100 + InvalidUserName = 400101 + InvalidUserToken = 400102 + InvalidParameter = 400103 + EmptyQueryParameter = 400104 + InvalidQueryInsParameter = 400105 + InvalidNamespaceName = 400110 + InvalidNamespaceOwners = 400111 + InvalidNamespaceToken = 400112 + InvalidServiceName = 400120 + InvalidServiceOwners = 400121 + InvalidServiceToken = 400122 + InvalidServiceMetadata = 400123 + InvalidServicePorts = 400124 + InvalidServiceBusiness = 400125 + InvalidServiceDepartment = 400126 + InvalidServiceCMDB = 400127 + InvalidServiceComment = 400128 + InvalidServiceAliasComment = 400129 + InvalidInstanceID = 400130 + InvalidInstanceHost = 400131 + InvalidInstancePort = 400132 + InvalidServiceAlias = 400133 + InvalidNamespaceWithAlias = 400134 + InvalidServiceAliasOwners = 400135 + InvalidInstanceProtocol = 400136 + InvalidInstanceVersion = 400137 + InvalidInstanceLogicSet = 400138 + InvalidInstanceIsolate = 400139 + HealthCheckNotOpen = 400140 + HeartbeatOnDisabledIns = 400141 + HeartbeatExceedLimit = 400142 + InvalidMetadata = 400150 + InvalidRateLimitID = 400151 + InvalidRateLimitLabels = 400152 + InvalidRateLimitAmounts = 400153 + InvalidCircuitBreakerID = 400160 + InvalidCircuitBreakerVersion = 400161 + InvalidCircuitBreakerName = 400162 + InvalidCircuitBreakerNamespace = 400163 + InvalidCircuitBreakerOwners = 400164 + InvalidCircuitBreakerToken = 400165 + InvalidCircuitBreakerBusiness = 400166 + InvalidCircuitBreakerDepartment = 400167 + InvalidCircuitBreakerComment = 400168 + + // 网格相关错误码 + ServicesExistedMesh = 400170 + ResourcesExistedMesh = 400171 + InvalidMeshParameter = 400172 + + // 平台信息相关错误码 + InvalidPlatformID = 400180 + InvalidPlatformName = 400181 + InvalidPlatformDomain = 400182 + InvalidPlatformQPS = 400183 + InvalidPlatformToken = 400184 + InvalidPlatformOwner = 400185 + InvalidPlatformDepartment = 400186 + InvalidPlatformComment = 400187 + NotFoundPlatform = 400188 + + // flux相关错误码 + InvalidFluxRateLimitId = 400190 + InvalidFluxRateLimitQps = 400191 + InvalidFluxRateLimitSetKey = 400192 + + ExistedResource = 400201 + NotFoundResource = 400202 + NamespaceExistedServices = 400203 + ServiceExistedInstances = 400204 + ServiceExistedRoutings = 400205 + ServiceExistedRateLimits = 400206 + ExistReleasedConfig = 400207 + SameInstanceRequest = 400208 + ServiceExistedCircuitBreakers = 400209 + ServiceExistedAlias = 400210 + NamespaceExistedMeshResources = 400211 + NamespaceExistedCircuitBreakers = 400212 + ServiceSubscribedByMeshes = 400213 + ServiceExistedFluxRateLimits = 400214 + NotFoundService = 400301 + NotFoundRouting = 400302 + NotFoundInstance = 400303 + NotFoundServiceAlias = 400304 + NotFoundNamespace = 400305 + NotFoundSourceService = 400306 + NotFoundRateLimit = 400307 + NotFoundCircuitBreaker = 400308 + NotFoundMasterConfig = 400309 + NotFoundTagConfig = 400310 + NotFoundTagConfigOrService = 400311 + ClientAPINotOpen = 400401 + NotAllowBusinessService = 400402 + NotAllowAliasUpdate = 400501 + NotAllowAliasCreateInstance = 400502 + NotAllowAliasCreateRouting = 400503 + NotAllowCreateAliasForAlias = 400504 + NotAllowAliasCreateRateLimit = 400505 + NotAllowAliasBindRule = 400506 + NotAllowDifferentNamespaceBindRule = 400507 + Unauthorized = 401000 + NotAllowedAccess = 401001 + IPRateLimit = 403001 + APIRateLimit = 403002 + CMDBNotFindHost = 404001 + DataConflict = 409000 + InstanceTooManyRequests = 429001 + ExecuteException = 500000 + StoreLayerException = 500001 + CMDBPluginException = 500002 + ParseRoutingException = 500004 + ParseRateLimitException = 500005 + ParseCircuitBreakerException = 500006 + HeartbeatException = 500007 +) + +// code to string +// code的字符串描述信息 +var code2info = map[uint32]string{ + ExecuteSuccess: "execute success", + DataNoChange: "discover data is no change", + NoNeedUpdate: "update data is no change, no need to update", + BadRequest: "bad request", + ParseException: "request decode failed", + EmptyRequest: "empty request", + BatchSizeOverLimit: "batch size over the limit", + InvalidDiscoverResource: "invalid discover resource", + InvalidRequestID: "invalid request id", + InvalidUserName: "invalid user name", + InvalidUserToken: "invalid user token", + InvalidParameter: "invalid parameter", + EmptyQueryParameter: "query instance parameter is empty", + InvalidQueryInsParameter: "query instance, (service,namespace) or host is required", + InvalidNamespaceName: "invalid namespace name", + InvalidNamespaceOwners: "invalid namespace owners", + InvalidNamespaceToken: "invalid namespace token", + InvalidServiceName: "invalid service name", + InvalidServiceOwners: "invalid service owners", + InvalidServiceToken: "invalid service token", + InvalidServiceMetadata: "invalid service metadata", + InvalidServicePorts: "invalid service ports", + InvalidServiceBusiness: "invalid service business", + InvalidServiceDepartment: "invalid service department", + InvalidServiceCMDB: "invalid service CMDB", + InvalidServiceComment: "invalid service comment", + InvalidServiceAliasComment: "invalid service alias comment", + InvalidInstanceID: "invalid instance id", + InvalidInstanceHost: "invalid instance host", + InvalidInstancePort: "invalid instance port", + InvalidInstanceProtocol: "invalid instance protocol", + InvalidInstanceVersion: "invalid instance version", + InvalidInstanceLogicSet: "invalid instance logic set", + InvalidInstanceIsolate: "invalid instance isolate", + InvalidServiceAlias: "invalid service alias", + InvalidNamespaceWithAlias: "request namespace is not allow to create sid type alias", + InvalidServiceAliasOwners: "invalid service alias owners", + HealthCheckNotOpen: "server not open health check", + HeartbeatOnDisabledIns: "heartbeat on disabled instance", + HeartbeatExceedLimit: "instance can only heartbeat 1 time per second", + InvalidMetadata: "the length of metadata is too long or metadata contains invalid characters", + InvalidRateLimitID: "invalid rate limit id", + InvalidRateLimitLabels: "invalid rate limit labels", + InvalidRateLimitAmounts: "invalid rate limit amounts", + InvalidCircuitBreakerID: "invalid circuit breaker id", + InvalidCircuitBreakerVersion: "invalid circuit breaker version", + InvalidCircuitBreakerName: "invalid circuit breaker name", + InvalidCircuitBreakerNamespace: "invalid circuit breaker namespace", + InvalidCircuitBreakerOwners: "invalid circuit breaker owners", + InvalidCircuitBreakerToken: "invalid circuit breaker token", + InvalidCircuitBreakerBusiness: "invalid circuit breaker business", + InvalidCircuitBreakerDepartment: "invalid circuit breaker department", + InvalidCircuitBreakerComment: "invalid circuit breaker comment", + ExistedResource: "existed resource", + SameInstanceRequest: "the same instance request", + NotFoundResource: "not found resource", + ClientAPINotOpen: "client api is not open", + NotAllowBusinessService: "not allow requesting business service", + NotAllowAliasUpdate: "not allow service alias updating", + NotAllowAliasCreateInstance: "not allow service alias creating instance", + NotAllowAliasCreateRouting: "not allow service alias creating routing config", + NotAllowCreateAliasForAlias: "only source service can create alias", + NotAllowAliasCreateRateLimit: "not allow service alias creating rate limit", + NotAllowAliasBindRule: "not allow service alias binding rule", + NotAllowDifferentNamespaceBindRule: "not allow different namespace binding rule", + NamespaceExistedServices: "some services existed in namespace", + ServiceExistedInstances: "some instances existed in service", + ServiceExistedRoutings: "some routings existed in service", + ServiceExistedRateLimits: "some rate limits existed in service", + ServiceExistedCircuitBreakers: "some circuit breakers existed in service", + ServiceExistedAlias: "some aliases existed in service", + NamespaceExistedMeshResources: "some mesh resources existed in namespace", + NamespaceExistedCircuitBreakers: "some circuit breakers existed in namespace", + ExistReleasedConfig: "exist released config", + NotFoundService: "not found service", + NotFoundRouting: "not found routing", + NotFoundInstance: "not found instances", + NotFoundServiceAlias: "not found service alias", + NotFoundNamespace: "not found namespace", + NotFoundSourceService: "not found the source service link with the alias", + NotFoundRateLimit: "not found rate limit", + NotFoundCircuitBreaker: "not found circuit breaker", + NotFoundTagConfig: "not found tag config", + NotFoundMasterConfig: "not found master config", + NotFoundTagConfigOrService: "not found tag config or service, or relation already exists", + Unauthorized: "unauthorized", + NotAllowedAccess: "access is not approved", + IPRateLimit: "server limit the ip access", + APIRateLimit: "server limit the api access", + CMDBNotFindHost: "not found the host cmdb", + DataConflict: "data is conflict, please try again", + InstanceTooManyRequests: "your instance has too many requests", + ExecuteException: "execute exception", + StoreLayerException: "store layer exception", + CMDBPluginException: "cmdb plugin exception", + ParseRoutingException: "parsing routing failed", + ParseRateLimitException: "parse rate limit failed", + ParseCircuitBreakerException: "parse circuit breaker failed", + HeartbeatException: "heartbeat execute exception", + InvalidPlatformID: "invalid platform id", + InvalidPlatformName: "invalid platform name", + InvalidPlatformDomain: "invalid platform domain", + InvalidPlatformQPS: "invalid platform qps", + InvalidPlatformToken: "invalid platform token", + InvalidPlatformOwner: "invalid platform owner", + InvalidPlatformDepartment: "invalid platform department", + InvalidPlatformComment: "invalid platform comment", + NotFoundPlatform: "not found platform", + ServicesExistedMesh: "services existed mesh", + ResourcesExistedMesh: "resources existed mesh", + ServiceSubscribedByMeshes: "service subscribed by some mesh", + InvalidMeshParameter: "invalid mesh parameter", + InvalidFluxRateLimitId: "invalid flux ratelimit id", + InvalidFluxRateLimitQps: "invalid flux ratelimit qps", + InvalidFluxRateLimitSetKey: "invalid flux ratelimit key", +} + +// code to info +func Code2Info(code uint32) string { + info, ok := code2info[code] + if ok { + return info + } + + return "" +} diff --git a/common/api/v1/configrelease.pb.go b/common/api/v1/configrelease.pb.go new file mode 100644 index 000000000..86e838e6f --- /dev/null +++ b/common/api/v1/configrelease.pb.go @@ -0,0 +1,155 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: configrelease.proto + +package v1 + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import wrappers "github.com/golang/protobuf/ptypes/wrappers" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type ConfigRelease struct { + Service *Service `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` + Ctime *wrappers.StringValue `protobuf:"bytes,2,opt,name=ctime,proto3" json:"ctime,omitempty"` + Mtime *wrappers.StringValue `protobuf:"bytes,3,opt,name=mtime,proto3" json:"mtime,omitempty"` + CircuitBreaker *CircuitBreaker `protobuf:"bytes,4,opt,name=circuitBreaker,proto3" json:"circuitBreaker,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ConfigRelease) Reset() { *m = ConfigRelease{} } +func (m *ConfigRelease) String() string { return proto.CompactTextString(m) } +func (*ConfigRelease) ProtoMessage() {} +func (*ConfigRelease) Descriptor() ([]byte, []int) { + return fileDescriptor_configrelease_c3cd47f6dfc8ea11, []int{0} +} +func (m *ConfigRelease) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ConfigRelease.Unmarshal(m, b) +} +func (m *ConfigRelease) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ConfigRelease.Marshal(b, m, deterministic) +} +func (dst *ConfigRelease) XXX_Merge(src proto.Message) { + xxx_messageInfo_ConfigRelease.Merge(dst, src) +} +func (m *ConfigRelease) XXX_Size() int { + return xxx_messageInfo_ConfigRelease.Size(m) +} +func (m *ConfigRelease) XXX_DiscardUnknown() { + xxx_messageInfo_ConfigRelease.DiscardUnknown(m) +} + +var xxx_messageInfo_ConfigRelease proto.InternalMessageInfo + +func (m *ConfigRelease) GetService() *Service { + if m != nil { + return m.Service + } + return nil +} + +func (m *ConfigRelease) GetCtime() *wrappers.StringValue { + if m != nil { + return m.Ctime + } + return nil +} + +func (m *ConfigRelease) GetMtime() *wrappers.StringValue { + if m != nil { + return m.Mtime + } + return nil +} + +func (m *ConfigRelease) GetCircuitBreaker() *CircuitBreaker { + if m != nil { + return m.CircuitBreaker + } + return nil +} + +type ConfigWithService struct { + Services []*Service `protobuf:"bytes,1,rep,name=services,proto3" json:"services,omitempty"` + CircuitBreaker *CircuitBreaker `protobuf:"bytes,2,opt,name=circuitBreaker,proto3" json:"circuitBreaker,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ConfigWithService) Reset() { *m = ConfigWithService{} } +func (m *ConfigWithService) String() string { return proto.CompactTextString(m) } +func (*ConfigWithService) ProtoMessage() {} +func (*ConfigWithService) Descriptor() ([]byte, []int) { + return fileDescriptor_configrelease_c3cd47f6dfc8ea11, []int{1} +} +func (m *ConfigWithService) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ConfigWithService.Unmarshal(m, b) +} +func (m *ConfigWithService) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ConfigWithService.Marshal(b, m, deterministic) +} +func (dst *ConfigWithService) XXX_Merge(src proto.Message) { + xxx_messageInfo_ConfigWithService.Merge(dst, src) +} +func (m *ConfigWithService) XXX_Size() int { + return xxx_messageInfo_ConfigWithService.Size(m) +} +func (m *ConfigWithService) XXX_DiscardUnknown() { + xxx_messageInfo_ConfigWithService.DiscardUnknown(m) +} + +var xxx_messageInfo_ConfigWithService proto.InternalMessageInfo + +func (m *ConfigWithService) GetServices() []*Service { + if m != nil { + return m.Services + } + return nil +} + +func (m *ConfigWithService) GetCircuitBreaker() *CircuitBreaker { + if m != nil { + return m.CircuitBreaker + } + return nil +} + +func init() { + proto.RegisterType((*ConfigRelease)(nil), "v1.ConfigRelease") + proto.RegisterType((*ConfigWithService)(nil), "v1.ConfigWithService") +} + +func init() { proto.RegisterFile("configrelease.proto", fileDescriptor_configrelease_c3cd47f6dfc8ea11) } + +var fileDescriptor_configrelease_c3cd47f6dfc8ea11 = []byte{ + // 242 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x8f, 0xcf, 0x4a, 0x03, 0x31, + 0x18, 0xc4, 0xd9, 0xad, 0xff, 0x48, 0xa9, 0x60, 0xf4, 0x10, 0x16, 0x91, 0x52, 0x10, 0x3d, 0xa5, + 0xb4, 0xde, 0x3c, 0xda, 0x37, 0x48, 0x41, 0xcf, 0xd9, 0xf0, 0x35, 0x06, 0xb7, 0xcd, 0xf2, 0x25, + 0xbb, 0xfa, 0xac, 0x3e, 0x8d, 0x34, 0x5f, 0x2a, 0x58, 0x3c, 0xec, 0x75, 0x66, 0x7e, 0xcc, 0x0c, + 0xbb, 0x36, 0x7e, 0xb7, 0x71, 0x16, 0xa1, 0x01, 0x1d, 0x40, 0xb6, 0xe8, 0xa3, 0xe7, 0x65, 0xbf, + 0xa8, 0xee, 0xac, 0xf7, 0xb6, 0x81, 0x79, 0x52, 0xea, 0x6e, 0x33, 0xff, 0x44, 0xdd, 0xb6, 0x80, + 0x81, 0x32, 0xd5, 0x24, 0x00, 0xf6, 0xce, 0x64, 0xa4, 0xba, 0x31, 0x0e, 0x4d, 0xe7, 0x62, 0x8d, + 0xa0, 0x3f, 0x00, 0x49, 0x9d, 0x7d, 0x17, 0x6c, 0xb2, 0x4a, 0x05, 0x8a, 0x0a, 0xf8, 0x3d, 0x3b, + 0xcf, 0xa0, 0x28, 0xa6, 0xc5, 0xe3, 0x78, 0x39, 0x96, 0xfd, 0x42, 0xae, 0x49, 0x52, 0x07, 0x8f, + 0x2f, 0xd9, 0xa9, 0x89, 0x6e, 0x0b, 0xa2, 0x4c, 0xa1, 0x5b, 0x49, 0x6b, 0xe4, 0x61, 0x8d, 0x5c, + 0x47, 0x74, 0x3b, 0xfb, 0xaa, 0x9b, 0x0e, 0x14, 0x45, 0xf7, 0xcc, 0x36, 0x31, 0xa3, 0x21, 0x4c, + 0x8a, 0xf2, 0x67, 0x76, 0x99, 0x87, 0xbf, 0xd0, 0x70, 0x71, 0x92, 0x60, 0xbe, 0x5f, 0xb5, 0xfa, + 0xe3, 0xa8, 0xa3, 0xe4, 0xec, 0x8b, 0x5d, 0xd1, 0xb7, 0x37, 0x17, 0xdf, 0xf3, 0x03, 0xfe, 0xc0, + 0x2e, 0xf2, 0x87, 0x20, 0x8a, 0xe9, 0xe8, 0xf8, 0xe0, 0xaf, 0xf9, 0x4f, 0x73, 0x39, 0xb4, 0xb9, + 0x3e, 0x4b, 0x97, 0x9e, 0x7e, 0x02, 0x00, 0x00, 0xff, 0xff, 0xa6, 0x30, 0x71, 0xf6, 0xbd, 0x01, + 0x00, 0x00, +} diff --git a/common/api/v1/configrelease.proto b/common/api/v1/configrelease.proto new file mode 100644 index 000000000..b5e5b6c07 --- /dev/null +++ b/common/api/v1/configrelease.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +package v1; + +import "google/protobuf/wrappers.proto"; +import "service.proto"; +import "circuitbreaker.proto"; + +message ConfigRelease { + Service service = 1; + google.protobuf.StringValue ctime = 2; + google.protobuf.StringValue mtime = 3; + + CircuitBreaker circuitBreaker = 4; +} + +message ConfigWithService { + repeated Service services = 1; + CircuitBreaker circuitBreaker = 2; +} \ No newline at end of file diff --git a/common/api/v1/grpcapi.pb.go b/common/api/v1/grpcapi.pb.go new file mode 100644 index 000000000..7748d261e --- /dev/null +++ b/common/api/v1/grpcapi.pb.go @@ -0,0 +1,290 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: grpcapi.proto + +package v1 + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// PolarisGRPCClient is the client API for PolarisGRPC service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type PolarisGRPCClient interface { + // 客户端上报 + ReportClient(ctx context.Context, in *Client, opts ...grpc.CallOption) (*Response, error) + // 被调方注册服务实例 + RegisterInstance(ctx context.Context, in *Instance, opts ...grpc.CallOption) (*Response, error) + // 被调方反注册服务实例 + DeregisterInstance(ctx context.Context, in *Instance, opts ...grpc.CallOption) (*Response, error) + // 统一发现接口 + Discover(ctx context.Context, opts ...grpc.CallOption) (PolarisGRPC_DiscoverClient, error) + // 被调方上报心跳 + Heartbeat(ctx context.Context, in *Instance, opts ...grpc.CallOption) (*Response, error) +} + +type polarisGRPCClient struct { + cc *grpc.ClientConn +} + +func NewPolarisGRPCClient(cc *grpc.ClientConn) PolarisGRPCClient { + return &polarisGRPCClient{cc} +} + +func (c *polarisGRPCClient) ReportClient(ctx context.Context, in *Client, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/v1.PolarisGRPC/ReportClient", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *polarisGRPCClient) RegisterInstance(ctx context.Context, in *Instance, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/v1.PolarisGRPC/RegisterInstance", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *polarisGRPCClient) DeregisterInstance(ctx context.Context, in *Instance, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/v1.PolarisGRPC/DeregisterInstance", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *polarisGRPCClient) Discover(ctx context.Context, opts ...grpc.CallOption) (PolarisGRPC_DiscoverClient, error) { + stream, err := c.cc.NewStream(ctx, &_PolarisGRPC_serviceDesc.Streams[0], "/v1.PolarisGRPC/Discover", opts...) + if err != nil { + return nil, err + } + x := &polarisGRPCDiscoverClient{stream} + return x, nil +} + +type PolarisGRPC_DiscoverClient interface { + Send(*DiscoverRequest) error + Recv() (*DiscoverResponse, error) + grpc.ClientStream +} + +type polarisGRPCDiscoverClient struct { + grpc.ClientStream +} + +func (x *polarisGRPCDiscoverClient) Send(m *DiscoverRequest) error { + return x.ClientStream.SendMsg(m) +} + +func (x *polarisGRPCDiscoverClient) Recv() (*DiscoverResponse, error) { + m := new(DiscoverResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *polarisGRPCClient) Heartbeat(ctx context.Context, in *Instance, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/v1.PolarisGRPC/Heartbeat", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// PolarisGRPCServer is the server API for PolarisGRPC service. +type PolarisGRPCServer interface { + // 客户端上报 + ReportClient(context.Context, *Client) (*Response, error) + // 被调方注册服务实例 + RegisterInstance(context.Context, *Instance) (*Response, error) + // 被调方反注册服务实例 + DeregisterInstance(context.Context, *Instance) (*Response, error) + // 统一发现接口 + Discover(PolarisGRPC_DiscoverServer) error + // 被调方上报心跳 + Heartbeat(context.Context, *Instance) (*Response, error) +} + +func RegisterPolarisGRPCServer(s *grpc.Server, srv PolarisGRPCServer) { + s.RegisterService(&_PolarisGRPC_serviceDesc, srv) +} + +func _PolarisGRPC_ReportClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Client) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PolarisGRPCServer).ReportClient(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/v1.PolarisGRPC/ReportClient", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PolarisGRPCServer).ReportClient(ctx, req.(*Client)) + } + return interceptor(ctx, in, info, handler) +} + +func _PolarisGRPC_RegisterInstance_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Instance) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PolarisGRPCServer).RegisterInstance(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/v1.PolarisGRPC/RegisterInstance", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PolarisGRPCServer).RegisterInstance(ctx, req.(*Instance)) + } + return interceptor(ctx, in, info, handler) +} + +func _PolarisGRPC_DeregisterInstance_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Instance) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PolarisGRPCServer).DeregisterInstance(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/v1.PolarisGRPC/DeregisterInstance", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PolarisGRPCServer).DeregisterInstance(ctx, req.(*Instance)) + } + return interceptor(ctx, in, info, handler) +} + +func _PolarisGRPC_Discover_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(PolarisGRPCServer).Discover(&polarisGRPCDiscoverServer{stream}) +} + +type PolarisGRPC_DiscoverServer interface { + Send(*DiscoverResponse) error + Recv() (*DiscoverRequest, error) + grpc.ServerStream +} + +type polarisGRPCDiscoverServer struct { + grpc.ServerStream +} + +func (x *polarisGRPCDiscoverServer) Send(m *DiscoverResponse) error { + return x.ServerStream.SendMsg(m) +} + +func (x *polarisGRPCDiscoverServer) Recv() (*DiscoverRequest, error) { + m := new(DiscoverRequest) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _PolarisGRPC_Heartbeat_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Instance) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PolarisGRPCServer).Heartbeat(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/v1.PolarisGRPC/Heartbeat", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PolarisGRPCServer).Heartbeat(ctx, req.(*Instance)) + } + return interceptor(ctx, in, info, handler) +} + +var _PolarisGRPC_serviceDesc = grpc.ServiceDesc{ + ServiceName: "v1.PolarisGRPC", + HandlerType: (*PolarisGRPCServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ReportClient", + Handler: _PolarisGRPC_ReportClient_Handler, + }, + { + MethodName: "RegisterInstance", + Handler: _PolarisGRPC_RegisterInstance_Handler, + }, + { + MethodName: "DeregisterInstance", + Handler: _PolarisGRPC_DeregisterInstance_Handler, + }, + { + MethodName: "Heartbeat", + Handler: _PolarisGRPC_Heartbeat_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "Discover", + Handler: _PolarisGRPC_Discover_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "grpcapi.proto", +} + +func init() { proto.RegisterFile("grpcapi.proto", fileDescriptor_grpcapi_1bc1b7fe180cc761) } + +var fileDescriptor_grpcapi_1bc1b7fe180cc761 = []byte{ + // 208 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x90, 0xbd, 0x4a, 0x04, 0x31, + 0x10, 0xc7, 0xbd, 0x2b, 0x44, 0xc7, 0x3d, 0x91, 0xd1, 0x2a, 0xa5, 0x95, 0x5a, 0x2c, 0x77, 0x6b, + 0x69, 0xb9, 0x0b, 0x6a, 0xb7, 0xe4, 0x0d, 0xb2, 0x61, 0x58, 0x02, 0x4b, 0x12, 0x67, 0x62, 0x5e, + 0xc1, 0xd7, 0x96, 0xfd, 0x48, 0x21, 0x82, 0x60, 0xf7, 0xff, 0xc8, 0x2f, 0xc3, 0x0c, 0x1c, 0x46, + 0x8e, 0xd6, 0x44, 0x57, 0x47, 0x0e, 0x29, 0xe0, 0x3e, 0x9f, 0x54, 0x65, 0x27, 0x47, 0x3e, 0xad, + 0x89, 0x3a, 0x08, 0x71, 0x76, 0x96, 0x8a, 0x65, 0xfa, 0xf8, 0x24, 0x29, 0xed, 0x35, 0x93, 0xc4, + 0xe0, 0x65, 0xab, 0x9b, 0xaf, 0x3d, 0x5c, 0xf5, 0x61, 0x32, 0xec, 0xe4, 0x55, 0xf7, 0x2d, 0x3e, + 0x41, 0xa5, 0x29, 0x06, 0x4e, 0xed, 0xf2, 0x27, 0x42, 0x9d, 0x4f, 0xf5, 0xaa, 0x55, 0x35, 0x6b, + 0xbd, 0xf1, 0xf7, 0x67, 0x78, 0x84, 0x1b, 0x4d, 0xa3, 0x93, 0x44, 0xfc, 0xee, 0x25, 0x19, 0x6f, + 0x09, 0x97, 0x37, 0xc5, 0xfd, 0x22, 0x1a, 0xc0, 0x8e, 0xf8, 0x7f, 0xcc, 0x0b, 0x5c, 0x74, 0x4e, + 0x6c, 0xc8, 0xc4, 0x78, 0x3b, 0x77, 0xc5, 0xe9, 0x75, 0x31, 0x75, 0xf7, 0x33, 0x2c, 0xe0, 0xc3, + 0xee, 0xb8, 0xc3, 0x47, 0xb8, 0x7c, 0x23, 0xc3, 0x69, 0x20, 0x93, 0xfe, 0x9e, 0x33, 0x9c, 0x2f, + 0x07, 0x79, 0xfe, 0x0e, 0x00, 0x00, 0xff, 0xff, 0xa5, 0x5a, 0x87, 0xc9, 0x61, 0x01, 0x00, 0x00, +} diff --git a/common/api/v1/grpcapi.proto b/common/api/v1/grpcapi.proto new file mode 100644 index 000000000..1fede7da0 --- /dev/null +++ b/common/api/v1/grpcapi.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; + +package v1; + +import "client.proto"; +import "service.proto"; +import "request.proto"; +import "response.proto"; + +service PolarisGRPC { + // 客户端上报 + rpc ReportClient(Client) returns(Response) {} + + // 被调方注册服务实例 + rpc RegisterInstance(Instance) returns(Response) {} + // 被调方反注册服务实例 + rpc DeregisterInstance(Instance) returns(Response) {} + + // 统一发现接口 + rpc Discover(stream DiscoverRequest) returns(stream DiscoverResponse) {} + + // 被调方上报心跳 + rpc Heartbeat(Instance) returns(Response) {} +} diff --git a/common/api/v1/model.pb.go b/common/api/v1/model.pb.go new file mode 100644 index 000000000..74600cab6 --- /dev/null +++ b/common/api/v1/model.pb.go @@ -0,0 +1,209 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: model.proto + +package v1 + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import wrappers "github.com/golang/protobuf/ptypes/wrappers" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type MatchString_MatchStringType int32 + +const ( + MatchString_EXACT MatchString_MatchStringType = 0 + MatchString_REGEX MatchString_MatchStringType = 1 +) + +var MatchString_MatchStringType_name = map[int32]string{ + 0: "EXACT", + 1: "REGEX", +} +var MatchString_MatchStringType_value = map[string]int32{ + "EXACT": 0, + "REGEX": 1, +} + +func (x MatchString_MatchStringType) String() string { + return proto.EnumName(MatchString_MatchStringType_name, int32(x)) +} +func (MatchString_MatchStringType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_model_534f8598bf3352f4, []int{1, 0} +} + +type MatchString_ValueType int32 + +const ( + MatchString_TEXT MatchString_ValueType = 0 + MatchString_PARAMETER MatchString_ValueType = 1 + MatchString_VARIABLE MatchString_ValueType = 2 +) + +var MatchString_ValueType_name = map[int32]string{ + 0: "TEXT", + 1: "PARAMETER", + 2: "VARIABLE", +} +var MatchString_ValueType_value = map[string]int32{ + "TEXT": 0, + "PARAMETER": 1, + "VARIABLE": 2, +} + +func (x MatchString_ValueType) String() string { + return proto.EnumName(MatchString_ValueType_name, int32(x)) +} +func (MatchString_ValueType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_model_534f8598bf3352f4, []int{1, 1} +} + +type Location struct { + Region *wrappers.StringValue `protobuf:"bytes,1,opt,name=region,proto3" json:"region,omitempty"` + Zone *wrappers.StringValue `protobuf:"bytes,2,opt,name=zone,proto3" json:"zone,omitempty"` + Campus *wrappers.StringValue `protobuf:"bytes,3,opt,name=campus,proto3" json:"campus,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Location) Reset() { *m = Location{} } +func (m *Location) String() string { return proto.CompactTextString(m) } +func (*Location) ProtoMessage() {} +func (*Location) Descriptor() ([]byte, []int) { + return fileDescriptor_model_534f8598bf3352f4, []int{0} +} +func (m *Location) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Location.Unmarshal(m, b) +} +func (m *Location) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Location.Marshal(b, m, deterministic) +} +func (dst *Location) XXX_Merge(src proto.Message) { + xxx_messageInfo_Location.Merge(dst, src) +} +func (m *Location) XXX_Size() int { + return xxx_messageInfo_Location.Size(m) +} +func (m *Location) XXX_DiscardUnknown() { + xxx_messageInfo_Location.DiscardUnknown(m) +} + +var xxx_messageInfo_Location proto.InternalMessageInfo + +func (m *Location) GetRegion() *wrappers.StringValue { + if m != nil { + return m.Region + } + return nil +} + +func (m *Location) GetZone() *wrappers.StringValue { + if m != nil { + return m.Zone + } + return nil +} + +func (m *Location) GetCampus() *wrappers.StringValue { + if m != nil { + return m.Campus + } + return nil +} + +type MatchString struct { + Type MatchString_MatchStringType `protobuf:"varint,1,opt,name=type,proto3,enum=v1.MatchString_MatchStringType" json:"type,omitempty"` + Value *wrappers.StringValue `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + ValueType MatchString_ValueType `protobuf:"varint,3,opt,name=value_type,json=valueType,proto3,enum=v1.MatchString_ValueType" json:"value_type,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *MatchString) Reset() { *m = MatchString{} } +func (m *MatchString) String() string { return proto.CompactTextString(m) } +func (*MatchString) ProtoMessage() {} +func (*MatchString) Descriptor() ([]byte, []int) { + return fileDescriptor_model_534f8598bf3352f4, []int{1} +} +func (m *MatchString) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_MatchString.Unmarshal(m, b) +} +func (m *MatchString) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_MatchString.Marshal(b, m, deterministic) +} +func (dst *MatchString) XXX_Merge(src proto.Message) { + xxx_messageInfo_MatchString.Merge(dst, src) +} +func (m *MatchString) XXX_Size() int { + return xxx_messageInfo_MatchString.Size(m) +} +func (m *MatchString) XXX_DiscardUnknown() { + xxx_messageInfo_MatchString.DiscardUnknown(m) +} + +var xxx_messageInfo_MatchString proto.InternalMessageInfo + +func (m *MatchString) GetType() MatchString_MatchStringType { + if m != nil { + return m.Type + } + return MatchString_EXACT +} + +func (m *MatchString) GetValue() *wrappers.StringValue { + if m != nil { + return m.Value + } + return nil +} + +func (m *MatchString) GetValueType() MatchString_ValueType { + if m != nil { + return m.ValueType + } + return MatchString_TEXT +} + +func init() { + proto.RegisterType((*Location)(nil), "v1.Location") + proto.RegisterType((*MatchString)(nil), "v1.MatchString") + proto.RegisterEnum("v1.MatchString_MatchStringType", MatchString_MatchStringType_name, MatchString_MatchStringType_value) + proto.RegisterEnum("v1.MatchString_ValueType", MatchString_ValueType_name, MatchString_ValueType_value) +} + +func init() { proto.RegisterFile("model.proto", fileDescriptor_model_534f8598bf3352f4) } + +var fileDescriptor_model_534f8598bf3352f4 = []byte{ + // 291 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x8e, 0xc1, 0x4e, 0xb3, 0x40, + 0x14, 0x85, 0x7f, 0x28, 0x6d, 0xe0, 0xf2, 0xab, 0x64, 0x56, 0x68, 0x8c, 0x1a, 0x36, 0xba, 0x9a, + 0x5a, 0xea, 0xc2, 0x2d, 0x9a, 0x89, 0x31, 0x69, 0x13, 0x33, 0x92, 0x86, 0x9d, 0xa1, 0x38, 0x22, + 0x09, 0x65, 0x26, 0x14, 0x30, 0xf5, 0x1d, 0x7c, 0x0f, 0x1f, 0xd3, 0x70, 0xb1, 0xa6, 0xe9, 0x8a, + 0xdd, 0xc9, 0xcc, 0x77, 0xee, 0x77, 0xc0, 0x5e, 0xc9, 0x57, 0x91, 0x53, 0x55, 0xca, 0x4a, 0x12, + 0xbd, 0x99, 0x9c, 0x9c, 0xa5, 0x52, 0xa6, 0xb9, 0x18, 0xe3, 0xcb, 0xb2, 0x7e, 0x1b, 0x7f, 0x94, + 0xb1, 0x52, 0xa2, 0x5c, 0x77, 0x8c, 0xf7, 0xad, 0x81, 0x39, 0x93, 0x49, 0x5c, 0x65, 0xb2, 0x20, + 0x37, 0x30, 0x2a, 0x45, 0x9a, 0xc9, 0xc2, 0xd5, 0x2e, 0xb4, 0x2b, 0xdb, 0x3f, 0xa5, 0x5d, 0x9b, + 0x6e, 0xdb, 0xf4, 0xb9, 0x2a, 0xb3, 0x22, 0x5d, 0xc4, 0x79, 0x2d, 0xf8, 0x2f, 0x4b, 0xae, 0xc1, + 0xf8, 0x94, 0x85, 0x70, 0xf5, 0x1e, 0x1d, 0x24, 0x5b, 0x4f, 0x12, 0xaf, 0x54, 0xbd, 0x76, 0x07, + 0x7d, 0x3c, 0x1d, 0xeb, 0x7d, 0xe9, 0x60, 0xcf, 0xe3, 0x2a, 0x79, 0xef, 0x3e, 0xc9, 0x14, 0x8c, + 0x6a, 0xa3, 0x04, 0x6e, 0x3d, 0xf4, 0xcf, 0x69, 0x33, 0xa1, 0x3b, 0xdf, 0xbb, 0x39, 0xdc, 0x28, + 0xc1, 0x11, 0x26, 0x3e, 0x0c, 0x9b, 0xf6, 0x6a, 0xaf, 0xb5, 0x1d, 0x4a, 0x6e, 0x01, 0x30, 0xbc, + 0xa0, 0x6e, 0x80, 0xba, 0xe3, 0x7d, 0x1d, 0x36, 0x50, 0x64, 0x35, 0xdb, 0xe8, 0x5d, 0xc2, 0xd1, + 0xde, 0x0c, 0x62, 0xc1, 0x90, 0x45, 0xc1, 0x7d, 0xe8, 0xfc, 0x6b, 0x23, 0x67, 0x0f, 0x2c, 0x72, + 0x34, 0xcf, 0x07, 0xeb, 0xef, 0x00, 0x31, 0xc1, 0x08, 0x59, 0xd4, 0x12, 0x07, 0x60, 0x3d, 0x05, + 0x3c, 0x98, 0xb3, 0x90, 0x71, 0x47, 0x23, 0xff, 0xc1, 0x5c, 0x04, 0xfc, 0x31, 0xb8, 0x9b, 0x31, + 0x47, 0x5f, 0x8e, 0x70, 0xf3, 0xf4, 0x27, 0x00, 0x00, 0xff, 0xff, 0x28, 0x8b, 0x9e, 0xbf, 0xf4, + 0x01, 0x00, 0x00, +} diff --git a/common/api/v1/model.proto b/common/api/v1/model.proto new file mode 100644 index 000000000..bef9a01ca --- /dev/null +++ b/common/api/v1/model.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; + +package v1; + +import "google/protobuf/wrappers.proto"; + +message Location { + google.protobuf.StringValue region = 1; + google.protobuf.StringValue zone = 2; + google.protobuf.StringValue campus = 3; +} + +message MatchString { + enum MatchStringType { + EXACT = 0; + REGEX = 1; + } + + enum ValueType { + TEXT = 0; + PARAMETER = 1; + VARIABLE = 2; + } + + MatchStringType type = 1; + google.protobuf.StringValue value = 2; + ValueType value_type = 3; +} diff --git a/common/api/v1/platform.pb.go b/common/api/v1/platform.pb.go new file mode 100644 index 000000000..14bc7148f --- /dev/null +++ b/common/api/v1/platform.pb.go @@ -0,0 +1,166 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: platform.proto + +package v1 + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import wrappers "github.com/golang/protobuf/ptypes/wrappers" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type Platform struct { + // 平台ID,唯一标识 + Id *wrappers.StringValue `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // 平台名称 + Name *wrappers.StringValue `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + // 平台域名 + Domain *wrappers.StringValue `protobuf:"bytes,3,opt,name=domain,proto3" json:"domain,omitempty"` + // 调用频率 + Qps *wrappers.UInt32Value `protobuf:"bytes,4,opt,name=qps,proto3" json:"qps,omitempty"` + // 平台token + Token *wrappers.StringValue `protobuf:"bytes,5,opt,name=token,proto3" json:"token,omitempty"` + // 负责人 + Owner *wrappers.StringValue `protobuf:"bytes,6,opt,name=owner,proto3" json:"owner,omitempty"` + // 部门 + Department *wrappers.StringValue `protobuf:"bytes,7,opt,name=department,proto3" json:"department,omitempty"` + // 描述 + Comment *wrappers.StringValue `protobuf:"bytes,8,opt,name=comment,proto3" json:"comment,omitempty"` + // 申请时间 + Ctime *wrappers.StringValue `protobuf:"bytes,9,opt,name=ctime,proto3" json:"ctime,omitempty"` + // 修改时间 + Mtime *wrappers.StringValue `protobuf:"bytes,10,opt,name=mtime,proto3" json:"mtime,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Platform) Reset() { *m = Platform{} } +func (m *Platform) String() string { return proto.CompactTextString(m) } +func (*Platform) ProtoMessage() {} +func (*Platform) Descriptor() ([]byte, []int) { + return fileDescriptor_platform_91ca9823fa0e864e, []int{0} +} +func (m *Platform) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Platform.Unmarshal(m, b) +} +func (m *Platform) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Platform.Marshal(b, m, deterministic) +} +func (dst *Platform) XXX_Merge(src proto.Message) { + xxx_messageInfo_Platform.Merge(dst, src) +} +func (m *Platform) XXX_Size() int { + return xxx_messageInfo_Platform.Size(m) +} +func (m *Platform) XXX_DiscardUnknown() { + xxx_messageInfo_Platform.DiscardUnknown(m) +} + +var xxx_messageInfo_Platform proto.InternalMessageInfo + +func (m *Platform) GetId() *wrappers.StringValue { + if m != nil { + return m.Id + } + return nil +} + +func (m *Platform) GetName() *wrappers.StringValue { + if m != nil { + return m.Name + } + return nil +} + +func (m *Platform) GetDomain() *wrappers.StringValue { + if m != nil { + return m.Domain + } + return nil +} + +func (m *Platform) GetQps() *wrappers.UInt32Value { + if m != nil { + return m.Qps + } + return nil +} + +func (m *Platform) GetToken() *wrappers.StringValue { + if m != nil { + return m.Token + } + return nil +} + +func (m *Platform) GetOwner() *wrappers.StringValue { + if m != nil { + return m.Owner + } + return nil +} + +func (m *Platform) GetDepartment() *wrappers.StringValue { + if m != nil { + return m.Department + } + return nil +} + +func (m *Platform) GetComment() *wrappers.StringValue { + if m != nil { + return m.Comment + } + return nil +} + +func (m *Platform) GetCtime() *wrappers.StringValue { + if m != nil { + return m.Ctime + } + return nil +} + +func (m *Platform) GetMtime() *wrappers.StringValue { + if m != nil { + return m.Mtime + } + return nil +} + +func init() { + proto.RegisterType((*Platform)(nil), "v1.Platform") +} + +func init() { proto.RegisterFile("platform.proto", fileDescriptor_platform_91ca9823fa0e864e) } + +var fileDescriptor_platform_91ca9823fa0e864e = []byte{ + // 244 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0xce, 0xc1, 0x4a, 0x03, 0x31, + 0x10, 0xc6, 0x71, 0xba, 0xdd, 0x6e, 0xeb, 0x08, 0x1e, 0x72, 0x0a, 0x22, 0x22, 0x9e, 0x3c, 0x48, + 0xaa, 0x5b, 0xf1, 0xe4, 0x0b, 0x78, 0x13, 0x45, 0xef, 0x69, 0x77, 0xba, 0x04, 0x37, 0x99, 0x98, + 0xa6, 0xf6, 0x4d, 0x7c, 0x5e, 0xd9, 0xd9, 0x2e, 0x78, 0x11, 0xe6, 0x9a, 0xfc, 0x7f, 0xcc, 0x07, + 0x67, 0xb1, 0xb3, 0x79, 0x4b, 0xc9, 0x9b, 0x98, 0x28, 0x93, 0x2a, 0xbe, 0xef, 0xcf, 0x2f, 0x5b, + 0xa2, 0xb6, 0xc3, 0x25, 0xbf, 0xac, 0xf7, 0xdb, 0xe5, 0x21, 0xd9, 0x18, 0x31, 0xed, 0x86, 0xe6, + 0xfa, 0xa7, 0x84, 0xc5, 0xcb, 0x91, 0xa9, 0x5b, 0x28, 0x5c, 0xa3, 0x27, 0x57, 0x93, 0x9b, 0xd3, + 0xfa, 0xc2, 0x0c, 0xd2, 0x8c, 0xd2, 0xbc, 0xe5, 0xe4, 0x42, 0xfb, 0x61, 0xbb, 0x3d, 0xbe, 0x16, + 0xae, 0x51, 0x77, 0x50, 0x06, 0xeb, 0x51, 0x17, 0x82, 0x9e, 0x4b, 0xf5, 0x00, 0x55, 0x43, 0xde, + 0xba, 0xa0, 0xa7, 0x02, 0x73, 0x6c, 0x95, 0x81, 0xe9, 0x57, 0xdc, 0xe9, 0xf2, 0x1f, 0xf2, 0xfe, + 0x1c, 0xf2, 0xaa, 0x1e, 0x48, 0x1f, 0xaa, 0x1a, 0x66, 0x99, 0x3e, 0x31, 0xe8, 0x99, 0xe0, 0xc8, + 0x90, 0xf6, 0x86, 0x0e, 0x01, 0x93, 0xae, 0x24, 0x86, 0x53, 0xf5, 0x04, 0xd0, 0x60, 0xb4, 0x29, + 0x7b, 0x0c, 0x59, 0xcf, 0x05, 0xf0, 0x4f, 0xaf, 0x1e, 0x61, 0xbe, 0x21, 0xcf, 0x74, 0x21, 0xa0, + 0x63, 0xdc, 0x2f, 0xdd, 0x64, 0xe7, 0x51, 0x9f, 0x48, 0x96, 0x72, 0xda, 0x1b, 0xcf, 0x06, 0x24, + 0x86, 0xd3, 0x75, 0xc5, 0x9f, 0xab, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x9e, 0xbb, 0xb7, 0x05, + 0x55, 0x02, 0x00, 0x00, +} diff --git a/common/api/v1/platform.proto b/common/api/v1/platform.proto new file mode 100644 index 000000000..92285efee --- /dev/null +++ b/common/api/v1/platform.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; + +package v1; + +import "google/protobuf/wrappers.proto"; + +message Platform { + // 平台ID,唯一标识 + google.protobuf.StringValue id = 1; + // 平台名称 + google.protobuf.StringValue name = 2; + // 平台域名 + google.protobuf.StringValue domain = 3; + // 调用频率 + google.protobuf.UInt32Value qps = 4; + // 平台token + google.protobuf.StringValue token = 5; + // 负责人 + google.protobuf.StringValue owner = 6; + // 部门 + google.protobuf.StringValue department = 7; + // 描述 + google.protobuf.StringValue comment = 8; + // 申请时间 + google.protobuf.StringValue ctime = 9; + // 修改时间 + google.protobuf.StringValue mtime = 10; +} \ No newline at end of file diff --git a/common/api/v1/ratelimit.pb.go b/common/api/v1/ratelimit.pb.go new file mode 100644 index 000000000..9a66fb3a4 --- /dev/null +++ b/common/api/v1/ratelimit.pb.go @@ -0,0 +1,1160 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: ratelimit.proto + +package v1 + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import duration "github.com/golang/protobuf/ptypes/duration" +import wrappers "github.com/golang/protobuf/ptypes/wrappers" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +// 限流资源 +type Rule_Resource int32 + +const ( + // 针对QPS进行限流 + Rule_QPS Rule_Resource = 0 + // 针对并发数进行限流 + Rule_CONCURRENCY Rule_Resource = 1 +) + +var Rule_Resource_name = map[int32]string{ + 0: "QPS", + 1: "CONCURRENCY", +} +var Rule_Resource_value = map[string]int32{ + "QPS": 0, + "CONCURRENCY": 1, +} + +func (x Rule_Resource) String() string { + return proto.EnumName(Rule_Resource_name, int32(x)) +} +func (Rule_Resource) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_ratelimit_2b495c7362132910, []int{1, 0} +} + +// 限流类型 +// global全局限流(默认)或者local单机限流 +type Rule_Type int32 + +const ( + Rule_GLOBAL Rule_Type = 0 + Rule_LOCAL Rule_Type = 1 +) + +var Rule_Type_name = map[int32]string{ + 0: "GLOBAL", + 1: "LOCAL", +} +var Rule_Type_value = map[string]int32{ + "GLOBAL": 0, + "LOCAL": 1, +} + +func (x Rule_Type) String() string { + return proto.EnumName(Rule_Type_name, int32(x)) +} +func (Rule_Type) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_ratelimit_2b495c7362132910, []int{1, 1} +} + +// 限流阈值模式 +type Rule_AmountMode int32 + +const ( + Rule_GLOBAL_TOTAL Rule_AmountMode = 0 + Rule_SHARE_EQUALLY Rule_AmountMode = 1 +) + +var Rule_AmountMode_name = map[int32]string{ + 0: "GLOBAL_TOTAL", + 1: "SHARE_EQUALLY", +} +var Rule_AmountMode_value = map[string]int32{ + "GLOBAL_TOTAL": 0, + "SHARE_EQUALLY": 1, +} + +func (x Rule_AmountMode) String() string { + return proto.EnumName(Rule_AmountMode_name, int32(x)) +} +func (Rule_AmountMode) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_ratelimit_2b495c7362132910, []int{1, 2} +} + +// 与限流集群连接失败时降级模式 +type Rule_FailoverType int32 + +const ( + Rule_FAILOVER_LOCAL Rule_FailoverType = 0 + Rule_FAILOVER_PASS Rule_FailoverType = 1 +) + +var Rule_FailoverType_name = map[int32]string{ + 0: "FAILOVER_LOCAL", + 1: "FAILOVER_PASS", +} +var Rule_FailoverType_value = map[string]int32{ + "FAILOVER_LOCAL": 0, + "FAILOVER_PASS": 1, +} + +func (x Rule_FailoverType) String() string { + return proto.EnumName(Rule_FailoverType_name, int32(x)) +} +func (Rule_FailoverType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_ratelimit_2b495c7362132910, []int{1, 3} +} + +// 同一服务下限流规则集合 +type RateLimit struct { + // 限流规则集合 + Rules []*Rule `protobuf:"bytes,1,rep,name=rules,proto3" json:"rules,omitempty"` + // 限流规则汇总的revision信息 + Revision *wrappers.StringValue `protobuf:"bytes,2,opt,name=revision,proto3" json:"revision,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *RateLimit) Reset() { *m = RateLimit{} } +func (m *RateLimit) String() string { return proto.CompactTextString(m) } +func (*RateLimit) ProtoMessage() {} +func (*RateLimit) Descriptor() ([]byte, []int) { + return fileDescriptor_ratelimit_2b495c7362132910, []int{0} +} +func (m *RateLimit) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_RateLimit.Unmarshal(m, b) +} +func (m *RateLimit) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_RateLimit.Marshal(b, m, deterministic) +} +func (dst *RateLimit) XXX_Merge(src proto.Message) { + xxx_messageInfo_RateLimit.Merge(dst, src) +} +func (m *RateLimit) XXX_Size() int { + return xxx_messageInfo_RateLimit.Size(m) +} +func (m *RateLimit) XXX_DiscardUnknown() { + xxx_messageInfo_RateLimit.DiscardUnknown(m) +} + +var xxx_messageInfo_RateLimit proto.InternalMessageInfo + +func (m *RateLimit) GetRules() []*Rule { + if m != nil { + return m.Rules + } + return nil +} + +func (m *RateLimit) GetRevision() *wrappers.StringValue { + if m != nil { + return m.Revision + } + return nil +} + +// 单个限流规则信息 +type Rule struct { + // 限流规则唯一标识 + Id *wrappers.StringValue `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // 限流规则所属服务名 + Service *wrappers.StringValue `protobuf:"bytes,2,opt,name=service,proto3" json:"service,omitempty"` + // 限流规则所属命名空间 + Namespace *wrappers.StringValue `protobuf:"bytes,3,opt,name=namespace,proto3" json:"namespace,omitempty"` + // 可选,SUBSET标识 + Subset map[string]*MatchString `protobuf:"bytes,4,rep,name=subset,proto3" json:"subset,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // 限流规则优先级,0值最高 + Priority *wrappers.UInt32Value `protobuf:"bytes,5,opt,name=priority,proto3" json:"priority,omitempty"` + Resource Rule_Resource `protobuf:"varint,6,opt,name=resource,proto3,enum=v1.Rule_Resource" json:"resource,omitempty"` + Type Rule_Type `protobuf:"varint,7,opt,name=type,proto3,enum=v1.Rule_Type" json:"type,omitempty"` + // 业务标签集合,通过KV进行匹配,全部匹配才使用该规则 + Labels map[string]*MatchString `protobuf:"bytes,8,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // 限流阈值 + // 可以有多个粒度的配置(比如同时针对秒级,分钟级,天级),匹配一个则进行限流 + // 全局限流模式下,该值为服务配额总量;单机限流模式下,该值为单个节点能处理的配额量 + Amounts []*Amount `protobuf:"bytes,9,rep,name=amounts,proto3" json:"amounts,omitempty"` + // 限流动作,对应着客户端的插件名字 + Action *wrappers.StringValue `protobuf:"bytes,10,opt,name=action,proto3" json:"action,omitempty"` + // 是否停用该限流规则,默认启用 + Disable *wrappers.BoolValue `protobuf:"bytes,11,opt,name=disable,proto3" json:"disable,omitempty"` + // 限流上报方式,同时支持按固定周期上报,以及达到配额百分比后上报 + Report *Report `protobuf:"bytes,12,opt,name=report,proto3" json:"report,omitempty"` + // 限流规则创建时间 + Ctime *wrappers.StringValue `protobuf:"bytes,13,opt,name=ctime,proto3" json:"ctime,omitempty"` + // 限流规则修改时间 + Mtime *wrappers.StringValue `protobuf:"bytes,14,opt,name=mtime,proto3" json:"mtime,omitempty"` + // 限流规则revision信息 + Revision *wrappers.StringValue `protobuf:"bytes,15,opt,name=revision,proto3" json:"revision,omitempty"` + // 服务的TOKEN信息,仅用于控制台,discover接口不下发 + ServiceToken *wrappers.StringValue `protobuf:"bytes,16,opt,name=service_token,proto3" json:"service_token,omitempty"` + // 配额调整算法 + Adjuster *AmountAdjuster `protobuf:"bytes,17,opt,name=adjuster,proto3" json:"adjuster,omitempty"` + // 通配符是否合并计算,默认分开计数 + RegexCombine *wrappers.BoolValue `protobuf:"bytes,18,opt,name=regex_combine,json=regexCombine,proto3" json:"regex_combine,omitempty"` + AmountMode Rule_AmountMode `protobuf:"varint,19,opt,name=amount_mode,json=amountMode,proto3,enum=v1.Rule_AmountMode" json:"amount_mode,omitempty"` + Failover Rule_FailoverType `protobuf:"varint,20,opt,name=failover,proto3,enum=v1.Rule_FailoverType" json:"failover,omitempty"` + // 分布式限流服务集群 + Cluster *RateLimitCluster `protobuf:"bytes,21,opt,name=cluster,proto3" json:"cluster,omitempty"` + // 被调接口名 + Method *MatchString `protobuf:"bytes,22,opt,name=method,proto3" json:"method,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Rule) Reset() { *m = Rule{} } +func (m *Rule) String() string { return proto.CompactTextString(m) } +func (*Rule) ProtoMessage() {} +func (*Rule) Descriptor() ([]byte, []int) { + return fileDescriptor_ratelimit_2b495c7362132910, []int{1} +} +func (m *Rule) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Rule.Unmarshal(m, b) +} +func (m *Rule) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Rule.Marshal(b, m, deterministic) +} +func (dst *Rule) XXX_Merge(src proto.Message) { + xxx_messageInfo_Rule.Merge(dst, src) +} +func (m *Rule) XXX_Size() int { + return xxx_messageInfo_Rule.Size(m) +} +func (m *Rule) XXX_DiscardUnknown() { + xxx_messageInfo_Rule.DiscardUnknown(m) +} + +var xxx_messageInfo_Rule proto.InternalMessageInfo + +func (m *Rule) GetId() *wrappers.StringValue { + if m != nil { + return m.Id + } + return nil +} + +func (m *Rule) GetService() *wrappers.StringValue { + if m != nil { + return m.Service + } + return nil +} + +func (m *Rule) GetNamespace() *wrappers.StringValue { + if m != nil { + return m.Namespace + } + return nil +} + +func (m *Rule) GetSubset() map[string]*MatchString { + if m != nil { + return m.Subset + } + return nil +} + +func (m *Rule) GetPriority() *wrappers.UInt32Value { + if m != nil { + return m.Priority + } + return nil +} + +func (m *Rule) GetResource() Rule_Resource { + if m != nil { + return m.Resource + } + return Rule_QPS +} + +func (m *Rule) GetType() Rule_Type { + if m != nil { + return m.Type + } + return Rule_GLOBAL +} + +func (m *Rule) GetLabels() map[string]*MatchString { + if m != nil { + return m.Labels + } + return nil +} + +func (m *Rule) GetAmounts() []*Amount { + if m != nil { + return m.Amounts + } + return nil +} + +func (m *Rule) GetAction() *wrappers.StringValue { + if m != nil { + return m.Action + } + return nil +} + +func (m *Rule) GetDisable() *wrappers.BoolValue { + if m != nil { + return m.Disable + } + return nil +} + +func (m *Rule) GetReport() *Report { + if m != nil { + return m.Report + } + return nil +} + +func (m *Rule) GetCtime() *wrappers.StringValue { + if m != nil { + return m.Ctime + } + return nil +} + +func (m *Rule) GetMtime() *wrappers.StringValue { + if m != nil { + return m.Mtime + } + return nil +} + +func (m *Rule) GetRevision() *wrappers.StringValue { + if m != nil { + return m.Revision + } + return nil +} + +func (m *Rule) GetServiceToken() *wrappers.StringValue { + if m != nil { + return m.ServiceToken + } + return nil +} + +func (m *Rule) GetAdjuster() *AmountAdjuster { + if m != nil { + return m.Adjuster + } + return nil +} + +func (m *Rule) GetRegexCombine() *wrappers.BoolValue { + if m != nil { + return m.RegexCombine + } + return nil +} + +func (m *Rule) GetAmountMode() Rule_AmountMode { + if m != nil { + return m.AmountMode + } + return Rule_GLOBAL_TOTAL +} + +func (m *Rule) GetFailover() Rule_FailoverType { + if m != nil { + return m.Failover + } + return Rule_FAILOVER_LOCAL +} + +func (m *Rule) GetCluster() *RateLimitCluster { + if m != nil { + return m.Cluster + } + return nil +} + +func (m *Rule) GetMethod() *MatchString { + if m != nil { + return m.Method + } + return nil +} + +// 分布式限流服务集群 +type RateLimitCluster struct { + Service *wrappers.StringValue `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` + // 限流规则所属命名空间 + Namespace *wrappers.StringValue `protobuf:"bytes,2,opt,name=namespace,proto3" json:"namespace,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *RateLimitCluster) Reset() { *m = RateLimitCluster{} } +func (m *RateLimitCluster) String() string { return proto.CompactTextString(m) } +func (*RateLimitCluster) ProtoMessage() {} +func (*RateLimitCluster) Descriptor() ([]byte, []int) { + return fileDescriptor_ratelimit_2b495c7362132910, []int{2} +} +func (m *RateLimitCluster) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_RateLimitCluster.Unmarshal(m, b) +} +func (m *RateLimitCluster) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_RateLimitCluster.Marshal(b, m, deterministic) +} +func (dst *RateLimitCluster) XXX_Merge(src proto.Message) { + xxx_messageInfo_RateLimitCluster.Merge(dst, src) +} +func (m *RateLimitCluster) XXX_Size() int { + return xxx_messageInfo_RateLimitCluster.Size(m) +} +func (m *RateLimitCluster) XXX_DiscardUnknown() { + xxx_messageInfo_RateLimitCluster.DiscardUnknown(m) +} + +var xxx_messageInfo_RateLimitCluster proto.InternalMessageInfo + +func (m *RateLimitCluster) GetService() *wrappers.StringValue { + if m != nil { + return m.Service + } + return nil +} + +func (m *RateLimitCluster) GetNamespace() *wrappers.StringValue { + if m != nil { + return m.Namespace + } + return nil +} + +// 限流配额 +type Amount struct { + // 时间周期内的最大配额数 + MaxAmount *wrappers.UInt32Value `protobuf:"bytes,1,opt,name=maxAmount,proto3" json:"maxAmount,omitempty"` + // 配额生效的时间周期,必须大于等于1s + ValidDuration *duration.Duration `protobuf:"bytes,2,opt,name=validDuration,proto3" json:"validDuration,omitempty"` + // 请求统计精度 + Precision *wrappers.UInt32Value `protobuf:"bytes,3,opt,name=precision,proto3" json:"precision,omitempty"` + // 可选,起始限流阈值,爬坡起始值 + StartAmount *wrappers.UInt32Value `protobuf:"bytes,4,opt,name=startAmount,proto3" json:"startAmount,omitempty"` + // 可选,最小限流阈值,降低时最小值 + MinAmount *wrappers.UInt32Value `protobuf:"bytes,5,opt,name=minAmount,proto3" json:"minAmount,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Amount) Reset() { *m = Amount{} } +func (m *Amount) String() string { return proto.CompactTextString(m) } +func (*Amount) ProtoMessage() {} +func (*Amount) Descriptor() ([]byte, []int) { + return fileDescriptor_ratelimit_2b495c7362132910, []int{3} +} +func (m *Amount) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Amount.Unmarshal(m, b) +} +func (m *Amount) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Amount.Marshal(b, m, deterministic) +} +func (dst *Amount) XXX_Merge(src proto.Message) { + xxx_messageInfo_Amount.Merge(dst, src) +} +func (m *Amount) XXX_Size() int { + return xxx_messageInfo_Amount.Size(m) +} +func (m *Amount) XXX_DiscardUnknown() { + xxx_messageInfo_Amount.DiscardUnknown(m) +} + +var xxx_messageInfo_Amount proto.InternalMessageInfo + +func (m *Amount) GetMaxAmount() *wrappers.UInt32Value { + if m != nil { + return m.MaxAmount + } + return nil +} + +func (m *Amount) GetValidDuration() *duration.Duration { + if m != nil { + return m.ValidDuration + } + return nil +} + +func (m *Amount) GetPrecision() *wrappers.UInt32Value { + if m != nil { + return m.Precision + } + return nil +} + +func (m *Amount) GetStartAmount() *wrappers.UInt32Value { + if m != nil { + return m.StartAmount + } + return nil +} + +func (m *Amount) GetMinAmount() *wrappers.UInt32Value { + if m != nil { + return m.MinAmount + } + return nil +} + +// 限流上报方式 +type Report struct { + // 配额固定上报周期,单位毫秒 + Interval *duration.Duration `protobuf:"bytes,1,opt,name=interval,proto3" json:"interval,omitempty"` + // 使用了百分之多少配额后启动一次实时上报,值范围(0,100] + AmountPercent *wrappers.UInt32Value `protobuf:"bytes,2,opt,name=amountPercent,proto3" json:"amountPercent,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Report) Reset() { *m = Report{} } +func (m *Report) String() string { return proto.CompactTextString(m) } +func (*Report) ProtoMessage() {} +func (*Report) Descriptor() ([]byte, []int) { + return fileDescriptor_ratelimit_2b495c7362132910, []int{4} +} +func (m *Report) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Report.Unmarshal(m, b) +} +func (m *Report) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Report.Marshal(b, m, deterministic) +} +func (dst *Report) XXX_Merge(src proto.Message) { + xxx_messageInfo_Report.Merge(dst, src) +} +func (m *Report) XXX_Size() int { + return xxx_messageInfo_Report.Size(m) +} +func (m *Report) XXX_DiscardUnknown() { + xxx_messageInfo_Report.DiscardUnknown(m) +} + +var xxx_messageInfo_Report proto.InternalMessageInfo + +func (m *Report) GetInterval() *duration.Duration { + if m != nil { + return m.Interval + } + return nil +} + +func (m *Report) GetAmountPercent() *wrappers.UInt32Value { + if m != nil { + return m.AmountPercent + } + return nil +} + +// 配额调整算法 +type AmountAdjuster struct { + Climb *ClimbConfig `protobuf:"bytes,1,opt,name=climb,proto3" json:"climb,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *AmountAdjuster) Reset() { *m = AmountAdjuster{} } +func (m *AmountAdjuster) String() string { return proto.CompactTextString(m) } +func (*AmountAdjuster) ProtoMessage() {} +func (*AmountAdjuster) Descriptor() ([]byte, []int) { + return fileDescriptor_ratelimit_2b495c7362132910, []int{5} +} +func (m *AmountAdjuster) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_AmountAdjuster.Unmarshal(m, b) +} +func (m *AmountAdjuster) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_AmountAdjuster.Marshal(b, m, deterministic) +} +func (dst *AmountAdjuster) XXX_Merge(src proto.Message) { + xxx_messageInfo_AmountAdjuster.Merge(dst, src) +} +func (m *AmountAdjuster) XXX_Size() int { + return xxx_messageInfo_AmountAdjuster.Size(m) +} +func (m *AmountAdjuster) XXX_DiscardUnknown() { + xxx_messageInfo_AmountAdjuster.DiscardUnknown(m) +} + +var xxx_messageInfo_AmountAdjuster proto.InternalMessageInfo + +func (m *AmountAdjuster) GetClimb() *ClimbConfig { + if m != nil { + return m.Climb + } + return nil +} + +// 限流调整算法Climb相关配置 +type ClimbConfig struct { + Enable *wrappers.BoolValue `protobuf:"bytes,1,opt,name=enable,proto3" json:"enable,omitempty"` + Metric *ClimbConfig_MetricConfig `protobuf:"bytes,2,opt,name=metric,proto3" json:"metric,omitempty"` + Policy *ClimbConfig_TriggerPolicy `protobuf:"bytes,3,opt,name=policy,proto3" json:"policy,omitempty"` + Throttling *ClimbConfig_ClimbThrottling `protobuf:"bytes,4,opt,name=throttling,proto3" json:"throttling,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ClimbConfig) Reset() { *m = ClimbConfig{} } +func (m *ClimbConfig) String() string { return proto.CompactTextString(m) } +func (*ClimbConfig) ProtoMessage() {} +func (*ClimbConfig) Descriptor() ([]byte, []int) { + return fileDescriptor_ratelimit_2b495c7362132910, []int{6} +} +func (m *ClimbConfig) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ClimbConfig.Unmarshal(m, b) +} +func (m *ClimbConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ClimbConfig.Marshal(b, m, deterministic) +} +func (dst *ClimbConfig) XXX_Merge(src proto.Message) { + xxx_messageInfo_ClimbConfig.Merge(dst, src) +} +func (m *ClimbConfig) XXX_Size() int { + return xxx_messageInfo_ClimbConfig.Size(m) +} +func (m *ClimbConfig) XXX_DiscardUnknown() { + xxx_messageInfo_ClimbConfig.DiscardUnknown(m) +} + +var xxx_messageInfo_ClimbConfig proto.InternalMessageInfo + +func (m *ClimbConfig) GetEnable() *wrappers.BoolValue { + if m != nil { + return m.Enable + } + return nil +} + +func (m *ClimbConfig) GetMetric() *ClimbConfig_MetricConfig { + if m != nil { + return m.Metric + } + return nil +} + +func (m *ClimbConfig) GetPolicy() *ClimbConfig_TriggerPolicy { + if m != nil { + return m.Policy + } + return nil +} + +func (m *ClimbConfig) GetThrottling() *ClimbConfig_ClimbThrottling { + if m != nil { + return m.Throttling + } + return nil +} + +// 限流数据统计配置 +type ClimbConfig_MetricConfig struct { + // 限流数据度量周期,默认60s + Window *duration.Duration `protobuf:"bytes,1,opt,name=window,proto3" json:"window,omitempty"` + // 数据统计精度,决定数据度量的最小周期,度量滑窗的步长=window/precision + Precision *wrappers.UInt32Value `protobuf:"bytes,2,opt,name=precision,proto3" json:"precision,omitempty"` + // 上报周期,默认20s + ReportInterval *duration.Duration `protobuf:"bytes,3,opt,name=reportInterval,proto3" json:"reportInterval,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ClimbConfig_MetricConfig) Reset() { *m = ClimbConfig_MetricConfig{} } +func (m *ClimbConfig_MetricConfig) String() string { return proto.CompactTextString(m) } +func (*ClimbConfig_MetricConfig) ProtoMessage() {} +func (*ClimbConfig_MetricConfig) Descriptor() ([]byte, []int) { + return fileDescriptor_ratelimit_2b495c7362132910, []int{6, 0} +} +func (m *ClimbConfig_MetricConfig) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ClimbConfig_MetricConfig.Unmarshal(m, b) +} +func (m *ClimbConfig_MetricConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ClimbConfig_MetricConfig.Marshal(b, m, deterministic) +} +func (dst *ClimbConfig_MetricConfig) XXX_Merge(src proto.Message) { + xxx_messageInfo_ClimbConfig_MetricConfig.Merge(dst, src) +} +func (m *ClimbConfig_MetricConfig) XXX_Size() int { + return xxx_messageInfo_ClimbConfig_MetricConfig.Size(m) +} +func (m *ClimbConfig_MetricConfig) XXX_DiscardUnknown() { + xxx_messageInfo_ClimbConfig_MetricConfig.DiscardUnknown(m) +} + +var xxx_messageInfo_ClimbConfig_MetricConfig proto.InternalMessageInfo + +func (m *ClimbConfig_MetricConfig) GetWindow() *duration.Duration { + if m != nil { + return m.Window + } + return nil +} + +func (m *ClimbConfig_MetricConfig) GetPrecision() *wrappers.UInt32Value { + if m != nil { + return m.Precision + } + return nil +} + +func (m *ClimbConfig_MetricConfig) GetReportInterval() *duration.Duration { + if m != nil { + return m.ReportInterval + } + return nil +} + +// 触发调整的策略 +type ClimbConfig_TriggerPolicy struct { + ErrorRate *ClimbConfig_TriggerPolicy_ErrorRate `protobuf:"bytes,1,opt,name=errorRate,proto3" json:"errorRate,omitempty"` + SlowRate *ClimbConfig_TriggerPolicy_SlowRate `protobuf:"bytes,2,opt,name=slowRate,proto3" json:"slowRate,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ClimbConfig_TriggerPolicy) Reset() { *m = ClimbConfig_TriggerPolicy{} } +func (m *ClimbConfig_TriggerPolicy) String() string { return proto.CompactTextString(m) } +func (*ClimbConfig_TriggerPolicy) ProtoMessage() {} +func (*ClimbConfig_TriggerPolicy) Descriptor() ([]byte, []int) { + return fileDescriptor_ratelimit_2b495c7362132910, []int{6, 1} +} +func (m *ClimbConfig_TriggerPolicy) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ClimbConfig_TriggerPolicy.Unmarshal(m, b) +} +func (m *ClimbConfig_TriggerPolicy) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ClimbConfig_TriggerPolicy.Marshal(b, m, deterministic) +} +func (dst *ClimbConfig_TriggerPolicy) XXX_Merge(src proto.Message) { + xxx_messageInfo_ClimbConfig_TriggerPolicy.Merge(dst, src) +} +func (m *ClimbConfig_TriggerPolicy) XXX_Size() int { + return xxx_messageInfo_ClimbConfig_TriggerPolicy.Size(m) +} +func (m *ClimbConfig_TriggerPolicy) XXX_DiscardUnknown() { + xxx_messageInfo_ClimbConfig_TriggerPolicy.DiscardUnknown(m) +} + +var xxx_messageInfo_ClimbConfig_TriggerPolicy proto.InternalMessageInfo + +func (m *ClimbConfig_TriggerPolicy) GetErrorRate() *ClimbConfig_TriggerPolicy_ErrorRate { + if m != nil { + return m.ErrorRate + } + return nil +} + +func (m *ClimbConfig_TriggerPolicy) GetSlowRate() *ClimbConfig_TriggerPolicy_SlowRate { + if m != nil { + return m.SlowRate + } + return nil +} + +// 错误率触发调整配置 +type ClimbConfig_TriggerPolicy_ErrorRate struct { + Enable *wrappers.BoolValue `protobuf:"bytes,1,opt,name=enable,proto3" json:"enable,omitempty"` + RequestVolumeThreshold *wrappers.UInt32Value `protobuf:"bytes,2,opt,name=requestVolumeThreshold,proto3" json:"requestVolumeThreshold,omitempty"` + ErrorRate *wrappers.Int32Value `protobuf:"bytes,3,opt,name=errorRate,proto3" json:"errorRate,omitempty"` + Specials []*ClimbConfig_TriggerPolicy_ErrorRate_SpecialConfig `protobuf:"bytes,4,rep,name=specials,proto3" json:"specials,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ClimbConfig_TriggerPolicy_ErrorRate) Reset() { *m = ClimbConfig_TriggerPolicy_ErrorRate{} } +func (m *ClimbConfig_TriggerPolicy_ErrorRate) String() string { return proto.CompactTextString(m) } +func (*ClimbConfig_TriggerPolicy_ErrorRate) ProtoMessage() {} +func (*ClimbConfig_TriggerPolicy_ErrorRate) Descriptor() ([]byte, []int) { + return fileDescriptor_ratelimit_2b495c7362132910, []int{6, 1, 0} +} +func (m *ClimbConfig_TriggerPolicy_ErrorRate) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ClimbConfig_TriggerPolicy_ErrorRate.Unmarshal(m, b) +} +func (m *ClimbConfig_TriggerPolicy_ErrorRate) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ClimbConfig_TriggerPolicy_ErrorRate.Marshal(b, m, deterministic) +} +func (dst *ClimbConfig_TriggerPolicy_ErrorRate) XXX_Merge(src proto.Message) { + xxx_messageInfo_ClimbConfig_TriggerPolicy_ErrorRate.Merge(dst, src) +} +func (m *ClimbConfig_TriggerPolicy_ErrorRate) XXX_Size() int { + return xxx_messageInfo_ClimbConfig_TriggerPolicy_ErrorRate.Size(m) +} +func (m *ClimbConfig_TriggerPolicy_ErrorRate) XXX_DiscardUnknown() { + xxx_messageInfo_ClimbConfig_TriggerPolicy_ErrorRate.DiscardUnknown(m) +} + +var xxx_messageInfo_ClimbConfig_TriggerPolicy_ErrorRate proto.InternalMessageInfo + +func (m *ClimbConfig_TriggerPolicy_ErrorRate) GetEnable() *wrappers.BoolValue { + if m != nil { + return m.Enable + } + return nil +} + +func (m *ClimbConfig_TriggerPolicy_ErrorRate) GetRequestVolumeThreshold() *wrappers.UInt32Value { + if m != nil { + return m.RequestVolumeThreshold + } + return nil +} + +func (m *ClimbConfig_TriggerPolicy_ErrorRate) GetErrorRate() *wrappers.Int32Value { + if m != nil { + return m.ErrorRate + } + return nil +} + +func (m *ClimbConfig_TriggerPolicy_ErrorRate) GetSpecials() []*ClimbConfig_TriggerPolicy_ErrorRate_SpecialConfig { + if m != nil { + return m.Specials + } + return nil +} + +// 特殊错误码触发调整配置 +type ClimbConfig_TriggerPolicy_ErrorRate_SpecialConfig struct { + Type *wrappers.StringValue `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + ErrorCodes []*wrappers.Int64Value `protobuf:"bytes,2,rep,name=errorCodes,proto3" json:"errorCodes,omitempty"` + ErrorRate *wrappers.Int32Value `protobuf:"bytes,3,opt,name=errorRate,proto3" json:"errorRate,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ClimbConfig_TriggerPolicy_ErrorRate_SpecialConfig) Reset() { + *m = ClimbConfig_TriggerPolicy_ErrorRate_SpecialConfig{} +} +func (m *ClimbConfig_TriggerPolicy_ErrorRate_SpecialConfig) String() string { + return proto.CompactTextString(m) +} +func (*ClimbConfig_TriggerPolicy_ErrorRate_SpecialConfig) ProtoMessage() {} +func (*ClimbConfig_TriggerPolicy_ErrorRate_SpecialConfig) Descriptor() ([]byte, []int) { + return fileDescriptor_ratelimit_2b495c7362132910, []int{6, 1, 0, 0} +} +func (m *ClimbConfig_TriggerPolicy_ErrorRate_SpecialConfig) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ClimbConfig_TriggerPolicy_ErrorRate_SpecialConfig.Unmarshal(m, b) +} +func (m *ClimbConfig_TriggerPolicy_ErrorRate_SpecialConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ClimbConfig_TriggerPolicy_ErrorRate_SpecialConfig.Marshal(b, m, deterministic) +} +func (dst *ClimbConfig_TriggerPolicy_ErrorRate_SpecialConfig) XXX_Merge(src proto.Message) { + xxx_messageInfo_ClimbConfig_TriggerPolicy_ErrorRate_SpecialConfig.Merge(dst, src) +} +func (m *ClimbConfig_TriggerPolicy_ErrorRate_SpecialConfig) XXX_Size() int { + return xxx_messageInfo_ClimbConfig_TriggerPolicy_ErrorRate_SpecialConfig.Size(m) +} +func (m *ClimbConfig_TriggerPolicy_ErrorRate_SpecialConfig) XXX_DiscardUnknown() { + xxx_messageInfo_ClimbConfig_TriggerPolicy_ErrorRate_SpecialConfig.DiscardUnknown(m) +} + +var xxx_messageInfo_ClimbConfig_TriggerPolicy_ErrorRate_SpecialConfig proto.InternalMessageInfo + +func (m *ClimbConfig_TriggerPolicy_ErrorRate_SpecialConfig) GetType() *wrappers.StringValue { + if m != nil { + return m.Type + } + return nil +} + +func (m *ClimbConfig_TriggerPolicy_ErrorRate_SpecialConfig) GetErrorCodes() []*wrappers.Int64Value { + if m != nil { + return m.ErrorCodes + } + return nil +} + +func (m *ClimbConfig_TriggerPolicy_ErrorRate_SpecialConfig) GetErrorRate() *wrappers.Int32Value { + if m != nil { + return m.ErrorRate + } + return nil +} + +// 慢调用触发调整配置 +type ClimbConfig_TriggerPolicy_SlowRate struct { + Enable *wrappers.BoolValue `protobuf:"bytes,1,opt,name=enable,proto3" json:"enable,omitempty"` + MaxRt *duration.Duration `protobuf:"bytes,2,opt,name=maxRt,proto3" json:"maxRt,omitempty"` + SlowRate *wrappers.Int32Value `protobuf:"bytes,3,opt,name=slowRate,proto3" json:"slowRate,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ClimbConfig_TriggerPolicy_SlowRate) Reset() { *m = ClimbConfig_TriggerPolicy_SlowRate{} } +func (m *ClimbConfig_TriggerPolicy_SlowRate) String() string { return proto.CompactTextString(m) } +func (*ClimbConfig_TriggerPolicy_SlowRate) ProtoMessage() {} +func (*ClimbConfig_TriggerPolicy_SlowRate) Descriptor() ([]byte, []int) { + return fileDescriptor_ratelimit_2b495c7362132910, []int{6, 1, 1} +} +func (m *ClimbConfig_TriggerPolicy_SlowRate) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ClimbConfig_TriggerPolicy_SlowRate.Unmarshal(m, b) +} +func (m *ClimbConfig_TriggerPolicy_SlowRate) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ClimbConfig_TriggerPolicy_SlowRate.Marshal(b, m, deterministic) +} +func (dst *ClimbConfig_TriggerPolicy_SlowRate) XXX_Merge(src proto.Message) { + xxx_messageInfo_ClimbConfig_TriggerPolicy_SlowRate.Merge(dst, src) +} +func (m *ClimbConfig_TriggerPolicy_SlowRate) XXX_Size() int { + return xxx_messageInfo_ClimbConfig_TriggerPolicy_SlowRate.Size(m) +} +func (m *ClimbConfig_TriggerPolicy_SlowRate) XXX_DiscardUnknown() { + xxx_messageInfo_ClimbConfig_TriggerPolicy_SlowRate.DiscardUnknown(m) +} + +var xxx_messageInfo_ClimbConfig_TriggerPolicy_SlowRate proto.InternalMessageInfo + +func (m *ClimbConfig_TriggerPolicy_SlowRate) GetEnable() *wrappers.BoolValue { + if m != nil { + return m.Enable + } + return nil +} + +func (m *ClimbConfig_TriggerPolicy_SlowRate) GetMaxRt() *duration.Duration { + if m != nil { + return m.MaxRt + } + return nil +} + +func (m *ClimbConfig_TriggerPolicy_SlowRate) GetSlowRate() *wrappers.Int32Value { + if m != nil { + return m.SlowRate + } + return nil +} + +// 爬坡调整相关参数 +type ClimbConfig_ClimbThrottling struct { + ColdBelowTuneDownRate *wrappers.Int32Value `protobuf:"bytes,1,opt,name=coldBelowTuneDownRate,proto3" json:"coldBelowTuneDownRate,omitempty"` + ColdBelowTuneUpRate *wrappers.Int32Value `protobuf:"bytes,2,opt,name=coldBelowTuneUpRate,proto3" json:"coldBelowTuneUpRate,omitempty"` + ColdAboveTuneDownRate *wrappers.Int32Value `protobuf:"bytes,3,opt,name=coldAboveTuneDownRate,proto3" json:"coldAboveTuneDownRate,omitempty"` + ColdAboveTuneUpRate *wrappers.Int32Value `protobuf:"bytes,4,opt,name=coldAboveTuneUpRate,proto3" json:"coldAboveTuneUpRate,omitempty"` + LimitThresholdToTuneUp *wrappers.Int32Value `protobuf:"bytes,5,opt,name=limitThresholdToTuneUp,proto3" json:"limitThresholdToTuneUp,omitempty"` + JudgeDuration *duration.Duration `protobuf:"bytes,6,opt,name=judgeDuration,proto3" json:"judgeDuration,omitempty"` + TuneUpPeriod *wrappers.Int32Value `protobuf:"bytes,7,opt,name=tuneUpPeriod,proto3" json:"tuneUpPeriod,omitempty"` + TuneDownPeriod *wrappers.Int32Value `protobuf:"bytes,8,opt,name=tuneDownPeriod,proto3" json:"tuneDownPeriod,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ClimbConfig_ClimbThrottling) Reset() { *m = ClimbConfig_ClimbThrottling{} } +func (m *ClimbConfig_ClimbThrottling) String() string { return proto.CompactTextString(m) } +func (*ClimbConfig_ClimbThrottling) ProtoMessage() {} +func (*ClimbConfig_ClimbThrottling) Descriptor() ([]byte, []int) { + return fileDescriptor_ratelimit_2b495c7362132910, []int{6, 2} +} +func (m *ClimbConfig_ClimbThrottling) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ClimbConfig_ClimbThrottling.Unmarshal(m, b) +} +func (m *ClimbConfig_ClimbThrottling) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ClimbConfig_ClimbThrottling.Marshal(b, m, deterministic) +} +func (dst *ClimbConfig_ClimbThrottling) XXX_Merge(src proto.Message) { + xxx_messageInfo_ClimbConfig_ClimbThrottling.Merge(dst, src) +} +func (m *ClimbConfig_ClimbThrottling) XXX_Size() int { + return xxx_messageInfo_ClimbConfig_ClimbThrottling.Size(m) +} +func (m *ClimbConfig_ClimbThrottling) XXX_DiscardUnknown() { + xxx_messageInfo_ClimbConfig_ClimbThrottling.DiscardUnknown(m) +} + +var xxx_messageInfo_ClimbConfig_ClimbThrottling proto.InternalMessageInfo + +func (m *ClimbConfig_ClimbThrottling) GetColdBelowTuneDownRate() *wrappers.Int32Value { + if m != nil { + return m.ColdBelowTuneDownRate + } + return nil +} + +func (m *ClimbConfig_ClimbThrottling) GetColdBelowTuneUpRate() *wrappers.Int32Value { + if m != nil { + return m.ColdBelowTuneUpRate + } + return nil +} + +func (m *ClimbConfig_ClimbThrottling) GetColdAboveTuneDownRate() *wrappers.Int32Value { + if m != nil { + return m.ColdAboveTuneDownRate + } + return nil +} + +func (m *ClimbConfig_ClimbThrottling) GetColdAboveTuneUpRate() *wrappers.Int32Value { + if m != nil { + return m.ColdAboveTuneUpRate + } + return nil +} + +func (m *ClimbConfig_ClimbThrottling) GetLimitThresholdToTuneUp() *wrappers.Int32Value { + if m != nil { + return m.LimitThresholdToTuneUp + } + return nil +} + +func (m *ClimbConfig_ClimbThrottling) GetJudgeDuration() *duration.Duration { + if m != nil { + return m.JudgeDuration + } + return nil +} + +func (m *ClimbConfig_ClimbThrottling) GetTuneUpPeriod() *wrappers.Int32Value { + if m != nil { + return m.TuneUpPeriod + } + return nil +} + +func (m *ClimbConfig_ClimbThrottling) GetTuneDownPeriod() *wrappers.Int32Value { + if m != nil { + return m.TuneDownPeriod + } + return nil +} + +func init() { + proto.RegisterType((*RateLimit)(nil), "v1.RateLimit") + proto.RegisterType((*Rule)(nil), "v1.Rule") + proto.RegisterMapType((map[string]*MatchString)(nil), "v1.Rule.LabelsEntry") + proto.RegisterMapType((map[string]*MatchString)(nil), "v1.Rule.SubsetEntry") + proto.RegisterType((*RateLimitCluster)(nil), "v1.RateLimitCluster") + proto.RegisterType((*Amount)(nil), "v1.Amount") + proto.RegisterType((*Report)(nil), "v1.Report") + proto.RegisterType((*AmountAdjuster)(nil), "v1.AmountAdjuster") + proto.RegisterType((*ClimbConfig)(nil), "v1.ClimbConfig") + proto.RegisterType((*ClimbConfig_MetricConfig)(nil), "v1.ClimbConfig.MetricConfig") + proto.RegisterType((*ClimbConfig_TriggerPolicy)(nil), "v1.ClimbConfig.TriggerPolicy") + proto.RegisterType((*ClimbConfig_TriggerPolicy_ErrorRate)(nil), "v1.ClimbConfig.TriggerPolicy.ErrorRate") + proto.RegisterType((*ClimbConfig_TriggerPolicy_ErrorRate_SpecialConfig)(nil), "v1.ClimbConfig.TriggerPolicy.ErrorRate.SpecialConfig") + proto.RegisterType((*ClimbConfig_TriggerPolicy_SlowRate)(nil), "v1.ClimbConfig.TriggerPolicy.SlowRate") + proto.RegisterType((*ClimbConfig_ClimbThrottling)(nil), "v1.ClimbConfig.ClimbThrottling") + proto.RegisterEnum("v1.Rule_Resource", Rule_Resource_name, Rule_Resource_value) + proto.RegisterEnum("v1.Rule_Type", Rule_Type_name, Rule_Type_value) + proto.RegisterEnum("v1.Rule_AmountMode", Rule_AmountMode_name, Rule_AmountMode_value) + proto.RegisterEnum("v1.Rule_FailoverType", Rule_FailoverType_name, Rule_FailoverType_value) +} + +func init() { proto.RegisterFile("ratelimit.proto", fileDescriptor_ratelimit_2b495c7362132910) } + +var fileDescriptor_ratelimit_2b495c7362132910 = []byte{ + // 1341 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x57, 0xdd, 0x72, 0xdb, 0x36, + 0x13, 0x0d, 0xf5, 0x43, 0x4b, 0x2b, 0x4b, 0x56, 0x90, 0x9f, 0xe1, 0xa7, 0xaf, 0x49, 0x53, 0x4d, + 0xda, 0xe4, 0x22, 0x55, 0x6a, 0xc7, 0x4e, 0xd2, 0x74, 0xa6, 0x19, 0x59, 0x51, 0xda, 0x74, 0xe4, + 0xd8, 0x86, 0xe4, 0xcc, 0xf4, 0xca, 0x43, 0x91, 0x88, 0x8c, 0x84, 0x22, 0x54, 0x10, 0x92, 0xe3, + 0xeb, 0xce, 0xf4, 0x51, 0xda, 0xbe, 0x42, 0x2f, 0x3a, 0xd3, 0x47, 0xe8, 0x1b, 0xf4, 0x19, 0xfa, + 0x06, 0x1d, 0x02, 0x20, 0x45, 0xca, 0xb1, 0xc5, 0x38, 0x77, 0x14, 0xf6, 0x9c, 0xdd, 0x83, 0xdd, + 0xc5, 0x02, 0x82, 0x35, 0x6e, 0x0b, 0xe2, 0xd1, 0x31, 0x15, 0xad, 0x09, 0x67, 0x82, 0xa1, 0xdc, + 0x6c, 0xbd, 0x71, 0x73, 0xc4, 0xd8, 0xc8, 0x23, 0xf7, 0xe5, 0xca, 0x70, 0xfa, 0xfa, 0xfe, 0x31, + 0xb7, 0x27, 0x13, 0xc2, 0x03, 0x85, 0x39, 0x6d, 0x77, 0xa7, 0xdc, 0x16, 0x94, 0xf9, 0xda, 0x5e, + 0x19, 0x33, 0x97, 0x78, 0xea, 0x47, 0x93, 0x40, 0x19, 0xdb, 0x82, 0xf4, 0xc2, 0x18, 0xe8, 0x26, + 0x14, 0xf9, 0xd4, 0x23, 0x81, 0x65, 0xdc, 0xca, 0xdf, 0xad, 0x6c, 0x94, 0x5a, 0xb3, 0xf5, 0x16, + 0x9e, 0x7a, 0x04, 0xab, 0x65, 0xf4, 0x18, 0x4a, 0x9c, 0xcc, 0x68, 0x40, 0x99, 0x6f, 0xe5, 0x6e, + 0x19, 0x77, 0x2b, 0x1b, 0x9f, 0xb4, 0x54, 0xb0, 0x56, 0x14, 0xac, 0xd5, 0x17, 0x9c, 0xfa, 0xa3, + 0x57, 0xb6, 0x37, 0x25, 0x38, 0x46, 0x37, 0x7f, 0xad, 0x40, 0x21, 0xf4, 0x84, 0xee, 0x41, 0x8e, + 0xba, 0x96, 0x91, 0x81, 0x9c, 0xa3, 0x2e, 0x7a, 0x08, 0x2b, 0x01, 0xe1, 0x33, 0xea, 0x90, 0x4c, + 0xf1, 0x22, 0x30, 0x7a, 0x02, 0x65, 0xdf, 0x1e, 0x93, 0x60, 0x62, 0x3b, 0xc4, 0xca, 0x67, 0x60, + 0xce, 0xe1, 0xe8, 0x1e, 0x98, 0xc1, 0x74, 0x18, 0x10, 0x61, 0x15, 0x64, 0x16, 0xae, 0x46, 0x59, + 0x68, 0xf5, 0xe5, 0x72, 0xd7, 0x17, 0xfc, 0x04, 0x6b, 0x4c, 0x98, 0x92, 0x09, 0xa7, 0x8c, 0x53, + 0x71, 0x62, 0x15, 0xcf, 0x08, 0x74, 0xf0, 0xc2, 0x17, 0x0f, 0x36, 0x74, 0x4a, 0x22, 0x34, 0xfa, + 0x32, 0x4c, 0x66, 0xc0, 0xa6, 0xdc, 0x21, 0x96, 0x79, 0xcb, 0xb8, 0x5b, 0xdb, 0xb8, 0x1c, 0x47, + 0xc2, 0xda, 0x80, 0x63, 0x08, 0xfa, 0x0c, 0x0a, 0xe2, 0x64, 0x42, 0xac, 0x15, 0x09, 0xad, 0xc6, + 0xd0, 0xc1, 0xc9, 0x84, 0x60, 0x69, 0x0a, 0x95, 0x7b, 0xf6, 0x90, 0x78, 0x81, 0x55, 0x5a, 0x50, + 0xde, 0x93, 0xcb, 0x5a, 0xb9, 0xc2, 0xa0, 0xdb, 0xb0, 0x62, 0x8f, 0xd9, 0xd4, 0x17, 0x81, 0x55, + 0x96, 0x70, 0x08, 0xe1, 0x6d, 0xb9, 0x84, 0x23, 0x13, 0xda, 0x04, 0xd3, 0x76, 0xc2, 0xe6, 0xb1, + 0x20, 0x43, 0x1a, 0x35, 0x16, 0x6d, 0xc2, 0x8a, 0x4b, 0x03, 0x7b, 0xe8, 0x11, 0xab, 0x22, 0x69, + 0x8d, 0x53, 0xb4, 0x6d, 0xc6, 0x3c, 0x5d, 0x35, 0x0d, 0x45, 0x4d, 0x30, 0x39, 0x99, 0x30, 0x2e, + 0xac, 0x55, 0x49, 0x92, 0x82, 0xb0, 0x5c, 0xc1, 0xda, 0x82, 0x36, 0xa0, 0xe8, 0x08, 0x3a, 0x26, + 0x56, 0x35, 0x83, 0x1c, 0x05, 0x0d, 0x39, 0x63, 0xc9, 0xa9, 0x65, 0xe1, 0x48, 0x68, 0xaa, 0xd5, + 0xd7, 0x3e, 0xa4, 0xd5, 0xd1, 0x36, 0x54, 0x75, 0x1b, 0x1e, 0x0a, 0xf6, 0x96, 0xf8, 0x56, 0x3d, + 0x03, 0x3d, 0x4d, 0x41, 0x2d, 0x28, 0xd9, 0xee, 0x9b, 0x69, 0x20, 0x08, 0xb7, 0x2e, 0x4b, 0x3a, + 0x9a, 0x17, 0xa7, 0xad, 0x2d, 0x38, 0xc6, 0xa0, 0xa7, 0x50, 0xe5, 0x64, 0x44, 0xde, 0x1d, 0x3a, + 0x6c, 0x3c, 0xa4, 0x3e, 0xb1, 0xd0, 0xd2, 0xac, 0xaf, 0x4a, 0x42, 0x47, 0xe1, 0xd1, 0x26, 0x54, + 0x54, 0xc5, 0x0f, 0xc3, 0xe1, 0x60, 0x5d, 0x91, 0x4d, 0x76, 0x25, 0xee, 0x1f, 0x15, 0x78, 0x87, + 0xb9, 0x04, 0x83, 0x1d, 0x7f, 0xa3, 0x75, 0x28, 0xbd, 0xb6, 0xa9, 0xc7, 0x66, 0x84, 0x5b, 0x57, + 0x25, 0xe5, 0x5a, 0x4c, 0x79, 0xae, 0x0d, 0xb2, 0x3f, 0x63, 0x18, 0x6a, 0xc1, 0x8a, 0xe3, 0xa9, + 0x8d, 0x5d, 0x93, 0x1a, 0x55, 0x93, 0x46, 0x23, 0xa8, 0xa3, 0x6c, 0x38, 0x02, 0xa1, 0x3b, 0x60, + 0x8e, 0x89, 0x38, 0x62, 0xae, 0x75, 0x5d, 0xc2, 0xd7, 0x42, 0xf8, 0x8e, 0x2d, 0x9c, 0x23, 0x95, + 0x3e, 0xac, 0xcd, 0x8d, 0x1f, 0xa0, 0x92, 0x38, 0x9f, 0xa8, 0x0e, 0xf9, 0xb7, 0xe4, 0x44, 0x0e, + 0x9a, 0x32, 0x0e, 0x3f, 0xd1, 0xe7, 0x50, 0x9c, 0x85, 0x3b, 0xd7, 0x93, 0xe4, 0x94, 0x23, 0x65, + 0x7d, 0x92, 0x7b, 0x6c, 0x84, 0xbe, 0x12, 0x27, 0xe6, 0xa3, 0x7c, 0x35, 0x6f, 0x43, 0x29, 0x3a, + 0xcd, 0x68, 0x05, 0xf2, 0xfb, 0x7b, 0xfd, 0xfa, 0x25, 0xb4, 0x06, 0x95, 0xce, 0xee, 0xcb, 0xce, + 0x01, 0xc6, 0xdd, 0x97, 0x9d, 0x1f, 0xeb, 0x46, 0xf3, 0x06, 0x14, 0xc2, 0x44, 0x21, 0x00, 0xf3, + 0xbb, 0xde, 0xee, 0x76, 0xbb, 0x57, 0xbf, 0x84, 0xca, 0x50, 0xec, 0xed, 0x76, 0xda, 0xbd, 0xba, + 0xd1, 0x5c, 0x07, 0x98, 0x97, 0x00, 0xd5, 0x61, 0x55, 0x81, 0x0e, 0x07, 0xbb, 0x03, 0x09, 0xbd, + 0x0c, 0xd5, 0xfe, 0xf7, 0x6d, 0xdc, 0x3d, 0xec, 0xee, 0x1f, 0xb4, 0x7b, 0xbd, 0xd0, 0xe3, 0x16, + 0xac, 0x26, 0x4b, 0x80, 0x10, 0xd4, 0x9e, 0xb7, 0x5f, 0xf4, 0x76, 0x5f, 0x75, 0xf1, 0xa1, 0x72, + 0x2b, 0x69, 0xf1, 0xda, 0x5e, 0xbb, 0xdf, 0xaf, 0x1b, 0xcd, 0x5f, 0x0c, 0xa8, 0x2f, 0x56, 0x23, + 0x39, 0x86, 0x8d, 0x0b, 0x8f, 0xe1, 0xdc, 0x07, 0x8d, 0xe1, 0xe6, 0x9f, 0x39, 0x30, 0xd5, 0x9e, + 0x43, 0x37, 0x63, 0xfb, 0x9d, 0xfa, 0x71, 0xa6, 0x80, 0xe4, 0x90, 0x9d, 0xc3, 0xc3, 0x93, 0x31, + 0xb3, 0x3d, 0xea, 0x3e, 0xd3, 0x77, 0xa0, 0x96, 0xf1, 0xbf, 0x53, 0xfc, 0x08, 0x80, 0xd3, 0xf8, + 0x30, 0xf8, 0x84, 0x13, 0x47, 0x4d, 0x82, 0x7c, 0x96, 0xe0, 0x31, 0x1c, 0x7d, 0x0b, 0x95, 0x40, + 0xd8, 0x5c, 0x68, 0xe9, 0x85, 0x0c, 0xec, 0x24, 0x41, 0x6e, 0x9c, 0xfa, 0x9a, 0x5d, 0xcc, 0xb4, + 0xf1, 0x08, 0xde, 0xfc, 0xd9, 0x00, 0x53, 0xcd, 0x4e, 0xb4, 0x05, 0x25, 0xea, 0x0b, 0xc2, 0x67, + 0xb6, 0xa7, 0xd3, 0x77, 0xce, 0xf6, 0x63, 0x68, 0x38, 0xc8, 0xd4, 0x59, 0xdf, 0x23, 0xdc, 0x21, + 0xbe, 0x38, 0xb3, 0x82, 0x49, 0x05, 0x69, 0x4a, 0xf3, 0x11, 0xd4, 0xd2, 0x43, 0x2b, 0x3c, 0x3a, + 0x8e, 0x47, 0xc7, 0x43, 0xad, 0x44, 0x1e, 0x9d, 0x4e, 0xb8, 0xd0, 0x61, 0xfe, 0x6b, 0x3a, 0xc2, + 0xca, 0xda, 0xfc, 0xbb, 0x06, 0x95, 0xc4, 0x32, 0xda, 0x00, 0x93, 0xf8, 0xf2, 0x42, 0x31, 0x96, + 0x8e, 0x36, 0x8d, 0x0c, 0xef, 0xae, 0x31, 0x11, 0x9c, 0x3a, 0xb1, 0xf2, 0x74, 0xac, 0xd6, 0x8e, + 0xb4, 0xea, 0xc0, 0x1a, 0x8b, 0xb6, 0xc0, 0x9c, 0x30, 0x8f, 0x3a, 0x27, 0xba, 0xda, 0x37, 0x16, + 0x59, 0x03, 0x4e, 0x47, 0x23, 0xc2, 0xf7, 0x24, 0x08, 0x6b, 0x30, 0x7a, 0x0a, 0x20, 0x8e, 0x38, + 0x13, 0xc2, 0xa3, 0xfe, 0x48, 0x97, 0xfa, 0xd3, 0x45, 0xaa, 0xfc, 0x1e, 0xc4, 0x30, 0x9c, 0xa0, + 0x34, 0xfe, 0x32, 0x60, 0x35, 0x29, 0x08, 0xad, 0x83, 0x79, 0x4c, 0x7d, 0x97, 0x1d, 0x2f, 0x2f, + 0x9a, 0x06, 0xa6, 0x9b, 0x35, 0xf7, 0x61, 0xcd, 0xda, 0x86, 0x9a, 0xba, 0x63, 0x5f, 0x44, 0xbd, + 0x92, 0x5f, 0x16, 0x76, 0x81, 0xd0, 0xf8, 0xcd, 0x84, 0x6a, 0x2a, 0x3b, 0xa8, 0x0b, 0x65, 0xc2, + 0x39, 0xe3, 0xe1, 0x48, 0xd1, 0xdb, 0xb8, 0x73, 0x6e, 0x3e, 0x5b, 0xdd, 0x08, 0x8e, 0xe7, 0x4c, + 0xb4, 0x0d, 0xa5, 0xc0, 0x63, 0xc7, 0xd2, 0x8b, 0xda, 0xd6, 0x17, 0xe7, 0x7b, 0xe9, 0x6b, 0x34, + 0x8e, 0x79, 0x8d, 0x7f, 0xf2, 0x50, 0x8e, 0x9d, 0x5f, 0xa8, 0x9f, 0x06, 0x70, 0x9d, 0x93, 0x9f, + 0xa6, 0x24, 0x10, 0xaf, 0x98, 0x37, 0x1d, 0x93, 0xc1, 0x11, 0x27, 0xc1, 0x11, 0xf3, 0xdc, 0x4c, + 0xa9, 0x3e, 0x83, 0x8b, 0xbe, 0x4e, 0xa6, 0x48, 0xa5, 0xfc, 0xff, 0xa7, 0x1c, 0x25, 0x4b, 0x36, + 0x4f, 0xcb, 0x3e, 0x94, 0x82, 0x09, 0x71, 0xa8, 0xed, 0x05, 0xfa, 0xb1, 0xba, 0x95, 0x31, 0xb9, + 0xad, 0xbe, 0xe2, 0xe9, 0xde, 0x8f, 0xdd, 0x34, 0xfe, 0x30, 0xa0, 0x9a, 0xb2, 0xa1, 0xaf, 0xf4, + 0xc3, 0x33, 0xcb, 0xe4, 0x57, 0xef, 0xd0, 0x6f, 0x00, 0xa4, 0xc6, 0x0e, 0x73, 0x49, 0x60, 0xe5, + 0xa4, 0xb0, 0xf7, 0x6e, 0xe9, 0xe1, 0xa6, 0xa2, 0x25, 0xe0, 0x1f, 0x91, 0x8e, 0xc6, 0xef, 0x06, + 0x94, 0xa2, 0xc2, 0x5f, 0xa8, 0xc0, 0xf7, 0xa1, 0x38, 0xb6, 0xdf, 0x61, 0xb1, 0xfc, 0x92, 0x50, + 0x38, 0xf4, 0x28, 0xd1, 0x97, 0x19, 0xb4, 0xce, 0x9b, 0xf1, 0xdf, 0x02, 0xac, 0x2d, 0x0c, 0x03, + 0xb4, 0x0f, 0xd7, 0x1c, 0xe6, 0xb9, 0xdb, 0xc4, 0x63, 0xc7, 0x83, 0xa9, 0x4f, 0x9e, 0xb1, 0x63, + 0x3f, 0x71, 0x6e, 0xce, 0xf5, 0xfc, 0x7e, 0x26, 0xda, 0x81, 0x2b, 0x29, 0xc3, 0xc1, 0x24, 0x71, + 0x84, 0xce, 0x75, 0xf8, 0x3e, 0x5e, 0xa4, 0xb0, 0x3d, 0x64, 0x33, 0x92, 0x52, 0x98, 0xcf, 0xa8, + 0xf0, 0x14, 0x33, 0x52, 0x18, 0x1b, 0xb4, 0xc2, 0x42, 0x46, 0x85, 0x0b, 0x3c, 0xd4, 0x87, 0xeb, + 0xf2, 0xef, 0x72, 0x7c, 0xbc, 0x06, 0x4c, 0x59, 0xf5, 0xf5, 0x79, 0xae, 0xc7, 0x33, 0xa8, 0xe1, + 0x1b, 0xe2, 0xcd, 0xd4, 0x1d, 0x91, 0xf8, 0x0d, 0x61, 0x2e, 0x7d, 0x43, 0xa4, 0xf0, 0xe8, 0x29, + 0xac, 0x0a, 0xe9, 0x6a, 0x8f, 0x70, 0xca, 0x5c, 0xf9, 0x1f, 0x6e, 0x89, 0x96, 0x14, 0x01, 0x75, + 0xa0, 0x26, 0x74, 0xd6, 0xb4, 0x8b, 0xd2, 0x72, 0x17, 0x0b, 0x94, 0xa1, 0x29, 0x41, 0x0f, 0xfe, + 0x0b, 0x00, 0x00, 0xff, 0xff, 0x09, 0xad, 0x9c, 0x23, 0x55, 0x10, 0x00, 0x00, +} diff --git a/common/api/v1/ratelimit.proto b/common/api/v1/ratelimit.proto new file mode 100644 index 000000000..b0e9943b5 --- /dev/null +++ b/common/api/v1/ratelimit.proto @@ -0,0 +1,179 @@ +syntax = "proto3"; + +package v1; + +import "google/protobuf/wrappers.proto"; +import "google/protobuf/duration.proto"; +import "model.proto"; + +// 同一服务下限流规则集合 +message RateLimit { + // 限流规则集合 + repeated Rule rules = 1; + // 限流规则汇总的revision信息 + google.protobuf.StringValue revision = 2; +} + +// 单个限流规则信息 +message Rule { + // 限流规则唯一标识 + google.protobuf.StringValue id = 1; + // 限流规则所属服务名 + google.protobuf.StringValue service = 2; + // 限流规则所属命名空间 + google.protobuf.StringValue namespace = 3; + // 可选,SUBSET标识 + map subset = 4; + // 限流规则优先级,0值最高 + google.protobuf.UInt32Value priority = 5; + // 限流资源 + enum Resource { + // 针对QPS进行限流 + QPS = 0; + // 针对并发数进行限流 + CONCURRENCY = 1; + } + Resource resource = 6; + // 限流类型 + // global全局限流(默认)或者local单机限流 + enum Type { + GLOBAL = 0; + LOCAL = 1; + } + Type type = 7; + // 业务标签集合,通过KV进行匹配,全部匹配才使用该规则 + map labels = 8; + // 限流阈值 + // 可以有多个粒度的配置(比如同时针对秒级,分钟级,天级),匹配一个则进行限流 + // 全局限流模式下,该值为服务配额总量;单机限流模式下,该值为单个节点能处理的配额量 + repeated Amount amounts = 9; + // 限流动作,对应着客户端的插件名字 + google.protobuf.StringValue action = 10; + // 是否停用该限流规则,默认启用 + google.protobuf.BoolValue disable = 11; + // 限流上报方式,同时支持按固定周期上报,以及达到配额百分比后上报 + Report report = 12; + // 限流规则创建时间 + google.protobuf.StringValue ctime = 13; + // 限流规则修改时间 + google.protobuf.StringValue mtime = 14; + // 限流规则revision信息 + google.protobuf.StringValue revision = 15; + // 服务的TOKEN信息,仅用于控制台,discover接口不下发 + google.protobuf.StringValue service_token = 16 [json_name="service_token"]; + // 配额调整算法 + AmountAdjuster adjuster = 17; + // 通配符是否合并计算,默认分开计数 + google.protobuf.BoolValue regex_combine = 18; + + // 限流阈值模式 + enum AmountMode { + GLOBAL_TOTAL = 0; // 总体阈值 + SHARE_EQUALLY = 1; // 单机均摊阈值 + } + AmountMode amount_mode = 19; + // 与限流集群连接失败时降级模式 + enum FailoverType { + FAILOVER_LOCAL = 0; // 降级成本地阈值 + FAILOVER_PASS = 1; // 降级成直接通过 + } + FailoverType failover = 20; + // 分布式限流服务集群 + RateLimitCluster cluster = 21; + // 被调接口名 + MatchString method = 22; +} + +// 分布式限流服务集群 +message RateLimitCluster { + google.protobuf.StringValue service = 1; + // 限流规则所属命名空间 + google.protobuf.StringValue namespace = 2; +} + +// 限流配额 +message Amount { + // 时间周期内的最大配额数 + google.protobuf.UInt32Value maxAmount = 1; + // 配额生效的时间周期,必须大于等于1s + google.protobuf.Duration validDuration = 2; + // 请求统计精度 + google.protobuf.UInt32Value precision = 3; + // 可选,起始限流阈值,爬坡起始值 + google.protobuf.UInt32Value startAmount = 4; + // 可选,最小限流阈值,降低时最小值 + google.protobuf.UInt32Value minAmount = 5; +} + +// 限流上报方式 +message Report { + // 配额固定上报周期,单位毫秒 + google.protobuf.Duration interval = 1; + // 使用了百分之多少配额后启动一次实时上报,值范围(0,100] + google.protobuf.UInt32Value amountPercent = 2; +} + +// 配额调整算法 +message AmountAdjuster { + ClimbConfig climb = 1; +} + +// 限流调整算法Climb相关配置 +message ClimbConfig { + google.protobuf.BoolValue enable = 1; // 是否开启 + + // 限流数据统计配置 + message MetricConfig { + // 限流数据度量周期,默认60s + google.protobuf.Duration window = 1; + // 数据统计精度,决定数据度量的最小周期,度量滑窗的步长=window/precision + google.protobuf.UInt32Value precision = 2; + // 上报周期,默认20s + google.protobuf.Duration reportInterval = 3; + } + MetricConfig metric = 2; // 限流数据统计配置 + + // 触发调整的策略 + message TriggerPolicy { + // 错误率触发调整配置 + message ErrorRate { + google.protobuf.BoolValue enable = 1; // 是否开启 + google.protobuf.UInt32Value requestVolumeThreshold =2; // 触发限流调整的最小的请求数 + google.protobuf.Int32Value errorRate = 3; // 触发限流的错误率配置 + + // 特殊错误码触发调整配置 + message SpecialConfig { + google.protobuf.StringValue type = 1; // 自定义错误类型 + repeated google.protobuf.Int64Value errorCodes = 2; // 特定规则针对的错误码 + google.protobuf.Int32Value errorRate = 3; //特定规则错误率 + } + repeated SpecialConfig specials = 4; // 针对部分错误码,使用额外的错误率统计,可设置多组特殊规则 + } + + // 慢调用触发调整配置 + message SlowRate { + google.protobuf.BoolValue enable = 1; // 是否开启 + google.protobuf.Duration maxRt = 2; // 最大响应时间,超过该响应时间属于慢调用 + google.protobuf.Int32Value slowRate = 3; // 慢请求率阈值,达到该阈值进行限流 + } + + ErrorRate errorRate = 1; // 按错误率阈值调整 + SlowRate slowRate = 2; // 慢调用进行触发调整 + } + + TriggerPolicy policy = 3; // 触发调整策略 + + // 爬坡调整相关参数 + message ClimbThrottling { + google.protobuf.Int32Value coldBelowTuneDownRate = 1; // 冷水位以下区间的下调百分比 + google.protobuf.Int32Value coldBelowTuneUpRate = 2; // 冷水位以下区间的上调百分比 + google.protobuf.Int32Value coldAboveTuneDownRate = 3; // 冷水位以上区间的下调百分比 + google.protobuf.Int32Value coldAboveTuneUpRate = 4; // 冷水位以上区间的上调百分比 + google.protobuf.Int32Value limitThresholdToTuneUp = 5; // 冷水位以上,超过该百分的请求被限流后进行阈值上调 + google.protobuf.Duration judgeDuration = 6; // 阈值调整规则的决策间隔 + google.protobuf.Int32Value tuneUpPeriod = 7; // 阈值上调周期数,连续N个决策间隔都为上调,才执行上调 + google.protobuf.Int32Value tuneDownPeriod = 8; // 阈值下调周期数,连续N个决策间隔都为下调,才执行下调 + } + + ClimbThrottling throttling = 4; // 限流调整相关参数 +} diff --git a/common/api/v1/request.pb.go b/common/api/v1/request.pb.go new file mode 100644 index 000000000..91128dbba --- /dev/null +++ b/common/api/v1/request.pb.go @@ -0,0 +1,130 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: request.proto + +package v1 + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type DiscoverRequest_DiscoverRequestType int32 + +const ( + DiscoverRequest_UNKNOWN DiscoverRequest_DiscoverRequestType = 0 + DiscoverRequest_INSTANCE DiscoverRequest_DiscoverRequestType = 1 + DiscoverRequest_CLUSTER DiscoverRequest_DiscoverRequestType = 2 + DiscoverRequest_ROUTING DiscoverRequest_DiscoverRequestType = 3 + DiscoverRequest_RATE_LIMIT DiscoverRequest_DiscoverRequestType = 4 + DiscoverRequest_CIRCUIT_BREAKER DiscoverRequest_DiscoverRequestType = 5 + DiscoverRequest_SERVICES DiscoverRequest_DiscoverRequestType = 6 +) + +var DiscoverRequest_DiscoverRequestType_name = map[int32]string{ + 0: "UNKNOWN", + 1: "INSTANCE", + 2: "CLUSTER", + 3: "ROUTING", + 4: "RATE_LIMIT", + 5: "CIRCUIT_BREAKER", + 6: "SERVICES", +} +var DiscoverRequest_DiscoverRequestType_value = map[string]int32{ + "UNKNOWN": 0, + "INSTANCE": 1, + "CLUSTER": 2, + "ROUTING": 3, + "RATE_LIMIT": 4, + "CIRCUIT_BREAKER": 5, + "SERVICES": 6, +} + +func (x DiscoverRequest_DiscoverRequestType) String() string { + return proto.EnumName(DiscoverRequest_DiscoverRequestType_name, int32(x)) +} +func (DiscoverRequest_DiscoverRequestType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_request_89a70c847f5ccd5b, []int{0, 0} +} + +type DiscoverRequest struct { + Type DiscoverRequest_DiscoverRequestType `protobuf:"varint,1,opt,name=type,proto3,enum=v1.DiscoverRequest_DiscoverRequestType" json:"type,omitempty"` + Service *Service `protobuf:"bytes,2,opt,name=service,proto3" json:"service,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *DiscoverRequest) Reset() { *m = DiscoverRequest{} } +func (m *DiscoverRequest) String() string { return proto.CompactTextString(m) } +func (*DiscoverRequest) ProtoMessage() {} +func (*DiscoverRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_request_89a70c847f5ccd5b, []int{0} +} +func (m *DiscoverRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_DiscoverRequest.Unmarshal(m, b) +} +func (m *DiscoverRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_DiscoverRequest.Marshal(b, m, deterministic) +} +func (dst *DiscoverRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_DiscoverRequest.Merge(dst, src) +} +func (m *DiscoverRequest) XXX_Size() int { + return xxx_messageInfo_DiscoverRequest.Size(m) +} +func (m *DiscoverRequest) XXX_DiscardUnknown() { + xxx_messageInfo_DiscoverRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_DiscoverRequest proto.InternalMessageInfo + +func (m *DiscoverRequest) GetType() DiscoverRequest_DiscoverRequestType { + if m != nil { + return m.Type + } + return DiscoverRequest_UNKNOWN +} + +func (m *DiscoverRequest) GetService() *Service { + if m != nil { + return m.Service + } + return nil +} + +func init() { + proto.RegisterType((*DiscoverRequest)(nil), "v1.DiscoverRequest") + proto.RegisterEnum("v1.DiscoverRequest_DiscoverRequestType", DiscoverRequest_DiscoverRequestType_name, DiscoverRequest_DiscoverRequestType_value) +} + +func init() { proto.RegisterFile("request.proto", fileDescriptor_request_89a70c847f5ccd5b) } + +var fileDescriptor_request_89a70c847f5ccd5b = []byte{ + // 242 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x90, 0xbf, 0x4b, 0xc3, 0x40, + 0x14, 0xc7, 0xbd, 0x34, 0x6d, 0xc2, 0x8b, 0x6d, 0x1f, 0xd7, 0xa5, 0x38, 0x95, 0x80, 0xd8, 0x29, + 0xd0, 0x3a, 0x3a, 0xc5, 0xf3, 0x90, 0xb3, 0xf5, 0x0a, 0xef, 0x2e, 0x3a, 0x16, 0x2c, 0x37, 0x74, + 0x4a, 0x4c, 0x62, 0xa0, 0xa3, 0xf8, 0x8f, 0x4b, 0x1a, 0x5d, 0x42, 0xc7, 0xef, 0xaf, 0xcf, 0x83, + 0x07, 0xe3, 0xd2, 0x7d, 0x7e, 0xb9, 0xaa, 0x4e, 0x8a, 0x32, 0xaf, 0x73, 0xee, 0x35, 0xab, 0x9b, + 0x71, 0xe5, 0xca, 0xe6, 0x78, 0x70, 0x9d, 0x15, 0x7f, 0x7b, 0x30, 0x7d, 0x3a, 0x56, 0x87, 0xbc, + 0x71, 0x25, 0x75, 0x65, 0xfe, 0x00, 0x7e, 0x7d, 0x2a, 0xdc, 0x9c, 0x2d, 0xd8, 0x72, 0xb2, 0xbe, + 0x4b, 0x9a, 0x55, 0xd2, 0xab, 0xf4, 0xb5, 0x3d, 0x15, 0x8e, 0xce, 0x23, 0x7e, 0x0b, 0xc1, 0xdf, + 0x85, 0xb9, 0xb7, 0x60, 0xcb, 0x68, 0x1d, 0xb5, 0x7b, 0xd3, 0x59, 0xf4, 0x9f, 0xc5, 0x3f, 0x0c, + 0x66, 0x17, 0x20, 0x3c, 0x82, 0x20, 0xd3, 0x1b, 0xbd, 0x7b, 0xd7, 0x78, 0xc5, 0xaf, 0x21, 0x54, + 0xda, 0xd8, 0x54, 0x0b, 0x89, 0xac, 0x8d, 0xc4, 0x36, 0x33, 0x56, 0x12, 0x7a, 0xad, 0xa0, 0x5d, + 0x66, 0x95, 0x7e, 0xc6, 0x01, 0x9f, 0x00, 0x50, 0x6a, 0xe5, 0x7e, 0xab, 0x5e, 0x95, 0x45, 0x9f, + 0xcf, 0x60, 0x2a, 0x14, 0x89, 0x4c, 0xd9, 0xfd, 0x23, 0xc9, 0x74, 0x23, 0x09, 0x87, 0x2d, 0xcc, + 0x48, 0x7a, 0x53, 0x42, 0x1a, 0x1c, 0xc5, 0x7e, 0x18, 0x60, 0xf4, 0xe2, 0x87, 0x03, 0x1c, 0x7e, + 0x8c, 0xce, 0xaf, 0xb8, 0xff, 0x0d, 0x00, 0x00, 0xff, 0xff, 0x54, 0x58, 0xfb, 0xdf, 0x2e, 0x01, + 0x00, 0x00, +} diff --git a/common/api/v1/request.proto b/common/api/v1/request.proto new file mode 100644 index 000000000..10425774d --- /dev/null +++ b/common/api/v1/request.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; + +package v1; + +import "service.proto"; + +message DiscoverRequest { + enum DiscoverRequestType { + UNKNOWN = 0; + INSTANCE = 1; + CLUSTER = 2; + ROUTING = 3; + RATE_LIMIT = 4; + CIRCUIT_BREAKER = 5; + SERVICES = 6; + reserved 7 to 11; + } + + DiscoverRequestType type = 1; + Service service = 2; + reserved 3 to 4; +} diff --git a/common/api/v1/response.go b/common/api/v1/response.go new file mode 100644 index 000000000..68ea7c56d --- /dev/null +++ b/common/api/v1/response.go @@ -0,0 +1,376 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package v1 + +import ( + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/ptypes/wrappers" +) + +/** + * @brief 回复消息接口 + */ +type ResponseMessage interface { + proto.Message + GetCode() *wrappers.UInt32Value +} + +/** + * @brief 获取返回码前三位 + * @note 返回码前三位和HTTP返回码定义一致 + */ +func CalcCode(rm ResponseMessage) int { + return int(rm.GetCode().GetValue() / 1000) +} + +/** + * @brief BatchWriteResponse添加Response + */ +func (b *BatchWriteResponse) Collect(response *Response) { + // 非200的code,都归为异常 + if CalcCode(response) != 200 { + b.Code.Value = ExecuteException + b.Info.Value = code2info[ExecuteException] + } + + b.Size.Value++ + b.Responses = append(b.Responses, response) +} + +/** + * @brief BatchWriteResponse添加Response + */ +func (b *BatchWriteResponse) CollectBatch(response []*Response) { + for _, resp := range response { + b.Collect(resp) + } +} + +/** + * @brief BatchQueryResponse添加命名空间 + */ +func (b *BatchQueryResponse) AddNamespace(namespace *Namespace) { + b.Namespaces = append(b.Namespaces, namespace) +} + +/** + * @brief 创建简单回复 + */ +func NewSimpleResponse(code uint32) *SimpleResponse { + return &SimpleResponse{ + Code: &wrappers.UInt32Value{Value: code}, + Info: &wrappers.StringValue{Value: code2info[code]}, + } +} + +/** + * @brief 创建回复 + */ +func NewResponse(code uint32) *Response { + return &Response{ + Code: &wrappers.UInt32Value{Value: code}, + Info: &wrappers.StringValue{Value: code2info[code]}, + } +} + +// 带上具体的错误信息 +func NewResponseWithMsg(code uint32, msg string) *Response { + resp := NewResponse(code) + resp.Info.Value += ": " + msg + return resp +} + +/** + * @brief 创建回复带客户端信息 + */ +func NewClientResponse(code uint32, client *Client) *Response { + return &Response{ + Code: &wrappers.UInt32Value{Value: code}, + Info: &wrappers.StringValue{Value: code2info[code]}, + Client: client, + } +} + +/** + * @brief 创建回复带命名空间信息 + */ +func NewNamespaceResponse(code uint32, namespace *Namespace) *Response { + return &Response{ + Code: &wrappers.UInt32Value{Value: code}, + Info: &wrappers.StringValue{Value: code2info[code]}, + Namespace: namespace, + } +} + +/** + * @brief 创建回复带服务信息 + */ +func NewServiceResponse(code uint32, service *Service) *Response { + return &Response{ + Code: &wrappers.UInt32Value{Value: code}, + Info: &wrappers.StringValue{Value: code2info[code]}, + Service: service, + } +} + +// 创建带别名信息的答复 +func NewServiceAliasResponse(code uint32, alias *ServiceAlias) *Response { + resp := NewResponse(code) + resp.Alias = alias + return resp +} + +/** + * @brief 创建回复带服务实例信息 + */ +func NewInstanceResponse(code uint32, instance *Instance) *Response { + return &Response{ + Code: &wrappers.UInt32Value{Value: code}, + Info: &wrappers.StringValue{Value: code2info[code]}, + Instance: instance, + } +} + +// 创建带自定义error的服务实例response +func NewInstanceRespWithError(code uint32, err error, instance *Instance) *Response { + resp := NewInstanceResponse(code, instance) + resp.Info.Value += " : " + err.Error() + + return resp +} + +/** + * @brief 创建回复带服务路由信息 + */ +func NewRoutingResponse(code uint32, routing *Routing) *Response { + return &Response{ + Code: &wrappers.UInt32Value{Value: code}, + Info: &wrappers.StringValue{Value: code2info[code]}, + Routing: routing, + } +} + +/** + * @brief 创建回复带限流规则信息 + */ +func NewRateLimitResponse(code uint32, rule *Rule) *Response { + return &Response{ + Code: &wrappers.UInt32Value{Value: code}, + Info: &wrappers.StringValue{Value: code2info[code]}, + RateLimit: rule, + } +} + +/** + * @brief 创建回复带熔断规则信息 + */ +func NewCircuitBreakerResponse(code uint32, circuitBreaker *CircuitBreaker) *Response { + return &Response{ + Code: &wrappers.UInt32Value{Value: code}, + Info: &wrappers.StringValue{Value: code2info[code]}, + CircuitBreaker: circuitBreaker, + } +} + +/** + * @brief 创建回复带发布信息 + */ +func NewConfigResponse(code uint32, configRelease *ConfigRelease) *Response { + return &Response{ + Code: &wrappers.UInt32Value{Value: code}, + Info: &wrappers.StringValue{Value: code2info[code]}, + ConfigRelease: configRelease, + } +} + +/** + * @brief 创建回复带平台信息 + */ +func NewPlatformResponse(code uint32, platform *Platform) *Response { + return &Response{ + Code: &wrappers.UInt32Value{Value: code}, + Info: &wrappers.StringValue{Value: code2info[code]}, + Platform: platform, + } +} + +/** + * @brief 创建带详细信息的平台回复信息 + */ +func NewPlatformResponseWithMsg(code uint32, platform *Platform, msg string) *Response { + response := NewPlatformResponse(code, platform) + response.Info.Value += ": " + msg + return response +} + +/** + * @brief 创建批量回复 + */ +func NewBatchWriteResponse(code uint32) *BatchWriteResponse { + return &BatchWriteResponse{ + Code: &wrappers.UInt32Value{Value: code}, + Info: &wrappers.StringValue{Value: code2info[code]}, + Size: &wrappers.UInt32Value{Value: 0}, + } +} + +/** + * @brief 创建带详细信息的批量回复 + */ +func NewBatchWriteResponseWithMsg(code uint32, msg string) *BatchWriteResponse { + resp := NewBatchWriteResponse(code) + resp.Info.Value += ": " + msg + return resp +} + +/** + * @brief 创建批量查询回复 + */ +func NewBatchQueryResponse(code uint32) *BatchQueryResponse { + return &BatchQueryResponse{ + Code: &wrappers.UInt32Value{Value: code}, + Info: &wrappers.StringValue{Value: code2info[code]}, + Amount: &wrappers.UInt32Value{Value: 0}, + Size: &wrappers.UInt32Value{Value: 0}, + } +} + +/** + * @brief 创建带详细信息的批量查询回复 + */ +func NewBatchQueryResponseWithMsg(code uint32, msg string) *BatchQueryResponse { + resp := NewBatchQueryResponse(code) + resp.Info.Value += ": " + msg + return resp +} + +// 创建一个空白的discoverResponse +func NewDiscoverResponse(code uint32) *DiscoverResponse { + return &DiscoverResponse{ + Code: &wrappers.UInt32Value{Value: code}, + Info: &wrappers.StringValue{Value: code2info[code]}, + } +} + +/** + * @brief 创建查询服务回复 + */ +func NewDiscoverServiceResponse(code uint32, service *Service) *DiscoverResponse { + return &DiscoverResponse{ + Code: &wrappers.UInt32Value{Value: code}, + Info: &wrappers.StringValue{Value: code2info[code]}, + Type: DiscoverResponse_SERVICES, + Service: service, + } +} + +/** + * @brief 创建查询服务实例回复 + */ +func NewDiscoverInstanceResponse(code uint32, service *Service) *DiscoverResponse { + return &DiscoverResponse{ + Code: &wrappers.UInt32Value{Value: code}, + Info: &wrappers.StringValue{Value: code2info[code]}, + Type: DiscoverResponse_INSTANCE, + Service: service, + } +} + +/** + * @brief 创建查询服务路由回复 + */ +func NewDiscoverRoutingResponse(code uint32, service *Service) *DiscoverResponse { + return &DiscoverResponse{ + Code: &wrappers.UInt32Value{Value: code}, + Info: &wrappers.StringValue{Value: code2info[code]}, + Type: DiscoverResponse_ROUTING, + Service: service, + } +} + +/** + * @brief 创建查询限流规则回复 + */ +func NewDiscoverRateLimitResponse(code uint32, service *Service) *DiscoverResponse { + return &DiscoverResponse{ + Code: &wrappers.UInt32Value{Value: code}, + Info: &wrappers.StringValue{Value: code2info[code]}, + Type: DiscoverResponse_RATE_LIMIT, + Service: service, + } +} + +/** + * @brief 创建查询熔断规则回复 + */ +func NewDiscoverCircuitBreakerResponse(code uint32, service *Service) *DiscoverResponse { + return &DiscoverResponse{ + Code: &wrappers.UInt32Value{Value: code}, + Info: &wrappers.StringValue{Value: code2info[code]}, + Type: DiscoverResponse_CIRCUIT_BREAKER, + Service: service, + } +} + +// 格式化responses +// batch操作 +// 如果所有子错误码一致,那么使用子错误码 +// 如果包含任意一个5xx,那么返回500 +func FormatBatchWriteResponse(response *BatchWriteResponse) *BatchWriteResponse { + var code uint32 + for _, resp := range response.Responses { + if code == 0 { + code = resp.GetCode().GetValue() + continue + } + if code == resp.GetCode().GetValue() { + continue + } + // 发现不一样 + code = 0 + break + } + // code不等于0,意味着所有的resp都是一样的错误码,则合并为同一个错误码 + if code != 0 { + response.Code.Value = code + response.Info.Value = code2info[code] + return response + } + + // 错误都不一致 + // 存在5XX,则返回500 + // 不存在5XX,但是存在4XX,则返回4XX + // 除去以上两个情况,不修改返回值 + hasBadRequest := false + for _, resp := range response.Responses { + httpStatus := CalcCode(resp) + if httpStatus >= 500 { + response.Code.Value = ExecuteException + response.Info.Value = code2info[response.Code.Value] + return response + } else if httpStatus >= 400 { + hasBadRequest = true + } + } + + if hasBadRequest { + response.Code.Value = BadRequest + response.Info.Value = code2info[response.Code.Value] + } + return response +} \ No newline at end of file diff --git a/common/api/v1/response.pb.go b/common/api/v1/response.pb.go new file mode 100644 index 000000000..6000804ed --- /dev/null +++ b/common/api/v1/response.pb.go @@ -0,0 +1,588 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: response.proto + +package v1 + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import wrappers "github.com/golang/protobuf/ptypes/wrappers" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type DiscoverResponse_DiscoverResponseType int32 + +const ( + DiscoverResponse_UNKNOWN DiscoverResponse_DiscoverResponseType = 0 + DiscoverResponse_INSTANCE DiscoverResponse_DiscoverResponseType = 1 + DiscoverResponse_CLUSTER DiscoverResponse_DiscoverResponseType = 2 + DiscoverResponse_ROUTING DiscoverResponse_DiscoverResponseType = 3 + DiscoverResponse_RATE_LIMIT DiscoverResponse_DiscoverResponseType = 4 + DiscoverResponse_CIRCUIT_BREAKER DiscoverResponse_DiscoverResponseType = 5 + DiscoverResponse_SERVICES DiscoverResponse_DiscoverResponseType = 6 +) + +var DiscoverResponse_DiscoverResponseType_name = map[int32]string{ + 0: "UNKNOWN", + 1: "INSTANCE", + 2: "CLUSTER", + 3: "ROUTING", + 4: "RATE_LIMIT", + 5: "CIRCUIT_BREAKER", + 6: "SERVICES", +} +var DiscoverResponse_DiscoverResponseType_value = map[string]int32{ + "UNKNOWN": 0, + "INSTANCE": 1, + "CLUSTER": 2, + "ROUTING": 3, + "RATE_LIMIT": 4, + "CIRCUIT_BREAKER": 5, + "SERVICES": 6, +} + +func (x DiscoverResponse_DiscoverResponseType) String() string { + return proto.EnumName(DiscoverResponse_DiscoverResponseType_name, int32(x)) +} +func (DiscoverResponse_DiscoverResponseType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_response_64a333bb5c94d3ea, []int{4, 0} +} + +type SimpleResponse struct { + Code *wrappers.UInt32Value `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"` + Info *wrappers.StringValue `protobuf:"bytes,2,opt,name=info,proto3" json:"info,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SimpleResponse) Reset() { *m = SimpleResponse{} } +func (m *SimpleResponse) String() string { return proto.CompactTextString(m) } +func (*SimpleResponse) ProtoMessage() {} +func (*SimpleResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_response_64a333bb5c94d3ea, []int{0} +} +func (m *SimpleResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SimpleResponse.Unmarshal(m, b) +} +func (m *SimpleResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SimpleResponse.Marshal(b, m, deterministic) +} +func (dst *SimpleResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_SimpleResponse.Merge(dst, src) +} +func (m *SimpleResponse) XXX_Size() int { + return xxx_messageInfo_SimpleResponse.Size(m) +} +func (m *SimpleResponse) XXX_DiscardUnknown() { + xxx_messageInfo_SimpleResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_SimpleResponse proto.InternalMessageInfo + +func (m *SimpleResponse) GetCode() *wrappers.UInt32Value { + if m != nil { + return m.Code + } + return nil +} + +func (m *SimpleResponse) GetInfo() *wrappers.StringValue { + if m != nil { + return m.Info + } + return nil +} + +type Response struct { + Code *wrappers.UInt32Value `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"` + Info *wrappers.StringValue `protobuf:"bytes,2,opt,name=info,proto3" json:"info,omitempty"` + Client *Client `protobuf:"bytes,3,opt,name=client,proto3" json:"client,omitempty"` + Namespace *Namespace `protobuf:"bytes,4,opt,name=namespace,proto3" json:"namespace,omitempty"` + Service *Service `protobuf:"bytes,5,opt,name=service,proto3" json:"service,omitempty"` + Instance *Instance `protobuf:"bytes,6,opt,name=instance,proto3" json:"instance,omitempty"` + Routing *Routing `protobuf:"bytes,7,opt,name=routing,proto3" json:"routing,omitempty"` + Alias *ServiceAlias `protobuf:"bytes,8,opt,name=alias,proto3" json:"alias,omitempty"` + RateLimit *Rule `protobuf:"bytes,9,opt,name=rateLimit,proto3" json:"rateLimit,omitempty"` + CircuitBreaker *CircuitBreaker `protobuf:"bytes,10,opt,name=circuitBreaker,proto3" json:"circuitBreaker,omitempty"` + ConfigRelease *ConfigRelease `protobuf:"bytes,11,opt,name=configRelease,proto3" json:"configRelease,omitempty"` + Platform *Platform `protobuf:"bytes,15,opt,name=platform,proto3" json:"platform,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Response) Reset() { *m = Response{} } +func (m *Response) String() string { return proto.CompactTextString(m) } +func (*Response) ProtoMessage() {} +func (*Response) Descriptor() ([]byte, []int) { + return fileDescriptor_response_64a333bb5c94d3ea, []int{1} +} +func (m *Response) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Response.Unmarshal(m, b) +} +func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Response.Marshal(b, m, deterministic) +} +func (dst *Response) XXX_Merge(src proto.Message) { + xxx_messageInfo_Response.Merge(dst, src) +} +func (m *Response) XXX_Size() int { + return xxx_messageInfo_Response.Size(m) +} +func (m *Response) XXX_DiscardUnknown() { + xxx_messageInfo_Response.DiscardUnknown(m) +} + +var xxx_messageInfo_Response proto.InternalMessageInfo + +func (m *Response) GetCode() *wrappers.UInt32Value { + if m != nil { + return m.Code + } + return nil +} + +func (m *Response) GetInfo() *wrappers.StringValue { + if m != nil { + return m.Info + } + return nil +} + +func (m *Response) GetClient() *Client { + if m != nil { + return m.Client + } + return nil +} + +func (m *Response) GetNamespace() *Namespace { + if m != nil { + return m.Namespace + } + return nil +} + +func (m *Response) GetService() *Service { + if m != nil { + return m.Service + } + return nil +} + +func (m *Response) GetInstance() *Instance { + if m != nil { + return m.Instance + } + return nil +} + +func (m *Response) GetRouting() *Routing { + if m != nil { + return m.Routing + } + return nil +} + +func (m *Response) GetAlias() *ServiceAlias { + if m != nil { + return m.Alias + } + return nil +} + +func (m *Response) GetRateLimit() *Rule { + if m != nil { + return m.RateLimit + } + return nil +} + +func (m *Response) GetCircuitBreaker() *CircuitBreaker { + if m != nil { + return m.CircuitBreaker + } + return nil +} + +func (m *Response) GetConfigRelease() *ConfigRelease { + if m != nil { + return m.ConfigRelease + } + return nil +} + +func (m *Response) GetPlatform() *Platform { + if m != nil { + return m.Platform + } + return nil +} + +type BatchWriteResponse struct { + Code *wrappers.UInt32Value `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"` + Info *wrappers.StringValue `protobuf:"bytes,2,opt,name=info,proto3" json:"info,omitempty"` + Size *wrappers.UInt32Value `protobuf:"bytes,3,opt,name=size,proto3" json:"size,omitempty"` + Responses []*Response `protobuf:"bytes,4,rep,name=responses,proto3" json:"responses,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *BatchWriteResponse) Reset() { *m = BatchWriteResponse{} } +func (m *BatchWriteResponse) String() string { return proto.CompactTextString(m) } +func (*BatchWriteResponse) ProtoMessage() {} +func (*BatchWriteResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_response_64a333bb5c94d3ea, []int{2} +} +func (m *BatchWriteResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_BatchWriteResponse.Unmarshal(m, b) +} +func (m *BatchWriteResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_BatchWriteResponse.Marshal(b, m, deterministic) +} +func (dst *BatchWriteResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_BatchWriteResponse.Merge(dst, src) +} +func (m *BatchWriteResponse) XXX_Size() int { + return xxx_messageInfo_BatchWriteResponse.Size(m) +} +func (m *BatchWriteResponse) XXX_DiscardUnknown() { + xxx_messageInfo_BatchWriteResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_BatchWriteResponse proto.InternalMessageInfo + +func (m *BatchWriteResponse) GetCode() *wrappers.UInt32Value { + if m != nil { + return m.Code + } + return nil +} + +func (m *BatchWriteResponse) GetInfo() *wrappers.StringValue { + if m != nil { + return m.Info + } + return nil +} + +func (m *BatchWriteResponse) GetSize() *wrappers.UInt32Value { + if m != nil { + return m.Size + } + return nil +} + +func (m *BatchWriteResponse) GetResponses() []*Response { + if m != nil { + return m.Responses + } + return nil +} + +type BatchQueryResponse struct { + Code *wrappers.UInt32Value `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"` + Info *wrappers.StringValue `protobuf:"bytes,2,opt,name=info,proto3" json:"info,omitempty"` + Amount *wrappers.UInt32Value `protobuf:"bytes,3,opt,name=amount,proto3" json:"amount,omitempty"` + Size *wrappers.UInt32Value `protobuf:"bytes,4,opt,name=size,proto3" json:"size,omitempty"` + Namespaces []*Namespace `protobuf:"bytes,5,rep,name=namespaces,proto3" json:"namespaces,omitempty"` + Services []*Service `protobuf:"bytes,6,rep,name=services,proto3" json:"services,omitempty"` + Instances []*Instance `protobuf:"bytes,7,rep,name=instances,proto3" json:"instances,omitempty"` + Routings []*Routing `protobuf:"bytes,8,rep,name=routings,proto3" json:"routings,omitempty"` + Aliases []*ServiceAlias `protobuf:"bytes,9,rep,name=aliases,proto3" json:"aliases,omitempty"` + RateLimits []*Rule `protobuf:"bytes,10,rep,name=rateLimits,proto3" json:"rateLimits,omitempty"` + ConfigWithServices []*ConfigWithService `protobuf:"bytes,11,rep,name=configWithServices,proto3" json:"configWithServices,omitempty"` + Platforms []*Platform `protobuf:"bytes,15,rep,name=platforms,proto3" json:"platforms,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *BatchQueryResponse) Reset() { *m = BatchQueryResponse{} } +func (m *BatchQueryResponse) String() string { return proto.CompactTextString(m) } +func (*BatchQueryResponse) ProtoMessage() {} +func (*BatchQueryResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_response_64a333bb5c94d3ea, []int{3} +} +func (m *BatchQueryResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_BatchQueryResponse.Unmarshal(m, b) +} +func (m *BatchQueryResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_BatchQueryResponse.Marshal(b, m, deterministic) +} +func (dst *BatchQueryResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_BatchQueryResponse.Merge(dst, src) +} +func (m *BatchQueryResponse) XXX_Size() int { + return xxx_messageInfo_BatchQueryResponse.Size(m) +} +func (m *BatchQueryResponse) XXX_DiscardUnknown() { + xxx_messageInfo_BatchQueryResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_BatchQueryResponse proto.InternalMessageInfo + +func (m *BatchQueryResponse) GetCode() *wrappers.UInt32Value { + if m != nil { + return m.Code + } + return nil +} + +func (m *BatchQueryResponse) GetInfo() *wrappers.StringValue { + if m != nil { + return m.Info + } + return nil +} + +func (m *BatchQueryResponse) GetAmount() *wrappers.UInt32Value { + if m != nil { + return m.Amount + } + return nil +} + +func (m *BatchQueryResponse) GetSize() *wrappers.UInt32Value { + if m != nil { + return m.Size + } + return nil +} + +func (m *BatchQueryResponse) GetNamespaces() []*Namespace { + if m != nil { + return m.Namespaces + } + return nil +} + +func (m *BatchQueryResponse) GetServices() []*Service { + if m != nil { + return m.Services + } + return nil +} + +func (m *BatchQueryResponse) GetInstances() []*Instance { + if m != nil { + return m.Instances + } + return nil +} + +func (m *BatchQueryResponse) GetRoutings() []*Routing { + if m != nil { + return m.Routings + } + return nil +} + +func (m *BatchQueryResponse) GetAliases() []*ServiceAlias { + if m != nil { + return m.Aliases + } + return nil +} + +func (m *BatchQueryResponse) GetRateLimits() []*Rule { + if m != nil { + return m.RateLimits + } + return nil +} + +func (m *BatchQueryResponse) GetConfigWithServices() []*ConfigWithService { + if m != nil { + return m.ConfigWithServices + } + return nil +} + +func (m *BatchQueryResponse) GetPlatforms() []*Platform { + if m != nil { + return m.Platforms + } + return nil +} + +type DiscoverResponse struct { + Code *wrappers.UInt32Value `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"` + Info *wrappers.StringValue `protobuf:"bytes,2,opt,name=info,proto3" json:"info,omitempty"` + Type DiscoverResponse_DiscoverResponseType `protobuf:"varint,3,opt,name=type,proto3,enum=v1.DiscoverResponse_DiscoverResponseType" json:"type,omitempty"` + Service *Service `protobuf:"bytes,4,opt,name=service,proto3" json:"service,omitempty"` + Instances []*Instance `protobuf:"bytes,5,rep,name=instances,proto3" json:"instances,omitempty"` + Routing *Routing `protobuf:"bytes,6,opt,name=routing,proto3" json:"routing,omitempty"` + RateLimit *RateLimit `protobuf:"bytes,7,opt,name=rateLimit,proto3" json:"rateLimit,omitempty"` + CircuitBreaker *CircuitBreaker `protobuf:"bytes,8,opt,name=circuitBreaker,proto3" json:"circuitBreaker,omitempty"` + Services []*Service `protobuf:"bytes,9,rep,name=services,proto3" json:"services,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *DiscoverResponse) Reset() { *m = DiscoverResponse{} } +func (m *DiscoverResponse) String() string { return proto.CompactTextString(m) } +func (*DiscoverResponse) ProtoMessage() {} +func (*DiscoverResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_response_64a333bb5c94d3ea, []int{4} +} +func (m *DiscoverResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_DiscoverResponse.Unmarshal(m, b) +} +func (m *DiscoverResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_DiscoverResponse.Marshal(b, m, deterministic) +} +func (dst *DiscoverResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_DiscoverResponse.Merge(dst, src) +} +func (m *DiscoverResponse) XXX_Size() int { + return xxx_messageInfo_DiscoverResponse.Size(m) +} +func (m *DiscoverResponse) XXX_DiscardUnknown() { + xxx_messageInfo_DiscoverResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_DiscoverResponse proto.InternalMessageInfo + +func (m *DiscoverResponse) GetCode() *wrappers.UInt32Value { + if m != nil { + return m.Code + } + return nil +} + +func (m *DiscoverResponse) GetInfo() *wrappers.StringValue { + if m != nil { + return m.Info + } + return nil +} + +func (m *DiscoverResponse) GetType() DiscoverResponse_DiscoverResponseType { + if m != nil { + return m.Type + } + return DiscoverResponse_UNKNOWN +} + +func (m *DiscoverResponse) GetService() *Service { + if m != nil { + return m.Service + } + return nil +} + +func (m *DiscoverResponse) GetInstances() []*Instance { + if m != nil { + return m.Instances + } + return nil +} + +func (m *DiscoverResponse) GetRouting() *Routing { + if m != nil { + return m.Routing + } + return nil +} + +func (m *DiscoverResponse) GetRateLimit() *RateLimit { + if m != nil { + return m.RateLimit + } + return nil +} + +func (m *DiscoverResponse) GetCircuitBreaker() *CircuitBreaker { + if m != nil { + return m.CircuitBreaker + } + return nil +} + +func (m *DiscoverResponse) GetServices() []*Service { + if m != nil { + return m.Services + } + return nil +} + +func init() { + proto.RegisterType((*SimpleResponse)(nil), "v1.SimpleResponse") + proto.RegisterType((*Response)(nil), "v1.Response") + proto.RegisterType((*BatchWriteResponse)(nil), "v1.BatchWriteResponse") + proto.RegisterType((*BatchQueryResponse)(nil), "v1.BatchQueryResponse") + proto.RegisterType((*DiscoverResponse)(nil), "v1.DiscoverResponse") + proto.RegisterEnum("v1.DiscoverResponse_DiscoverResponseType", DiscoverResponse_DiscoverResponseType_name, DiscoverResponse_DiscoverResponseType_value) +} + +func init() { proto.RegisterFile("response.proto", fileDescriptor_response_64a333bb5c94d3ea) } + +var fileDescriptor_response_64a333bb5c94d3ea = []byte{ + // 842 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x55, 0xd1, 0x6e, 0xe3, 0x44, + 0x14, 0x25, 0xad, 0x93, 0x38, 0x37, 0x6d, 0xe2, 0x9d, 0x2e, 0xd2, 0xa8, 0x42, 0x68, 0x65, 0x89, + 0xa5, 0x64, 0x45, 0x96, 0xed, 0x22, 0x21, 0x21, 0xf1, 0xd0, 0xa6, 0xee, 0xe2, 0xb6, 0xeb, 0xc2, + 0x38, 0x69, 0x79, 0xab, 0x5c, 0x33, 0xcd, 0x8e, 0x70, 0x6c, 0x6b, 0xc6, 0x29, 0x2a, 0x5f, 0xc0, + 0xf7, 0xf0, 0x01, 0x7c, 0x06, 0x1f, 0xc0, 0x97, 0xa0, 0x19, 0xcf, 0xd8, 0x6e, 0x77, 0xc9, 0xf6, + 0x29, 0x2f, 0x55, 0xe7, 0x9e, 0x73, 0x67, 0xee, 0xbd, 0x3e, 0xe7, 0x06, 0x06, 0x9c, 0x8a, 0x3c, + 0x4b, 0x05, 0x1d, 0xe7, 0x3c, 0x2b, 0x32, 0xb4, 0x71, 0xfb, 0x6a, 0xf7, 0xf3, 0x79, 0x96, 0xcd, + 0x13, 0xfa, 0x52, 0x45, 0xae, 0x97, 0x37, 0x2f, 0x7f, 0xe7, 0x51, 0x9e, 0x53, 0x2e, 0x4a, 0xce, + 0xee, 0xb6, 0xa0, 0xfc, 0x96, 0xc5, 0xd4, 0x1c, 0x79, 0xb6, 0x2c, 0x58, 0x3a, 0xd7, 0xc7, 0xad, + 0x38, 0x61, 0x34, 0x2d, 0xf4, 0x69, 0xc8, 0xa3, 0x82, 0x26, 0x6c, 0xc1, 0x4c, 0xe0, 0x69, 0xcc, + 0x78, 0xbc, 0x64, 0xc5, 0x35, 0xa7, 0xd1, 0x6f, 0x94, 0xeb, 0xe8, 0x4e, 0x9c, 0xa5, 0x37, 0x6c, + 0xce, 0x69, 0x42, 0x23, 0x53, 0xcb, 0xee, 0x20, 0x4f, 0xa2, 0xe2, 0x26, 0xe3, 0x8b, 0xf2, 0xec, + 0x16, 0x30, 0x08, 0xd9, 0x22, 0x4f, 0x28, 0xd1, 0x35, 0xa3, 0x6f, 0xc0, 0x8a, 0xb3, 0x5f, 0x29, + 0x6e, 0x3d, 0x6b, 0xed, 0xf5, 0xf7, 0x3f, 0x1b, 0x97, 0x85, 0x8f, 0x4d, 0xe1, 0xe3, 0x99, 0x9f, + 0x16, 0xaf, 0xf7, 0x2f, 0xa2, 0x64, 0x49, 0x89, 0x62, 0xca, 0x0c, 0x96, 0xde, 0x64, 0x78, 0xe3, + 0x7f, 0x32, 0xc2, 0x82, 0xb3, 0x74, 0xae, 0x33, 0x24, 0xd3, 0xfd, 0xcb, 0x02, 0x7b, 0x9d, 0x0f, + 0x22, 0x17, 0x3a, 0xe5, 0x08, 0xf1, 0xa6, 0xca, 0x81, 0xf1, 0xed, 0xab, 0xf1, 0x44, 0x45, 0x88, + 0x46, 0xd0, 0x0b, 0xe8, 0xa5, 0xd1, 0x82, 0x8a, 0x3c, 0x8a, 0x29, 0xb6, 0x14, 0x6d, 0x5b, 0xd2, + 0x02, 0x13, 0x24, 0x35, 0x8e, 0xbe, 0x80, 0xae, 0xfe, 0x62, 0xb8, 0xad, 0xa8, 0x7d, 0x49, 0x0d, + 0xcb, 0x10, 0x31, 0x18, 0xda, 0x03, 0x9b, 0xa5, 0xa2, 0x88, 0xd2, 0x98, 0xe2, 0x8e, 0xe2, 0x6d, + 0x49, 0x9e, 0xaf, 0x63, 0xa4, 0x42, 0xe5, 0x85, 0xfa, 0x9b, 0xe3, 0x6e, 0x7d, 0x21, 0x29, 0x43, + 0xc4, 0x60, 0xe8, 0x39, 0xb4, 0xa3, 0x84, 0x45, 0x02, 0xdb, 0x8a, 0xe4, 0x34, 0x5e, 0x3d, 0x90, + 0x71, 0x52, 0xc2, 0xe8, 0x39, 0xf4, 0xa4, 0x4a, 0xce, 0xa4, 0x4a, 0x70, 0x4f, 0x71, 0x6d, 0x75, + 0xe1, 0x32, 0xa1, 0xa4, 0x86, 0xd0, 0xf7, 0x30, 0xd0, 0xe2, 0x39, 0x2c, 0xc5, 0x83, 0x41, 0x91, + 0x91, 0x1a, 0xd0, 0x3d, 0x84, 0x3c, 0x60, 0xa2, 0xef, 0x60, 0xbb, 0x94, 0x18, 0x29, 0x25, 0x86, + 0xfb, 0x2a, 0xf5, 0x89, 0x4a, 0x6d, 0x02, 0xe4, 0x3e, 0x4f, 0x4e, 0xc5, 0xc8, 0x10, 0x0f, 0xeb, + 0xa9, 0xfc, 0xa4, 0x63, 0xa4, 0x42, 0x4f, 0x2c, 0x7b, 0xcb, 0x19, 0x9e, 0x58, 0xb6, 0xe3, 0xec, + 0xb8, 0xff, 0xb4, 0x00, 0x1d, 0x46, 0x45, 0xfc, 0xee, 0x92, 0xb3, 0x62, 0xad, 0x7a, 0x95, 0x19, + 0x82, 0xfd, 0x41, 0xb5, 0x78, 0x3e, 0xf2, 0x86, 0x64, 0xa2, 0x11, 0xf4, 0xcc, 0x16, 0x10, 0xd8, + 0x7a, 0xb6, 0x69, 0x7a, 0x34, 0x65, 0x93, 0x1a, 0x76, 0xff, 0xb5, 0x74, 0x63, 0x3f, 0x2f, 0x29, + 0xbf, 0x5b, 0x6b, 0x63, 0xdf, 0x42, 0x27, 0x5a, 0x64, 0xcb, 0xca, 0x17, 0xab, 0x5f, 0xd1, 0xdc, + 0x6a, 0x1c, 0xd6, 0xa3, 0xc7, 0xf1, 0x35, 0x40, 0xe5, 0x1d, 0x81, 0xdb, 0x6a, 0x1e, 0x0f, 0xcc, + 0xd5, 0x20, 0xa0, 0x2f, 0xc1, 0xd6, 0x0e, 0x12, 0xb8, 0xa3, 0xc8, 0xf7, 0xec, 0x55, 0x81, 0x72, + 0xcc, 0xc6, 0x41, 0x02, 0x77, 0xeb, 0x31, 0x57, 0x06, 0xab, 0x61, 0x79, 0xa9, 0x76, 0x91, 0x74, + 0xcf, 0xe6, 0x43, 0x8b, 0x55, 0x20, 0x1a, 0x41, 0x57, 0x99, 0x88, 0x0a, 0xdc, 0x53, 0xbc, 0xf7, + 0x5d, 0x66, 0x08, 0x68, 0x0f, 0xa0, 0x32, 0x93, 0xc0, 0xa0, 0xe8, 0xb5, 0xd1, 0x1a, 0x18, 0xf2, + 0x00, 0x95, 0x2e, 0xb8, 0x64, 0xc5, 0xbb, 0xd0, 0x74, 0xd7, 0x57, 0x19, 0x9f, 0xd6, 0x96, 0x69, + 0xa0, 0xe4, 0x03, 0x09, 0xb2, 0x63, 0xe3, 0x0e, 0x81, 0x87, 0x75, 0xc7, 0x95, 0x79, 0x6a, 0xb8, + 0xe1, 0x9e, 0x27, 0xee, 0x9f, 0x6d, 0x70, 0x8e, 0x98, 0x88, 0xb3, 0x5b, 0xca, 0xd7, 0x2a, 0xb1, + 0x1f, 0xc0, 0x2a, 0xee, 0xf2, 0xd2, 0x3b, 0x83, 0xfd, 0xaf, 0x64, 0xad, 0x0f, 0xeb, 0x78, 0x2f, + 0x30, 0xbd, 0xcb, 0x29, 0x51, 0x69, 0xcd, 0x45, 0x6b, 0xad, 0x58, 0xb4, 0xf7, 0x84, 0xd0, 0x5e, + 0x2d, 0x84, 0xc6, 0xaa, 0xed, 0xac, 0x58, 0xb5, 0x2f, 0x9a, 0x2b, 0xb4, 0x5b, 0xff, 0x1e, 0x10, + 0x13, 0x5c, 0xbd, 0x47, 0xed, 0x47, 0xef, 0xd1, 0xa6, 0xda, 0x7b, 0x2b, 0xd4, 0xee, 0xfe, 0xdd, + 0x82, 0xa7, 0x1f, 0x1a, 0x15, 0xea, 0x43, 0x77, 0x16, 0x9c, 0x06, 0xe7, 0x97, 0x81, 0xf3, 0x09, + 0xda, 0x02, 0xdb, 0x0f, 0xc2, 0xe9, 0x41, 0x30, 0xf1, 0x9c, 0x96, 0x84, 0x26, 0x67, 0xb3, 0x70, + 0xea, 0x11, 0x67, 0x43, 0x1e, 0xc8, 0xf9, 0x6c, 0xea, 0x07, 0x6f, 0x9c, 0x4d, 0x34, 0x00, 0x20, + 0x07, 0x53, 0xef, 0xea, 0xcc, 0x7f, 0xeb, 0x4f, 0x1d, 0x0b, 0xed, 0xc0, 0x70, 0xe2, 0x93, 0xc9, + 0xcc, 0x9f, 0x5e, 0x1d, 0x12, 0xef, 0xe0, 0xd4, 0x23, 0x4e, 0x5b, 0x5e, 0x16, 0x7a, 0xe4, 0xc2, + 0x9f, 0x78, 0xa1, 0xd3, 0x71, 0x2d, 0xbb, 0xeb, 0xf4, 0x47, 0xd6, 0x5b, 0x2f, 0xfc, 0x71, 0xd4, + 0x97, 0x7f, 0xaf, 0x26, 0xe7, 0xc1, 0xb1, 0xff, 0x66, 0x34, 0x38, 0x3e, 0x9b, 0xfd, 0x72, 0x75, + 0x74, 0x48, 0xbc, 0x63, 0x22, 0x41, 0x5b, 0x9d, 0xc3, 0xa3, 0xd3, 0x51, 0xbf, 0xfc, 0xcf, 0x23, + 0x17, 0x1e, 0x39, 0xb1, 0x6c, 0x70, 0x06, 0xd7, 0x1d, 0xa5, 0x96, 0xd7, 0xff, 0x05, 0x00, 0x00, + 0xff, 0xff, 0x42, 0xc3, 0xf9, 0x58, 0x28, 0x09, 0x00, 0x00, +} diff --git a/common/api/v1/response.proto b/common/api/v1/response.proto new file mode 100644 index 000000000..2e817dc53 --- /dev/null +++ b/common/api/v1/response.proto @@ -0,0 +1,82 @@ +syntax = "proto3"; + +package v1; + +import "google/protobuf/wrappers.proto"; +import "service.proto"; +import "routing.proto"; +import "client.proto"; +import "ratelimit.proto"; +import "circuitbreaker.proto"; +import "configrelease.proto"; +import "platform.proto"; + +message SimpleResponse { + google.protobuf.UInt32Value code = 1; + google.protobuf.StringValue info = 2; +} + +message Response { + google.protobuf.UInt32Value code = 1; + google.protobuf.StringValue info = 2; + Client client = 3; + Namespace namespace = 4; + Service service = 5; + Instance instance = 6; + Routing routing = 7; + ServiceAlias alias = 8; + Rule rateLimit = 9; + CircuitBreaker circuitBreaker = 10; + ConfigRelease configRelease = 11; + Platform platform = 15; + reserved 12 to 14, 16 to 18; +} + +message BatchWriteResponse { + google.protobuf.UInt32Value code = 1; + google.protobuf.StringValue info = 2; + google.protobuf.UInt32Value size = 3; + repeated Response responses = 4; +} + +message BatchQueryResponse { + google.protobuf.UInt32Value code = 1; + google.protobuf.StringValue info = 2; + google.protobuf.UInt32Value amount = 3; + google.protobuf.UInt32Value size = 4; + repeated Namespace namespaces = 5; + repeated Service services = 6; + repeated Instance instances = 7; + repeated Routing routings = 8; + repeated ServiceAlias aliases = 9; + repeated Rule rateLimits = 10; + repeated ConfigWithService configWithServices = 11; + repeated Platform platforms = 15; + reserved 12 to 14, 16; +} + +message DiscoverResponse { + google.protobuf.UInt32Value code = 1; + google.protobuf.StringValue info = 2; + + enum DiscoverResponseType { + UNKNOWN = 0; + INSTANCE = 1; + CLUSTER = 2; + ROUTING = 3; + RATE_LIMIT = 4; + CIRCUIT_BREAKER = 5; + SERVICES = 6; + reserved 7 to 11; + reserved "MESH", "MESH_CONFIG", "FLUX_DBREFRESH", "FLUX_SDK", "FLUX_SERVER"; + } + + DiscoverResponseType type = 3; + Service service = 4; + repeated Instance instances = 5; + Routing routing = 6; + RateLimit rateLimit = 7; + CircuitBreaker circuitBreaker = 8; + repeated Service services = 9; + reserved 10 to 13; +} diff --git a/common/api/v1/routing.pb.go b/common/api/v1/routing.pb.go new file mode 100644 index 000000000..673cad9ae --- /dev/null +++ b/common/api/v1/routing.pb.go @@ -0,0 +1,368 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: routing.proto + +package v1 + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import wrappers "github.com/golang/protobuf/ptypes/wrappers" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type Routing struct { + // 规则所属服务以及命名空间 + Service *wrappers.StringValue `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` + Namespace *wrappers.StringValue `protobuf:"bytes,2,opt,name=namespace,proto3" json:"namespace,omitempty"` + // 每个服务可以配置多条入站或者出站规则 + // 对于每个请求,从上到下依次匹配,若命中则终止 + Inbounds []*Route `protobuf:"bytes,3,rep,name=inbounds,proto3" json:"inbounds,omitempty"` + Outbounds []*Route `protobuf:"bytes,4,rep,name=outbounds,proto3" json:"outbounds,omitempty"` + Ctime *wrappers.StringValue `protobuf:"bytes,5,opt,name=ctime,proto3" json:"ctime,omitempty"` + Mtime *wrappers.StringValue `protobuf:"bytes,6,opt,name=mtime,proto3" json:"mtime,omitempty"` + Revision *wrappers.StringValue `protobuf:"bytes,7,opt,name=revision,proto3" json:"revision,omitempty"` + ServiceToken *wrappers.StringValue `protobuf:"bytes,8,opt,name=service_token,proto3" json:"service_token,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Routing) Reset() { *m = Routing{} } +func (m *Routing) String() string { return proto.CompactTextString(m) } +func (*Routing) ProtoMessage() {} +func (*Routing) Descriptor() ([]byte, []int) { + return fileDescriptor_routing_413c16898e0eee19, []int{0} +} +func (m *Routing) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Routing.Unmarshal(m, b) +} +func (m *Routing) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Routing.Marshal(b, m, deterministic) +} +func (dst *Routing) XXX_Merge(src proto.Message) { + xxx_messageInfo_Routing.Merge(dst, src) +} +func (m *Routing) XXX_Size() int { + return xxx_messageInfo_Routing.Size(m) +} +func (m *Routing) XXX_DiscardUnknown() { + xxx_messageInfo_Routing.DiscardUnknown(m) +} + +var xxx_messageInfo_Routing proto.InternalMessageInfo + +func (m *Routing) GetService() *wrappers.StringValue { + if m != nil { + return m.Service + } + return nil +} + +func (m *Routing) GetNamespace() *wrappers.StringValue { + if m != nil { + return m.Namespace + } + return nil +} + +func (m *Routing) GetInbounds() []*Route { + if m != nil { + return m.Inbounds + } + return nil +} + +func (m *Routing) GetOutbounds() []*Route { + if m != nil { + return m.Outbounds + } + return nil +} + +func (m *Routing) GetCtime() *wrappers.StringValue { + if m != nil { + return m.Ctime + } + return nil +} + +func (m *Routing) GetMtime() *wrappers.StringValue { + if m != nil { + return m.Mtime + } + return nil +} + +func (m *Routing) GetRevision() *wrappers.StringValue { + if m != nil { + return m.Revision + } + return nil +} + +func (m *Routing) GetServiceToken() *wrappers.StringValue { + if m != nil { + return m.ServiceToken + } + return nil +} + +type Route struct { + // 如果匹配Source规则,按照Destination路由 + // 多个Source之间的关系为或 + Sources []*Source `protobuf:"bytes,1,rep,name=sources,proto3" json:"sources,omitempty"` + Destinations []*Destination `protobuf:"bytes,2,rep,name=destinations,proto3" json:"destinations,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Route) Reset() { *m = Route{} } +func (m *Route) String() string { return proto.CompactTextString(m) } +func (*Route) ProtoMessage() {} +func (*Route) Descriptor() ([]byte, []int) { + return fileDescriptor_routing_413c16898e0eee19, []int{1} +} +func (m *Route) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Route.Unmarshal(m, b) +} +func (m *Route) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Route.Marshal(b, m, deterministic) +} +func (dst *Route) XXX_Merge(src proto.Message) { + xxx_messageInfo_Route.Merge(dst, src) +} +func (m *Route) XXX_Size() int { + return xxx_messageInfo_Route.Size(m) +} +func (m *Route) XXX_DiscardUnknown() { + xxx_messageInfo_Route.DiscardUnknown(m) +} + +var xxx_messageInfo_Route proto.InternalMessageInfo + +func (m *Route) GetSources() []*Source { + if m != nil { + return m.Sources + } + return nil +} + +func (m *Route) GetDestinations() []*Destination { + if m != nil { + return m.Destinations + } + return nil +} + +type Source struct { + // 主调方服务以及命名空间 + Service *wrappers.StringValue `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` + Namespace *wrappers.StringValue `protobuf:"bytes,2,opt,name=namespace,proto3" json:"namespace,omitempty"` + // 主调方服务实例标签或者请求标签 + // value支持正则匹配 + Metadata map[string]*MatchString `protobuf:"bytes,3,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Source) Reset() { *m = Source{} } +func (m *Source) String() string { return proto.CompactTextString(m) } +func (*Source) ProtoMessage() {} +func (*Source) Descriptor() ([]byte, []int) { + return fileDescriptor_routing_413c16898e0eee19, []int{2} +} +func (m *Source) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Source.Unmarshal(m, b) +} +func (m *Source) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Source.Marshal(b, m, deterministic) +} +func (dst *Source) XXX_Merge(src proto.Message) { + xxx_messageInfo_Source.Merge(dst, src) +} +func (m *Source) XXX_Size() int { + return xxx_messageInfo_Source.Size(m) +} +func (m *Source) XXX_DiscardUnknown() { + xxx_messageInfo_Source.DiscardUnknown(m) +} + +var xxx_messageInfo_Source proto.InternalMessageInfo + +func (m *Source) GetService() *wrappers.StringValue { + if m != nil { + return m.Service + } + return nil +} + +func (m *Source) GetNamespace() *wrappers.StringValue { + if m != nil { + return m.Namespace + } + return nil +} + +func (m *Source) GetMetadata() map[string]*MatchString { + if m != nil { + return m.Metadata + } + return nil +} + +type Destination struct { + // 被调方服务以及命名空间 + Service *wrappers.StringValue `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` + Namespace *wrappers.StringValue `protobuf:"bytes,2,opt,name=namespace,proto3" json:"namespace,omitempty"` + // 被调方服务实例标签 + // value支持正则匹配 + Metadata map[string]*MatchString `protobuf:"bytes,3,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // 根据服务名和服务实例metadata筛选符合条件的服务实例子集 + // 服务实例子集可以设置优先级和权重 + // 优先级:整型,范围[0, 9],最高优先级为0 + // 权重:整型 + // 先按优先级路由,如果存在高优先级,不会使用低优先级 + // 如果存在优先级相同的子集,再按权重分配 + // 优先级和权重可以都不设置/设置一个/设置两个 + // 如果部分设置优先级,部分没有设置,认为没有设置的优先级最低 + // 如果部分设置权重,部分没有设置,认为没有设置的权重为0 + // 如果全部没有设置权重,认为权重相同 + Priority *wrappers.UInt32Value `protobuf:"bytes,4,opt,name=priority,proto3" json:"priority,omitempty"` + Weight *wrappers.UInt32Value `protobuf:"bytes,5,opt,name=weight,proto3" json:"weight,omitempty"` + // 将请求转发到代理服务 + Transfer *wrappers.StringValue `protobuf:"bytes,6,opt,name=transfer,proto3" json:"transfer,omitempty"` + // 是否对该set执行隔离,隔离后,不会再分配流量 + Isolate *wrappers.BoolValue `protobuf:"bytes,7,opt,name=isolate,proto3" json:"isolate,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Destination) Reset() { *m = Destination{} } +func (m *Destination) String() string { return proto.CompactTextString(m) } +func (*Destination) ProtoMessage() {} +func (*Destination) Descriptor() ([]byte, []int) { + return fileDescriptor_routing_413c16898e0eee19, []int{3} +} +func (m *Destination) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Destination.Unmarshal(m, b) +} +func (m *Destination) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Destination.Marshal(b, m, deterministic) +} +func (dst *Destination) XXX_Merge(src proto.Message) { + xxx_messageInfo_Destination.Merge(dst, src) +} +func (m *Destination) XXX_Size() int { + return xxx_messageInfo_Destination.Size(m) +} +func (m *Destination) XXX_DiscardUnknown() { + xxx_messageInfo_Destination.DiscardUnknown(m) +} + +var xxx_messageInfo_Destination proto.InternalMessageInfo + +func (m *Destination) GetService() *wrappers.StringValue { + if m != nil { + return m.Service + } + return nil +} + +func (m *Destination) GetNamespace() *wrappers.StringValue { + if m != nil { + return m.Namespace + } + return nil +} + +func (m *Destination) GetMetadata() map[string]*MatchString { + if m != nil { + return m.Metadata + } + return nil +} + +func (m *Destination) GetPriority() *wrappers.UInt32Value { + if m != nil { + return m.Priority + } + return nil +} + +func (m *Destination) GetWeight() *wrappers.UInt32Value { + if m != nil { + return m.Weight + } + return nil +} + +func (m *Destination) GetTransfer() *wrappers.StringValue { + if m != nil { + return m.Transfer + } + return nil +} + +func (m *Destination) GetIsolate() *wrappers.BoolValue { + if m != nil { + return m.Isolate + } + return nil +} + +func init() { + proto.RegisterType((*Routing)(nil), "v1.Routing") + proto.RegisterType((*Route)(nil), "v1.Route") + proto.RegisterType((*Source)(nil), "v1.Source") + proto.RegisterMapType((map[string]*MatchString)(nil), "v1.Source.MetadataEntry") + proto.RegisterType((*Destination)(nil), "v1.Destination") + proto.RegisterMapType((map[string]*MatchString)(nil), "v1.Destination.MetadataEntry") +} + +func init() { proto.RegisterFile("routing.proto", fileDescriptor_routing_413c16898e0eee19) } + +var fileDescriptor_routing_413c16898e0eee19 = []byte{ + // 468 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x93, 0xc1, 0x6a, 0xdb, 0x40, + 0x10, 0x86, 0xb1, 0x15, 0x5b, 0xf6, 0xb8, 0xa6, 0x65, 0x4f, 0x8b, 0x69, 0x4b, 0x30, 0x0d, 0xcd, + 0x49, 0x21, 0xb6, 0x29, 0x69, 0x8e, 0xa1, 0x3d, 0x14, 0x9a, 0x8b, 0x42, 0x7b, 0x2d, 0x6b, 0x79, + 0xa2, 0x2c, 0x91, 0x76, 0xc5, 0xee, 0x48, 0xc1, 0xb7, 0xbe, 0x53, 0xdf, 0xaa, 0x4f, 0x51, 0xb4, + 0x5a, 0xdb, 0x71, 0x82, 0x41, 0x87, 0x42, 0x6e, 0xd2, 0xce, 0xf7, 0xcf, 0xcc, 0xfe, 0x3b, 0x03, + 0x63, 0xa3, 0x4b, 0x92, 0x2a, 0x8d, 0x0a, 0xa3, 0x49, 0xb3, 0x6e, 0x75, 0x3e, 0x79, 0x9f, 0x6a, + 0x9d, 0x66, 0x78, 0xe6, 0x4e, 0x96, 0xe5, 0xed, 0xd9, 0x83, 0x11, 0x45, 0x81, 0xc6, 0x36, 0xcc, + 0x64, 0x94, 0xeb, 0x15, 0x66, 0xcd, 0xcf, 0xf4, 0x4f, 0x00, 0x61, 0xdc, 0xa4, 0x60, 0x9f, 0x20, + 0xb4, 0x68, 0x2a, 0x99, 0x20, 0xef, 0x1c, 0x77, 0x4e, 0x47, 0xb3, 0xb7, 0x51, 0x93, 0x2a, 0xda, + 0xa4, 0x8a, 0x6e, 0xc8, 0x48, 0x95, 0xfe, 0x14, 0x59, 0x89, 0xf1, 0x06, 0x66, 0x97, 0x30, 0x54, + 0x22, 0x47, 0x5b, 0x88, 0x04, 0x79, 0xb7, 0x85, 0x72, 0x87, 0xb3, 0x13, 0x18, 0x48, 0xb5, 0xd4, + 0xa5, 0x5a, 0x59, 0x1e, 0x1c, 0x07, 0xa7, 0xa3, 0xd9, 0x30, 0xaa, 0xce, 0xa3, 0xba, 0x25, 0x8c, + 0xb7, 0x21, 0xf6, 0x11, 0x86, 0xba, 0x24, 0xcf, 0x1d, 0x3d, 0xe5, 0x76, 0x31, 0x36, 0x83, 0x5e, + 0x42, 0x32, 0x47, 0xde, 0x6b, 0xd1, 0x47, 0x83, 0xd6, 0x9a, 0xdc, 0x69, 0xfa, 0x6d, 0x34, 0x0e, + 0x65, 0x17, 0x30, 0x30, 0x58, 0x49, 0x2b, 0xb5, 0xe2, 0x61, 0x0b, 0xd9, 0x96, 0x66, 0x57, 0x30, + 0xf6, 0xc6, 0xfd, 0x22, 0x7d, 0x8f, 0x8a, 0x0f, 0x5a, 0xc8, 0xf7, 0x25, 0xd3, 0x25, 0xf4, 0xdc, + 0xcd, 0xd9, 0x07, 0x08, 0xad, 0x2e, 0x4d, 0x82, 0x96, 0x77, 0x9c, 0x2b, 0x50, 0xbb, 0x72, 0xe3, + 0x8e, 0xe2, 0x4d, 0x88, 0xcd, 0xe1, 0xd5, 0x0a, 0x2d, 0x49, 0x25, 0x48, 0x6a, 0x65, 0x79, 0xd7, + 0xa1, 0xaf, 0x6b, 0xf4, 0xcb, 0xee, 0x3c, 0xde, 0x83, 0xa6, 0xbf, 0xbb, 0xd0, 0x6f, 0x12, 0xbd, + 0xc8, 0x60, 0x2c, 0x60, 0x90, 0x23, 0x89, 0x95, 0x20, 0xe1, 0x07, 0x83, 0xef, 0xae, 0x16, 0x5d, + 0xfb, 0xd0, 0x57, 0x45, 0x66, 0x1d, 0x6f, 0xc9, 0xc9, 0x77, 0x18, 0xef, 0x85, 0xd8, 0x1b, 0x08, + 0xee, 0x71, 0xed, 0xda, 0x1e, 0xc6, 0xf5, 0x27, 0x3b, 0x81, 0x5e, 0x55, 0x17, 0xf3, 0x0d, 0x39, + 0x17, 0xae, 0x05, 0x25, 0x77, 0x4d, 0x23, 0x71, 0x13, 0xbd, 0xec, 0x5e, 0x74, 0xa6, 0x7f, 0x03, + 0x18, 0x3d, 0x32, 0xe8, 0x45, 0x7c, 0xf8, 0xfc, 0xcc, 0x87, 0x77, 0x4f, 0xde, 0xed, 0x90, 0x19, + 0xf5, 0x8c, 0x16, 0x46, 0x6a, 0x23, 0x69, 0xcd, 0x8f, 0x0e, 0x54, 0xfd, 0xf1, 0x4d, 0xd1, 0x7c, + 0xe6, 0x67, 0x74, 0x43, 0xb3, 0x05, 0xf4, 0x1f, 0x50, 0xa6, 0x77, 0x74, 0x70, 0x8d, 0x1e, 0xeb, + 0x3c, 0x5b, 0xd7, 0x23, 0x23, 0x94, 0xbd, 0x45, 0xd3, 0x6a, 0x95, 0xb6, 0x34, 0x5b, 0x40, 0x28, + 0xad, 0xce, 0x04, 0xa1, 0x5f, 0xa6, 0xc9, 0x33, 0xe1, 0x95, 0xd6, 0x99, 0xb7, 0xd5, 0xa3, 0xff, + 0xf7, 0xb1, 0x97, 0x7d, 0x57, 0x6a, 0xfe, 0x2f, 0x00, 0x00, 0xff, 0xff, 0xb3, 0x0a, 0x3d, 0x7c, + 0x52, 0x05, 0x00, 0x00, +} diff --git a/common/api/v1/routing.proto b/common/api/v1/routing.proto new file mode 100644 index 000000000..bbc730298 --- /dev/null +++ b/common/api/v1/routing.proto @@ -0,0 +1,69 @@ +syntax = "proto3"; + +package v1; + +import "google/protobuf/wrappers.proto"; +import "model.proto"; + +message Routing { + // 规则所属服务以及命名空间 + google.protobuf.StringValue service = 1; + google.protobuf.StringValue namespace = 2; + + // 每个服务可以配置多条入站或者出站规则 + // 对于每个请求,从上到下依次匹配,若命中则终止 + repeated Route inbounds = 3; + repeated Route outbounds = 4; + + google.protobuf.StringValue ctime = 5; + google.protobuf.StringValue mtime = 6; + google.protobuf.StringValue revision = 7; + + google.protobuf.StringValue service_token = 8 [json_name="service_token"]; +} + +message Route { + // 如果匹配Source规则,按照Destination路由 + // 多个Source之间的关系为或 + repeated Source sources = 1; + repeated Destination destinations = 2; +} + +message Source { + // 主调方服务以及命名空间 + google.protobuf.StringValue service = 1; + google.protobuf.StringValue namespace = 2; + + // 主调方服务实例标签或者请求标签 + // value支持正则匹配 + map metadata = 3; +} + +message Destination { + // 被调方服务以及命名空间 + google.protobuf.StringValue service = 1; + google.protobuf.StringValue namespace = 2; + + // 被调方服务实例标签 + // value支持正则匹配 + map metadata = 3; + + // 根据服务名和服务实例metadata筛选符合条件的服务实例子集 + // 服务实例子集可以设置优先级和权重 + // 优先级:整型,范围[0, 9],最高优先级为0 + // 权重:整型 + // 先按优先级路由,如果存在高优先级,不会使用低优先级 + // 如果存在优先级相同的子集,再按权重分配 + // 优先级和权重可以都不设置/设置一个/设置两个 + // 如果部分设置优先级,部分没有设置,认为没有设置的优先级最低 + // 如果部分设置权重,部分没有设置,认为没有设置的权重为0 + // 如果全部没有设置权重,认为权重相同 + google.protobuf.UInt32Value priority = 4; + google.protobuf.UInt32Value weight = 5; + + // 将请求转发到代理服务 + google.protobuf.StringValue transfer = 6; + + //是否对该set执行隔离,隔离后,不会再分配流量 + google.protobuf.BoolValue isolate = 7; +} diff --git a/common/api/v1/service.pb.go b/common/api/v1/service.pb.go new file mode 100644 index 000000000..7a94245c6 --- /dev/null +++ b/common/api/v1/service.pb.go @@ -0,0 +1,761 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: service.proto + +package v1 + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import wrappers "github.com/golang/protobuf/ptypes/wrappers" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type AliasType int32 + +const ( + AliasType_DEFAULT AliasType = 0 + AliasType_CL5SID AliasType = 1 +) + +var AliasType_name = map[int32]string{ + 0: "DEFAULT", + 1: "CL5SID", +} +var AliasType_value = map[string]int32{ + "DEFAULT": 0, + "CL5SID": 1, +} + +func (x AliasType) String() string { + return proto.EnumName(AliasType_name, int32(x)) +} +func (AliasType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_service_413f75d8eac84e7c, []int{0} +} + +type HealthCheck_HealthCheckType int32 + +const ( + HealthCheck_UNKNOWN HealthCheck_HealthCheckType = 0 + HealthCheck_HEARTBEAT HealthCheck_HealthCheckType = 1 +) + +var HealthCheck_HealthCheckType_name = map[int32]string{ + 0: "UNKNOWN", + 1: "HEARTBEAT", +} +var HealthCheck_HealthCheckType_value = map[string]int32{ + "UNKNOWN": 0, + "HEARTBEAT": 1, +} + +func (x HealthCheck_HealthCheckType) String() string { + return proto.EnumName(HealthCheck_HealthCheckType_name, int32(x)) +} +func (HealthCheck_HealthCheckType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_service_413f75d8eac84e7c, []int{4, 0} +} + +type Namespace struct { + Name *wrappers.StringValue `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Comment *wrappers.StringValue `protobuf:"bytes,2,opt,name=comment,proto3" json:"comment,omitempty"` + Owners *wrappers.StringValue `protobuf:"bytes,3,opt,name=owners,proto3" json:"owners,omitempty"` + Token *wrappers.StringValue `protobuf:"bytes,4,opt,name=token,proto3" json:"token,omitempty"` + Ctime *wrappers.StringValue `protobuf:"bytes,5,opt,name=ctime,proto3" json:"ctime,omitempty"` + Mtime *wrappers.StringValue `protobuf:"bytes,6,opt,name=mtime,proto3" json:"mtime,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Namespace) Reset() { *m = Namespace{} } +func (m *Namespace) String() string { return proto.CompactTextString(m) } +func (*Namespace) ProtoMessage() {} +func (*Namespace) Descriptor() ([]byte, []int) { + return fileDescriptor_service_413f75d8eac84e7c, []int{0} +} +func (m *Namespace) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Namespace.Unmarshal(m, b) +} +func (m *Namespace) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Namespace.Marshal(b, m, deterministic) +} +func (dst *Namespace) XXX_Merge(src proto.Message) { + xxx_messageInfo_Namespace.Merge(dst, src) +} +func (m *Namespace) XXX_Size() int { + return xxx_messageInfo_Namespace.Size(m) +} +func (m *Namespace) XXX_DiscardUnknown() { + xxx_messageInfo_Namespace.DiscardUnknown(m) +} + +var xxx_messageInfo_Namespace proto.InternalMessageInfo + +func (m *Namespace) GetName() *wrappers.StringValue { + if m != nil { + return m.Name + } + return nil +} + +func (m *Namespace) GetComment() *wrappers.StringValue { + if m != nil { + return m.Comment + } + return nil +} + +func (m *Namespace) GetOwners() *wrappers.StringValue { + if m != nil { + return m.Owners + } + return nil +} + +func (m *Namespace) GetToken() *wrappers.StringValue { + if m != nil { + return m.Token + } + return nil +} + +func (m *Namespace) GetCtime() *wrappers.StringValue { + if m != nil { + return m.Ctime + } + return nil +} + +func (m *Namespace) GetMtime() *wrappers.StringValue { + if m != nil { + return m.Mtime + } + return nil +} + +type Service struct { + Name *wrappers.StringValue `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Namespace *wrappers.StringValue `protobuf:"bytes,2,opt,name=namespace,proto3" json:"namespace,omitempty"` + Metadata map[string]string `protobuf:"bytes,3,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Ports *wrappers.StringValue `protobuf:"bytes,4,opt,name=ports,proto3" json:"ports,omitempty"` + Business *wrappers.StringValue `protobuf:"bytes,5,opt,name=business,proto3" json:"business,omitempty"` + Department *wrappers.StringValue `protobuf:"bytes,6,opt,name=department,proto3" json:"department,omitempty"` + CmdbMod1 *wrappers.StringValue `protobuf:"bytes,7,opt,name=cmdb_mod1,proto3" json:"cmdb_mod1,omitempty"` + CmdbMod2 *wrappers.StringValue `protobuf:"bytes,8,opt,name=cmdb_mod2,proto3" json:"cmdb_mod2,omitempty"` + CmdbMod3 *wrappers.StringValue `protobuf:"bytes,9,opt,name=cmdb_mod3,proto3" json:"cmdb_mod3,omitempty"` + Comment *wrappers.StringValue `protobuf:"bytes,10,opt,name=comment,proto3" json:"comment,omitempty"` + Owners *wrappers.StringValue `protobuf:"bytes,11,opt,name=owners,proto3" json:"owners,omitempty"` + Token *wrappers.StringValue `protobuf:"bytes,12,opt,name=token,proto3" json:"token,omitempty"` + Ctime *wrappers.StringValue `protobuf:"bytes,13,opt,name=ctime,proto3" json:"ctime,omitempty"` + Mtime *wrappers.StringValue `protobuf:"bytes,14,opt,name=mtime,proto3" json:"mtime,omitempty"` + Revision *wrappers.StringValue `protobuf:"bytes,15,opt,name=revision,proto3" json:"revision,omitempty"` + PlatformId *wrappers.StringValue `protobuf:"bytes,16,opt,name=platform_id,proto3" json:"platform_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Service) Reset() { *m = Service{} } +func (m *Service) String() string { return proto.CompactTextString(m) } +func (*Service) ProtoMessage() {} +func (*Service) Descriptor() ([]byte, []int) { + return fileDescriptor_service_413f75d8eac84e7c, []int{1} +} +func (m *Service) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Service.Unmarshal(m, b) +} +func (m *Service) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Service.Marshal(b, m, deterministic) +} +func (dst *Service) XXX_Merge(src proto.Message) { + xxx_messageInfo_Service.Merge(dst, src) +} +func (m *Service) XXX_Size() int { + return xxx_messageInfo_Service.Size(m) +} +func (m *Service) XXX_DiscardUnknown() { + xxx_messageInfo_Service.DiscardUnknown(m) +} + +var xxx_messageInfo_Service proto.InternalMessageInfo + +func (m *Service) GetName() *wrappers.StringValue { + if m != nil { + return m.Name + } + return nil +} + +func (m *Service) GetNamespace() *wrappers.StringValue { + if m != nil { + return m.Namespace + } + return nil +} + +func (m *Service) GetMetadata() map[string]string { + if m != nil { + return m.Metadata + } + return nil +} + +func (m *Service) GetPorts() *wrappers.StringValue { + if m != nil { + return m.Ports + } + return nil +} + +func (m *Service) GetBusiness() *wrappers.StringValue { + if m != nil { + return m.Business + } + return nil +} + +func (m *Service) GetDepartment() *wrappers.StringValue { + if m != nil { + return m.Department + } + return nil +} + +func (m *Service) GetCmdbMod1() *wrappers.StringValue { + if m != nil { + return m.CmdbMod1 + } + return nil +} + +func (m *Service) GetCmdbMod2() *wrappers.StringValue { + if m != nil { + return m.CmdbMod2 + } + return nil +} + +func (m *Service) GetCmdbMod3() *wrappers.StringValue { + if m != nil { + return m.CmdbMod3 + } + return nil +} + +func (m *Service) GetComment() *wrappers.StringValue { + if m != nil { + return m.Comment + } + return nil +} + +func (m *Service) GetOwners() *wrappers.StringValue { + if m != nil { + return m.Owners + } + return nil +} + +func (m *Service) GetToken() *wrappers.StringValue { + if m != nil { + return m.Token + } + return nil +} + +func (m *Service) GetCtime() *wrappers.StringValue { + if m != nil { + return m.Ctime + } + return nil +} + +func (m *Service) GetMtime() *wrappers.StringValue { + if m != nil { + return m.Mtime + } + return nil +} + +func (m *Service) GetRevision() *wrappers.StringValue { + if m != nil { + return m.Revision + } + return nil +} + +func (m *Service) GetPlatformId() *wrappers.StringValue { + if m != nil { + return m.PlatformId + } + return nil +} + +type ServiceAlias struct { + Service *wrappers.StringValue `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` + Namespace *wrappers.StringValue `protobuf:"bytes,2,opt,name=namespace,proto3" json:"namespace,omitempty"` + Alias *wrappers.StringValue `protobuf:"bytes,3,opt,name=alias,proto3" json:"alias,omitempty"` + Type AliasType `protobuf:"varint,4,opt,name=type,proto3,enum=v1.AliasType" json:"type,omitempty"` + Owners *wrappers.StringValue `protobuf:"bytes,8,opt,name=owners,proto3" json:"owners,omitempty"` + Comment *wrappers.StringValue `protobuf:"bytes,9,opt,name=comment,proto3" json:"comment,omitempty"` + ServiceToken *wrappers.StringValue `protobuf:"bytes,5,opt,name=service_token,proto3" json:"service_token,omitempty"` + Ctime *wrappers.StringValue `protobuf:"bytes,6,opt,name=ctime,proto3" json:"ctime,omitempty"` + Mtime *wrappers.StringValue `protobuf:"bytes,7,opt,name=mtime,proto3" json:"mtime,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ServiceAlias) Reset() { *m = ServiceAlias{} } +func (m *ServiceAlias) String() string { return proto.CompactTextString(m) } +func (*ServiceAlias) ProtoMessage() {} +func (*ServiceAlias) Descriptor() ([]byte, []int) { + return fileDescriptor_service_413f75d8eac84e7c, []int{2} +} +func (m *ServiceAlias) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ServiceAlias.Unmarshal(m, b) +} +func (m *ServiceAlias) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ServiceAlias.Marshal(b, m, deterministic) +} +func (dst *ServiceAlias) XXX_Merge(src proto.Message) { + xxx_messageInfo_ServiceAlias.Merge(dst, src) +} +func (m *ServiceAlias) XXX_Size() int { + return xxx_messageInfo_ServiceAlias.Size(m) +} +func (m *ServiceAlias) XXX_DiscardUnknown() { + xxx_messageInfo_ServiceAlias.DiscardUnknown(m) +} + +var xxx_messageInfo_ServiceAlias proto.InternalMessageInfo + +func (m *ServiceAlias) GetService() *wrappers.StringValue { + if m != nil { + return m.Service + } + return nil +} + +func (m *ServiceAlias) GetNamespace() *wrappers.StringValue { + if m != nil { + return m.Namespace + } + return nil +} + +func (m *ServiceAlias) GetAlias() *wrappers.StringValue { + if m != nil { + return m.Alias + } + return nil +} + +func (m *ServiceAlias) GetType() AliasType { + if m != nil { + return m.Type + } + return AliasType_DEFAULT +} + +func (m *ServiceAlias) GetOwners() *wrappers.StringValue { + if m != nil { + return m.Owners + } + return nil +} + +func (m *ServiceAlias) GetComment() *wrappers.StringValue { + if m != nil { + return m.Comment + } + return nil +} + +func (m *ServiceAlias) GetServiceToken() *wrappers.StringValue { + if m != nil { + return m.ServiceToken + } + return nil +} + +func (m *ServiceAlias) GetCtime() *wrappers.StringValue { + if m != nil { + return m.Ctime + } + return nil +} + +func (m *ServiceAlias) GetMtime() *wrappers.StringValue { + if m != nil { + return m.Mtime + } + return nil +} + +type Instance struct { + Id *wrappers.StringValue `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Service *wrappers.StringValue `protobuf:"bytes,2,opt,name=service,proto3" json:"service,omitempty"` + Namespace *wrappers.StringValue `protobuf:"bytes,3,opt,name=namespace,proto3" json:"namespace,omitempty"` + VpcId *wrappers.StringValue `protobuf:"bytes,21,opt,name=vpc_id,proto3" json:"vpc_id,omitempty"` + Host *wrappers.StringValue `protobuf:"bytes,4,opt,name=host,proto3" json:"host,omitempty"` + Port *wrappers.UInt32Value `protobuf:"bytes,5,opt,name=port,proto3" json:"port,omitempty"` + Protocol *wrappers.StringValue `protobuf:"bytes,6,opt,name=protocol,proto3" json:"protocol,omitempty"` + Version *wrappers.StringValue `protobuf:"bytes,7,opt,name=version,proto3" json:"version,omitempty"` + Priority *wrappers.UInt32Value `protobuf:"bytes,8,opt,name=priority,proto3" json:"priority,omitempty"` + Weight *wrappers.UInt32Value `protobuf:"bytes,9,opt,name=weight,proto3" json:"weight,omitempty"` + EnableHealthCheck *wrappers.BoolValue `protobuf:"bytes,20,opt,name=enable_health_check,json=enableHealthCheck,proto3" json:"enable_health_check,omitempty"` + HealthCheck *HealthCheck `protobuf:"bytes,10,opt,name=health_check,json=healthCheck,proto3" json:"health_check,omitempty"` + Healthy *wrappers.BoolValue `protobuf:"bytes,11,opt,name=healthy,proto3" json:"healthy,omitempty"` + Isolate *wrappers.BoolValue `protobuf:"bytes,12,opt,name=isolate,proto3" json:"isolate,omitempty"` + Location *Location `protobuf:"bytes,13,opt,name=location,proto3" json:"location,omitempty"` + Metadata map[string]string `protobuf:"bytes,14,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + LogicSet *wrappers.StringValue `protobuf:"bytes,15,opt,name=logic_set,proto3" json:"logic_set,omitempty"` + Ctime *wrappers.StringValue `protobuf:"bytes,16,opt,name=ctime,proto3" json:"ctime,omitempty"` + Mtime *wrappers.StringValue `protobuf:"bytes,17,opt,name=mtime,proto3" json:"mtime,omitempty"` + Revision *wrappers.StringValue `protobuf:"bytes,18,opt,name=revision,proto3" json:"revision,omitempty"` + ServiceToken *wrappers.StringValue `protobuf:"bytes,19,opt,name=service_token,proto3" json:"service_token,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Instance) Reset() { *m = Instance{} } +func (m *Instance) String() string { return proto.CompactTextString(m) } +func (*Instance) ProtoMessage() {} +func (*Instance) Descriptor() ([]byte, []int) { + return fileDescriptor_service_413f75d8eac84e7c, []int{3} +} +func (m *Instance) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Instance.Unmarshal(m, b) +} +func (m *Instance) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Instance.Marshal(b, m, deterministic) +} +func (dst *Instance) XXX_Merge(src proto.Message) { + xxx_messageInfo_Instance.Merge(dst, src) +} +func (m *Instance) XXX_Size() int { + return xxx_messageInfo_Instance.Size(m) +} +func (m *Instance) XXX_DiscardUnknown() { + xxx_messageInfo_Instance.DiscardUnknown(m) +} + +var xxx_messageInfo_Instance proto.InternalMessageInfo + +func (m *Instance) GetId() *wrappers.StringValue { + if m != nil { + return m.Id + } + return nil +} + +func (m *Instance) GetService() *wrappers.StringValue { + if m != nil { + return m.Service + } + return nil +} + +func (m *Instance) GetNamespace() *wrappers.StringValue { + if m != nil { + return m.Namespace + } + return nil +} + +func (m *Instance) GetVpcId() *wrappers.StringValue { + if m != nil { + return m.VpcId + } + return nil +} + +func (m *Instance) GetHost() *wrappers.StringValue { + if m != nil { + return m.Host + } + return nil +} + +func (m *Instance) GetPort() *wrappers.UInt32Value { + if m != nil { + return m.Port + } + return nil +} + +func (m *Instance) GetProtocol() *wrappers.StringValue { + if m != nil { + return m.Protocol + } + return nil +} + +func (m *Instance) GetVersion() *wrappers.StringValue { + if m != nil { + return m.Version + } + return nil +} + +func (m *Instance) GetPriority() *wrappers.UInt32Value { + if m != nil { + return m.Priority + } + return nil +} + +func (m *Instance) GetWeight() *wrappers.UInt32Value { + if m != nil { + return m.Weight + } + return nil +} + +func (m *Instance) GetEnableHealthCheck() *wrappers.BoolValue { + if m != nil { + return m.EnableHealthCheck + } + return nil +} + +func (m *Instance) GetHealthCheck() *HealthCheck { + if m != nil { + return m.HealthCheck + } + return nil +} + +func (m *Instance) GetHealthy() *wrappers.BoolValue { + if m != nil { + return m.Healthy + } + return nil +} + +func (m *Instance) GetIsolate() *wrappers.BoolValue { + if m != nil { + return m.Isolate + } + return nil +} + +func (m *Instance) GetLocation() *Location { + if m != nil { + return m.Location + } + return nil +} + +func (m *Instance) GetMetadata() map[string]string { + if m != nil { + return m.Metadata + } + return nil +} + +func (m *Instance) GetLogicSet() *wrappers.StringValue { + if m != nil { + return m.LogicSet + } + return nil +} + +func (m *Instance) GetCtime() *wrappers.StringValue { + if m != nil { + return m.Ctime + } + return nil +} + +func (m *Instance) GetMtime() *wrappers.StringValue { + if m != nil { + return m.Mtime + } + return nil +} + +func (m *Instance) GetRevision() *wrappers.StringValue { + if m != nil { + return m.Revision + } + return nil +} + +func (m *Instance) GetServiceToken() *wrappers.StringValue { + if m != nil { + return m.ServiceToken + } + return nil +} + +type HealthCheck struct { + Type HealthCheck_HealthCheckType `protobuf:"varint,1,opt,name=type,proto3,enum=v1.HealthCheck_HealthCheckType" json:"type,omitempty"` + Heartbeat *HeartbeatHealthCheck `protobuf:"bytes,2,opt,name=heartbeat,proto3" json:"heartbeat,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *HealthCheck) Reset() { *m = HealthCheck{} } +func (m *HealthCheck) String() string { return proto.CompactTextString(m) } +func (*HealthCheck) ProtoMessage() {} +func (*HealthCheck) Descriptor() ([]byte, []int) { + return fileDescriptor_service_413f75d8eac84e7c, []int{4} +} +func (m *HealthCheck) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_HealthCheck.Unmarshal(m, b) +} +func (m *HealthCheck) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_HealthCheck.Marshal(b, m, deterministic) +} +func (dst *HealthCheck) XXX_Merge(src proto.Message) { + xxx_messageInfo_HealthCheck.Merge(dst, src) +} +func (m *HealthCheck) XXX_Size() int { + return xxx_messageInfo_HealthCheck.Size(m) +} +func (m *HealthCheck) XXX_DiscardUnknown() { + xxx_messageInfo_HealthCheck.DiscardUnknown(m) +} + +var xxx_messageInfo_HealthCheck proto.InternalMessageInfo + +func (m *HealthCheck) GetType() HealthCheck_HealthCheckType { + if m != nil { + return m.Type + } + return HealthCheck_UNKNOWN +} + +func (m *HealthCheck) GetHeartbeat() *HeartbeatHealthCheck { + if m != nil { + return m.Heartbeat + } + return nil +} + +type HeartbeatHealthCheck struct { + Ttl *wrappers.UInt32Value `protobuf:"bytes,1,opt,name=ttl,proto3" json:"ttl,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *HeartbeatHealthCheck) Reset() { *m = HeartbeatHealthCheck{} } +func (m *HeartbeatHealthCheck) String() string { return proto.CompactTextString(m) } +func (*HeartbeatHealthCheck) ProtoMessage() {} +func (*HeartbeatHealthCheck) Descriptor() ([]byte, []int) { + return fileDescriptor_service_413f75d8eac84e7c, []int{5} +} +func (m *HeartbeatHealthCheck) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_HeartbeatHealthCheck.Unmarshal(m, b) +} +func (m *HeartbeatHealthCheck) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_HeartbeatHealthCheck.Marshal(b, m, deterministic) +} +func (dst *HeartbeatHealthCheck) XXX_Merge(src proto.Message) { + xxx_messageInfo_HeartbeatHealthCheck.Merge(dst, src) +} +func (m *HeartbeatHealthCheck) XXX_Size() int { + return xxx_messageInfo_HeartbeatHealthCheck.Size(m) +} +func (m *HeartbeatHealthCheck) XXX_DiscardUnknown() { + xxx_messageInfo_HeartbeatHealthCheck.DiscardUnknown(m) +} + +var xxx_messageInfo_HeartbeatHealthCheck proto.InternalMessageInfo + +func (m *HeartbeatHealthCheck) GetTtl() *wrappers.UInt32Value { + if m != nil { + return m.Ttl + } + return nil +} + +func init() { + proto.RegisterType((*Namespace)(nil), "v1.Namespace") + proto.RegisterType((*Service)(nil), "v1.Service") + proto.RegisterMapType((map[string]string)(nil), "v1.Service.MetadataEntry") + proto.RegisterType((*ServiceAlias)(nil), "v1.ServiceAlias") + proto.RegisterType((*Instance)(nil), "v1.Instance") + proto.RegisterMapType((map[string]string)(nil), "v1.Instance.MetadataEntry") + proto.RegisterType((*HealthCheck)(nil), "v1.HealthCheck") + proto.RegisterType((*HeartbeatHealthCheck)(nil), "v1.HeartbeatHealthCheck") + proto.RegisterEnum("v1.AliasType", AliasType_name, AliasType_value) + proto.RegisterEnum("v1.HealthCheck_HealthCheckType", HealthCheck_HealthCheckType_name, HealthCheck_HealthCheckType_value) +} + +func init() { proto.RegisterFile("service.proto", fileDescriptor_service_413f75d8eac84e7c) } + +var fileDescriptor_service_413f75d8eac84e7c = []byte{ + // 892 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x56, 0xd1, 0x6e, 0xdb, 0x36, + 0x14, 0xad, 0xed, 0xc4, 0xb6, 0xae, 0xec, 0xc4, 0x65, 0x33, 0x40, 0x33, 0x86, 0xad, 0x33, 0xf6, + 0x10, 0x0c, 0x9b, 0xba, 0xc8, 0x69, 0x50, 0x64, 0xc3, 0x00, 0xa7, 0x75, 0xd1, 0x6c, 0x99, 0x07, + 0x28, 0xce, 0xf6, 0x68, 0xd0, 0x12, 0x6b, 0x0b, 0x91, 0x44, 0x41, 0x62, 0x1c, 0xf8, 0x93, 0xf6, + 0x25, 0x7b, 0xdc, 0xd3, 0xbe, 0x64, 0x3f, 0x30, 0x90, 0x94, 0x64, 0xa6, 0xad, 0x0b, 0x5a, 0x7d, + 0xb3, 0xa4, 0x73, 0xae, 0xe4, 0x7b, 0xcf, 0xb9, 0x87, 0xd0, 0xcd, 0x48, 0xba, 0x0a, 0x3c, 0x62, + 0x27, 0x29, 0x65, 0x14, 0xd5, 0x57, 0x27, 0xfd, 0x2f, 0x17, 0x94, 0x2e, 0x42, 0xf2, 0x4c, 0xdc, + 0x99, 0xdf, 0xbd, 0x7d, 0x76, 0x9f, 0xe2, 0x24, 0x21, 0x69, 0x26, 0x31, 0x7d, 0x33, 0xa2, 0x3e, + 0x09, 0xe5, 0xc5, 0xe0, 0xef, 0x3a, 0x18, 0x13, 0x1c, 0x91, 0x2c, 0xc1, 0x1e, 0x41, 0x3f, 0xc0, + 0x5e, 0x8c, 0x23, 0x62, 0xd5, 0x9e, 0xd6, 0x8e, 0x4d, 0xe7, 0x0b, 0x5b, 0x56, 0xb2, 0x8b, 0x4a, + 0xf6, 0x35, 0x4b, 0x83, 0x78, 0xf1, 0x07, 0x0e, 0xef, 0x88, 0x2b, 0x90, 0xe8, 0x0c, 0x5a, 0x1e, + 0x8d, 0x22, 0x12, 0x33, 0xab, 0xae, 0x41, 0x2a, 0xc0, 0xe8, 0x14, 0x9a, 0xf4, 0x3e, 0x26, 0x69, + 0x66, 0x35, 0x34, 0x68, 0x39, 0x16, 0x39, 0xb0, 0xcf, 0xe8, 0x2d, 0x89, 0xad, 0x3d, 0x0d, 0x92, + 0x84, 0x72, 0x8e, 0xc7, 0x82, 0x88, 0x58, 0xfb, 0x3a, 0x1c, 0x01, 0xe5, 0x9c, 0x48, 0x70, 0x9a, + 0x3a, 0x1c, 0x01, 0x1d, 0xfc, 0xd3, 0x82, 0xd6, 0xb5, 0x1c, 0x46, 0x85, 0x3e, 0x9e, 0x83, 0x11, + 0x17, 0x63, 0xd0, 0xea, 0xe4, 0x06, 0x8e, 0x9e, 0x43, 0x3b, 0x22, 0x0c, 0xfb, 0x98, 0x61, 0xab, + 0xf1, 0xb4, 0x71, 0x6c, 0x3a, 0x9f, 0xdb, 0xab, 0x13, 0x3b, 0xff, 0x18, 0xfb, 0xb7, 0xfc, 0xd9, + 0x38, 0x66, 0xe9, 0xda, 0x2d, 0xa1, 0xfc, 0x4f, 0x26, 0x34, 0x65, 0x99, 0x5e, 0x33, 0x05, 0x14, + 0xbd, 0x80, 0xf6, 0xfc, 0x2e, 0x0b, 0x62, 0x92, 0x65, 0x5a, 0xfd, 0x2c, 0xd1, 0xe8, 0x27, 0x00, + 0x9f, 0x24, 0x38, 0x65, 0x42, 0x2b, 0x3a, 0x7d, 0x55, 0xf0, 0xbc, 0x3d, 0x5e, 0xe4, 0xcf, 0x67, + 0x11, 0xf5, 0x4f, 0xac, 0x96, 0x4e, 0x7b, 0x4a, 0xb8, 0xca, 0x75, 0xac, 0xf6, 0x2e, 0x5c, 0x47, + 0xe5, 0x0e, 0x2d, 0x63, 0x17, 0xee, 0x50, 0xb5, 0x06, 0x54, 0xb3, 0x86, 0x59, 0xc5, 0x1a, 0x9d, + 0x0a, 0xd6, 0xe8, 0x56, 0xb0, 0xc6, 0x81, 0xb6, 0x35, 0xb8, 0x6a, 0x52, 0xb2, 0x0a, 0xb2, 0x80, + 0xc6, 0xd6, 0xa1, 0x8e, 0x6a, 0x0a, 0x34, 0xfa, 0x19, 0xcc, 0x24, 0xc4, 0xec, 0x2d, 0x4d, 0xa3, + 0x59, 0xe0, 0x5b, 0x3d, 0x0d, 0xb2, 0x4a, 0xe8, 0xff, 0x08, 0xdd, 0x07, 0xf2, 0x47, 0x3d, 0x68, + 0xdc, 0x92, 0xb5, 0x30, 0xa6, 0xe1, 0xf2, 0x9f, 0xe8, 0x08, 0xf6, 0x57, 0x9c, 0x28, 0x5c, 0x67, + 0xb8, 0xf2, 0xe2, 0xbc, 0xfe, 0xa2, 0x36, 0xf8, 0xaf, 0x01, 0x9d, 0xdc, 0x44, 0xa3, 0x30, 0xc0, + 0x19, 0x9f, 0x68, 0xbe, 0x6e, 0xb5, 0x9c, 0x5d, 0x80, 0x3f, 0xc9, 0xdc, 0x0e, 0xec, 0x63, 0xfe, + 0x72, 0xad, 0x3d, 0x29, 0xa1, 0xe8, 0x6b, 0xd8, 0x63, 0xeb, 0x84, 0x08, 0x63, 0x1f, 0x38, 0x5d, + 0xbe, 0x0c, 0xc4, 0x1f, 0x98, 0xae, 0x13, 0xe2, 0x8a, 0x47, 0x8a, 0xc8, 0xda, 0x3b, 0x88, 0x4c, + 0x91, 0xb4, 0xb1, 0x8b, 0xa4, 0x2f, 0xca, 0x9c, 0x9a, 0x49, 0x91, 0xea, 0xec, 0x8e, 0x87, 0x94, + 0x8d, 0x58, 0x9b, 0x15, 0xc4, 0xda, 0xd2, 0xdf, 0xe3, 0xff, 0x1a, 0xd0, 0xbe, 0x8c, 0x33, 0x86, + 0x63, 0x8f, 0xa0, 0xef, 0xa0, 0x1e, 0xf8, 0x5a, 0xc3, 0xae, 0x07, 0xbe, 0xaa, 0x8f, 0x7a, 0x65, + 0x7d, 0x34, 0x76, 0xd3, 0xc7, 0x29, 0x34, 0x57, 0x89, 0xc7, 0xcd, 0xf1, 0x99, 0xce, 0x20, 0x25, + 0x96, 0x07, 0xd4, 0x92, 0x66, 0x4c, 0x6b, 0xf5, 0x0b, 0x24, 0x67, 0xf0, 0x08, 0xd8, 0x3a, 0xb9, + 0x9b, 0xcb, 0x98, 0x0d, 0x9d, 0x9c, 0xc1, 0x91, 0xdc, 0xf5, 0xe2, 0xa9, 0x47, 0x43, 0xad, 0x99, + 0x95, 0x68, 0xde, 0xc7, 0x15, 0x49, 0xc5, 0xba, 0xd0, 0x19, 0x5c, 0x01, 0x96, 0x6f, 0x0c, 0x68, + 0x1a, 0xb0, 0xf5, 0x56, 0x59, 0xab, 0xdf, 0x59, 0xa2, 0x79, 0x17, 0xef, 0x49, 0xb0, 0x58, 0x6e, + 0xd7, 0xb5, 0xca, 0xcb, 0xb1, 0xe8, 0x17, 0x78, 0x42, 0x62, 0x3c, 0x0f, 0xc9, 0x6c, 0x49, 0x70, + 0xc8, 0x96, 0x33, 0x6f, 0x49, 0xbc, 0x5b, 0xeb, 0x48, 0x94, 0xe8, 0xbf, 0x57, 0xe2, 0x82, 0xd2, + 0x50, 0x16, 0x78, 0x2c, 0x69, 0x6f, 0x04, 0xeb, 0x25, 0x27, 0x21, 0x07, 0x3a, 0x0f, 0x8a, 0xc8, + 0xc8, 0x38, 0xe4, 0xde, 0x55, 0x60, 0xae, 0xb9, 0x54, 0x38, 0xa7, 0xd0, 0x92, 0x97, 0xeb, 0x3c, + 0x2a, 0x3e, 0xf6, 0xce, 0x02, 0xca, 0x59, 0x41, 0x46, 0x43, 0xcc, 0x48, 0x9e, 0x15, 0x1f, 0x65, + 0xe5, 0x50, 0x74, 0x0c, 0xed, 0x90, 0x7a, 0x98, 0xf1, 0xa1, 0xc8, 0xb8, 0xe8, 0xf0, 0x6f, 0xbb, + 0xca, 0xef, 0xb9, 0xe5, 0x53, 0x74, 0xa6, 0x1c, 0x47, 0x0e, 0xc4, 0x71, 0xa4, 0xcf, 0x91, 0x85, + 0xa7, 0xb6, 0x9e, 0x47, 0xce, 0xc1, 0x08, 0xe9, 0x22, 0xf0, 0x66, 0x19, 0x61, 0x5a, 0x31, 0xb1, + 0x81, 0x6f, 0x96, 0x43, 0xaf, 0xc2, 0x72, 0x78, 0x5c, 0x2d, 0xc9, 0xd0, 0x4e, 0x49, 0xf6, 0xde, + 0x0a, 0x7c, 0xb2, 0xf3, 0x0a, 0xfc, 0xb4, 0x34, 0xfb, 0xab, 0x06, 0xa6, 0x2a, 0xb8, 0x61, 0x1e, + 0x12, 0x35, 0x11, 0x12, 0x5f, 0xbd, 0x23, 0x34, 0xf5, 0xb7, 0x12, 0x1b, 0x67, 0x60, 0x2c, 0x09, + 0x4e, 0xd9, 0x9c, 0xe0, 0xe2, 0xc0, 0x6f, 0xe5, 0x4c, 0x79, 0x53, 0xd5, 0xea, 0x06, 0x3a, 0xf8, + 0x1e, 0x0e, 0xdf, 0x29, 0x88, 0x4c, 0x68, 0xdd, 0x4c, 0x7e, 0x9d, 0xfc, 0xfe, 0xe7, 0xa4, 0xf7, + 0x08, 0x75, 0xc1, 0x78, 0x33, 0x1e, 0xb9, 0xd3, 0x8b, 0xf1, 0x68, 0xda, 0xab, 0x0d, 0x5e, 0xc3, + 0xd1, 0x87, 0x2a, 0x22, 0x1b, 0x1a, 0x8c, 0x85, 0x5b, 0xf7, 0xb1, 0xea, 0x51, 0x0e, 0xfc, 0xf6, + 0x1b, 0x30, 0xca, 0xe0, 0xe3, 0x2f, 0x7c, 0x35, 0x7e, 0x3d, 0xba, 0xb9, 0x9a, 0xf6, 0x1e, 0x21, + 0x80, 0xe6, 0xcb, 0xab, 0xe7, 0xd7, 0x97, 0xaf, 0x7a, 0xb5, 0x79, 0x53, 0x14, 0x18, 0xfe, 0x1f, + 0x00, 0x00, 0xff, 0xff, 0xc0, 0x18, 0xe5, 0x5a, 0x4c, 0x0d, 0x00, 0x00, +} diff --git a/common/api/v1/service.proto b/common/api/v1/service.proto new file mode 100644 index 000000000..fabecede2 --- /dev/null +++ b/common/api/v1/service.proto @@ -0,0 +1,97 @@ +syntax = "proto3"; + +package v1; + +import "google/protobuf/wrappers.proto"; +import "model.proto"; + +message Namespace { + google.protobuf.StringValue name = 1; + google.protobuf.StringValue comment = 2; + google.protobuf.StringValue owners = 3; + google.protobuf.StringValue token = 4; + google.protobuf.StringValue ctime = 5; + google.protobuf.StringValue mtime = 6; +} + +message Service { + google.protobuf.StringValue name = 1; + google.protobuf.StringValue namespace = 2; + + map metadata = 3; + + google.protobuf.StringValue ports = 4; + google.protobuf.StringValue business = 5; + google.protobuf.StringValue department = 6; + google.protobuf.StringValue cmdb_mod1 = 7 [json_name="cmdb_mod1"]; + google.protobuf.StringValue cmdb_mod2 = 8 [json_name="cmdb_mod2"]; + google.protobuf.StringValue cmdb_mod3 = 9 [json_name="cmdb_mod3"]; + google.protobuf.StringValue comment = 10; + google.protobuf.StringValue owners = 11; + google.protobuf.StringValue token = 12; + + google.protobuf.StringValue ctime = 13; + google.protobuf.StringValue mtime = 14; + google.protobuf.StringValue revision = 15; + google.protobuf.StringValue platform_id = 16 [json_name="platform_id"]; +} + +enum AliasType { + DEFAULT = 0; + CL5SID = 1; +} + +message ServiceAlias { + google.protobuf.StringValue service = 1; + google.protobuf.StringValue namespace = 2; + google.protobuf.StringValue alias = 3; + AliasType type = 4; + google.protobuf.StringValue owners = 8; + google.protobuf.StringValue comment = 9; + google.protobuf.StringValue service_token = 5 [json_name="service_token"]; + + google.protobuf.StringValue ctime = 6; + google.protobuf.StringValue mtime = 7; +} + +message Instance { + google.protobuf.StringValue id = 1; + google.protobuf.StringValue service = 2; + google.protobuf.StringValue namespace = 3; + google.protobuf.StringValue vpc_id = 21 [json_name="vpc_id"]; + google.protobuf.StringValue host = 4; + google.protobuf.UInt32Value port = 5; + google.protobuf.StringValue protocol = 6; + google.protobuf.StringValue version = 7; + google.protobuf.UInt32Value priority = 8; + google.protobuf.UInt32Value weight = 9; + google.protobuf.BoolValue enable_health_check = 20; + HealthCheck health_check = 10; + google.protobuf.BoolValue healthy = 11; + google.protobuf.BoolValue isolate = 12; + Location location = 13; + + map metadata = 14; + google.protobuf.StringValue logic_set = 15 [json_name="logic_set"]; + + google.protobuf.StringValue ctime = 16; + google.protobuf.StringValue mtime = 17; + google.protobuf.StringValue revision = 18; + + google.protobuf.StringValue service_token = 19 [json_name="service_token"]; +} + +message HealthCheck { + enum HealthCheckType { + UNKNOWN = 0; + HEARTBEAT = 1; + } + + HealthCheckType type = 1; + + HeartbeatHealthCheck heartbeat = 2; +} + +message HeartbeatHealthCheck { + google.protobuf.UInt32Value ttl = 1; +} diff --git a/common/ckv/ckv_test.go b/common/ckv/ckv_test.go new file mode 100644 index 000000000..2b3463afd --- /dev/null +++ b/common/ckv/ckv_test.go @@ -0,0 +1,114 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package ckv + +import ( + "fmt" + "math/rand" + "os" + "runtime" + "sync" + "sync/atomic" + "testing" + "time" +) + +// 压测并发无锁加操作 +func BenchmarkAdd(t *testing.B) { + var a uint32 + + //共启动10000 * cpu数量个协程 + t.SetParallelism(10000) + t.RunParallel(func(pb *testing.PB) { + for pb.Next() { + a++ + } + }) +} + +// 压测并发原子加操作 +func BenchmarkAtomicAdd(t *testing.B) { + var a uint32 + + //共启动10000 * cpu数量个协程 + t.SetParallelism(10000) + t.RunParallel(func(pb *testing.PB) { + for pb.Next() { + b := atomic.AddUint32(&a, 1) + _ = b % 500 + } + }) +} + +// 压测并发有锁加操作 +func BenchmarkMutexAdd(t *testing.B) { + var a uint32 + var mu sync.Mutex + + //共启动10000 * cpu数量个协程 + t.SetParallelism(10000) + t.RunParallel(func(pb *testing.PB) { + for pb.Next() { + mu.Lock() + a++ + mu.Unlock() + } + }) +} + +// 压测并发生成随机数操作 +func BenchmarkRand(t *testing.B) { + rand.Seed(time.Now().Unix()) + + //共启动10000 * cpu数量个协程 + t.SetParallelism(10000) + t.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = rand.Intn(500) + } + }) +} + +// 压测直接加操作 +func BenchmarkTestAdd(t *testing.B) { + var a = ^uint32(0) + t.Log(a) + a++ + t.Log(a) +} + +// 测试os.Exit() +func TestExit(t *testing.T) { + go func() { + os.Exit(1) + }() + + time.Sleep(time.Second) + fmt.Println("a") +} + +// 测试runtime.Goexit() +func TestGoExit(t *testing.T) { + go func() { + defer fmt.Println("aaa") + runtime.Goexit() + }() + + time.Sleep(time.Second) + fmt.Println("a") +} diff --git a/common/ckv/ckvpool.go b/common/ckv/ckvpool.go new file mode 100644 index 000000000..6aa3dfaad --- /dev/null +++ b/common/ckv/ckvpool.go @@ -0,0 +1,357 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package ckv + +import ( + "errors" + "fmt" + "math/rand" + "strconv" + "sync" + "sync/atomic" + "time" + + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" +) + +/** +* 健康检查功能使用ckv+缓存实例健康状态 +* 实现一个无锁连接池供健康检查功能使用 +* +* 层次结构:连接池 -> ckv+节点 -> 连接 +* 每个ckv+节点建立n个(n:可配置)连接 +* 每个连接绑定一个chan,chan用于分发请求到此连接 +* 负载均衡方式:轮询 +* +* 当ckv+节点发生变动,连接池支持动态增删,业务无感知 + */ + +const ( + Get = 0 + Set = 1 + Del = 2 +) + +/** + * @brief ckv任务请求结构体 + */ +type Task struct { + taskType int + id string + status int + beatTime int64 + respCh chan *Resp +} + +/** + * @brief ckv任务结果 + */ +type Resp struct { + Value string + Err error +} + +/** + * @brief ckv连接池元数据 + */ +type MetaData struct { + insConnNum int + kvPasswd string + localHost string +} + +/** + * @brief ckv节点结构体 + */ +type Node struct { + // 节点在连接池中的序号 + index int + addr string + conns []*Conn + stopCh chan bool + changeCh chan bool +} + +/** + * @brief ckv连接池结构体 + */ +type Pool struct { + mu sync.Mutex + meta *MetaData + chs []chan *Task + nodes []*Node + connSize uint32 + index uint32 +} + +/** + * @brief 初始化一个ckv节点实例 + */ +func newNode(index, connNum int, kvPasswd string, ins *model.Instance) *Node { + node := &Node{ + index: index, + addr: ins.Host() + ":" + strconv.Itoa(int(ins.Port())), + stopCh: make(chan bool), + changeCh: make(chan bool), + } + log.Infof("[ckv] instance:%s connect, conn num:%d", node.addr, connNum) + + // 计算连接的序号,即连接要与序号为index的chan绑定 + connIndexStart := index * connNum + connIndexEnd := (index + 1) * connNum + for i := connIndexStart; i < connIndexEnd; i++ { + conn, err := newConn(i, node.addr, kvPasswd) + if err != nil { + log.Errorf("[ckv] instance:%s connect failed:%s", node.addr, err) + return nil + } + node.conns = append(node.conns, conn) + } + return node +} + +/** + * @brief 初始化一个ckv连接池实例 + */ +func NewPool(insConnNum int, kvPasswd, localHost string, kvInstances []*model.Instance) (*Pool, error) { + kvPool := &Pool{ + meta: &MetaData{ + insConnNum: insConnNum, + kvPasswd: kvPasswd, + localHost: localHost, + }, + connSize: uint32(len(kvInstances) * insConnNum), + } + err := kvPool.connect(kvInstances) + if err != nil { + log.Errorf("[kv] connect kv err:%s", err) + return nil, err + } + + rand.Seed(time.Now().Unix()) + // 从一个随机位置开始,防止所有server都从一个ckv开始 + kvPool.index = uint32(rand.Intn(int(kvPool.connSize))) + return kvPool, nil +} + +/** + * @brief 启动ckv连接池工作 + */ +func (p *Pool) Start() { + p.mu.Lock() + for _, node := range p.nodes { + for _, conn := range node.conns { + go p.worker(conn, node.changeCh, node.stopCh) + } + } + p.mu.Unlock() + log.Infof("[ckv] ckv pool start") +} + +/** + * @brief 更新ckv连接池中的节点 + * 重新建立ckv连接 + * 对业务无影响 + */ +func (p *Pool) Update(newKvInstances []*model.Instance) error { + p.mu.Lock() + defer p.mu.Unlock() + + change := len(newKvInstances) - len(p.nodes) + log.Infof("[ckv] update, old ins num:%d, new ins num:%d, change:%d", len(p.nodes), len(newKvInstances), change) + newConnSize := len(newKvInstances) * p.meta.insConnNum + oldConnSize := p.connSize + log.Infof("[ckv] update, old conn num:%d, new conn num:%d", oldConnSize, newConnSize) + + if change > 0 { + // ckv+节点数如果增多,则新增对应数量的chan + for i := 0; i < change*p.meta.insConnNum; i++ { + p.chs = append(p.chs, make(chan *Task, 100)) + } + } else if change < 0 { + // ckv+节点数如果减少,修改最大连接数(chan数),防止新的请求进入需要关闭的连接(chan) + // 等待chan中的积留请求被处理完再关闭 + p.connSize = uint32(newConnSize) + } + + var newNodes []*Node + for index, ins := range newKvInstances { + // 建立新连接 + node := newNode(index, p.meta.insConnNum, p.meta.kvPasswd, ins) + if node == nil { + return errors.New("create ckv node failed") + } + newNodes = append(newNodes, node) + + // 关闭原先的连接,绑定chan到新连接 + if index < len(p.nodes) { + close(p.nodes[index].changeCh) + } + for _, conn := range node.conns { + go p.worker(conn, node.changeCh, node.stopCh) + } + } + + if change > 0 { + // 修改最大连接数到正确状态,启用新增的chan + p.connSize = uint32(newConnSize) + } else if change < 0 { + // 如果节点数减少,关闭不需要的连接和chan + for index := len(newKvInstances); index < len(p.nodes); index++ { + close(p.nodes[index].stopCh) + } + time.Sleep(10 * time.Millisecond) + for index := p.connSize; index < oldConnSize; index++ { + close(p.chs[index]) + } + p.chs = p.chs[:p.connSize] + } + p.nodes = newNodes + log.Infof("[ckv] update success, node num:%d, conn num:%d, chan num:%d", len(p.nodes), len(p.chs), p.connSize) + + return nil +} + +/** + * @brief 建立连接 + */ +func (p *Pool) connect(kvInstances []*model.Instance) error { + p.mu.Lock() + defer p.mu.Unlock() + for index, ins := range kvInstances { + node := newNode(index, p.meta.insConnNum, p.meta.kvPasswd, ins) + if node == nil { + return errors.New("create ckv node failed") + } + + for i := 0; i < len(node.conns); i++ { + p.chs = append(p.chs, make(chan *Task, 100)) + } + p.nodes = append(p.nodes, node) + } + return nil +} + +/** + * @brief 使用连接池,向ckv发起Get请求 + */ +func (p *Pool) Get(id string, ch chan *Resp) { // nolint + task := &Task{ + taskType: Get, + id: id, + respCh: ch, + } + + index := atomic.AddUint32(&p.index, 1) % p.connSize + p.chs[index] <- task +} + +/** + * @brief 使用连接池,向ckv发起Set请求 + */ +func (p *Pool) Set(id string, status int, beatTime int64, ch chan *Resp) { // nolint + task := &Task{ + taskType: Set, + id: id, + status: status, + beatTime: beatTime, + respCh: ch, + } + + index := atomic.AddUint32(&p.index, 1) % p.connSize + p.chs[index] <- task +} + +/** + * @brief 使用连接池,向ckv发起Del请求 + */ +func (p *Pool) Del(id string, ch chan *Resp) { // nolint + task := &Task{ + taskType: Del, + id: id, + respCh: ch, + } + + index := atomic.AddUint32(&p.index, 1) % p.connSize + p.chs[index] <- task +} + +/** + * @brief 接收任务worker + */ +func (p *Pool) worker(conn *Conn, changeCh, stopCh chan bool) { + ch := p.chs[conn.index] + for { + select { + case task := <-ch: + p.handleTask(conn, task) + case <-stopCh: + // 发现ckv+节点变少,不再需要此chan + // 处理滞留请求,关闭连接 + for task := range ch { + p.handleTask(conn, task) + } + conn.conn.Close() + log.Infof("[ckv] instance:%s chan:%d close", conn.addr, conn.index) + return + case <-changeCh: + // 发现ckv+节点变动,更新chan绑定的连接 + conn.conn.Close() + log.Infof("[ckv] instance:%s chan:%d change", conn.addr, conn.index) + return + } + } +} + +/** + * @brief 任务处理函数 + */ +func (p *Pool) handleTask(conn *Conn, task *Task) { + if task == nil { + log.Errorf("chan:%d receive nil task", conn.index) + return + } + + var resp Resp + switch task.taskType { + case Get: + value, err := conn.Get(task.id) + if err != nil { + resp.Err = err + } else { + resp.Value = value + } + task.respCh <- &resp + case Set: + value := fmt.Sprintf("%d:%d:%s", task.status, task.beatTime, p.meta.localHost) + err := conn.Set(task.id, value) + if err != nil { + resp.Err = err + } + task.respCh <- &resp + case Del: + err := conn.Del(task.id) + if err != nil { + resp.Err = err + } + task.respCh <- &resp + default: + log.Errorf("[ckv] set key:%s type:%d wrong", task.id, task.taskType) + } +} diff --git a/common/ckv/conn.go b/common/ckv/conn.go new file mode 100644 index 000000000..1cd244d21 --- /dev/null +++ b/common/ckv/conn.go @@ -0,0 +1,74 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package ckv + +import ( + "github.com/gomodule/redigo/redis" +) + +/** + * @brief ckv连接结构体 + */ +type Conn struct { + // conn在连接池的序号 + index int + addr string + conn redis.Conn +} + +/** + * @brief 新建ckv连接 + */ +func newConn(index int, addr, passwd string) (*Conn, error) { + c, err := redis.Dial("tcp", addr, redis.DialPassword(passwd)) + if err != nil { + return nil, err + } + + return &Conn{index, addr, c}, nil +} + +/** + * @brief 返回ckv地址 + */ +func (c *Conn) Addr() string { + return c.addr +} + +/** + * @brief Get请求 + */ +func (c *Conn) Get(key string) (string, error) { + return redis.String(c.conn.Do("GET", key)) +} + +/** + * @brief Set请求 + */ +func (c *Conn) Set(key, value string) (err error) { + _, err = c.conn.Do("SET", key, value) + return +} + +/** + * @brief Del请求 + */ +func (c *Conn) Del(key string) (err error) { + _, err = c.conn.Do("DEL", key) + return +} diff --git a/common/connlimit/api.go b/common/connlimit/api.go new file mode 100644 index 000000000..45fc00c07 --- /dev/null +++ b/common/connlimit/api.go @@ -0,0 +1,65 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package connlimit + +import ( + "errors" + "github.com/polarismesh/polaris-server/common/log" + "sync" +) + +var ( + // 全局对象索引 + connLimitObj = make(map[string]*Listener) + // 全局对象锁 + connLimitMu = new(sync.Mutex) +) + +// 获取当前的listener +func GetLimitListener(protocol string) *Listener { + connLimitMu.Lock() + defer connLimitMu.Unlock() + obj, ok := connLimitObj[protocol] + if !ok { + return nil + } + + return obj +} + +// 设置当前的listener +// 注意:Listener.protocol不能重复 +func SetLimitListener(lis *Listener) error { + connLimitMu.Lock() + defer connLimitMu.Unlock() + + if _, ok := connLimitObj[lis.protocol]; ok { + log.Errorf("[ConnLimit] protocol(%s) is existed", lis.protocol) + return errors.New("protocol is existed") + } + + connLimitObj[lis.protocol] = lis + return nil +} + +// 清理对应协议的链接计数 +func RemoteLimitListener(protocol string) { + connLimitMu.Lock() + defer connLimitMu.Unlock() + delete(connLimitObj, protocol) +} diff --git a/common/connlimit/config.go b/common/connlimit/config.go new file mode 100644 index 000000000..18241fc13 --- /dev/null +++ b/common/connlimit/config.go @@ -0,0 +1,74 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package connlimit + +import ( + "github.com/polarismesh/polaris-server/common/log" + "github.com/mitchellh/mapstructure" + "time" +) + +// 连接限制配置 +type Config struct { + // 开启连接限制 + OpenConnLimit bool `mapstructure:"openConnLimit"` + + // 单个host最大的连接数,必须 > 1 + MaxConnPerHost int `mapstructure:"maxConnPerHost"` + + // 当前协议监听端口的最大连接数 + // 兼容老版本,> 1,则开启listen的全局限制;< 1则不开启listen的全局限制 + MaxConnLimit int `mapstructure:"maxConnLimit"` + + // 白名单,不进行host连接数限制 + WhiteList string `mapstructure:"whiteList"` + + // 读超时 + ReadTimeout time.Duration `mapstructure:"readTimeout"` + + // 回收连接统计数据的周期 + PurgeCounterInterval time.Duration `mapstructure:"purgeCounterInterval"` + + // 回收连接的最大超时时间 + PurgeCounterExpire time.Duration `mapstructure:"purgeCounterExpire"` +} + +// 解析配置 +func ParseConnLimitConfig(raw map[interface{}]interface{}) (*Config, error) { + if raw == nil { + return nil, nil + } + + var config Config + decodeConfig := &mapstructure.DecoderConfig{ + DecodeHook: mapstructure.StringToTimeDurationHookFunc(), + Result: &config, + } + decoder, err := mapstructure.NewDecoder(decodeConfig) + if err != nil { + log.Errorf("conn limit new decoder err: %s", err.Error()) + return nil, err + } + + if err := decoder.Decode(raw); err != nil { + log.Errorf("parse conn limit config(%+v) err: %s", raw, err.Error()) + return nil, err + } + + return &config, nil +} diff --git a/common/connlimit/config_test.go b/common/connlimit/config_test.go new file mode 100644 index 000000000..984218ca3 --- /dev/null +++ b/common/connlimit/config_test.go @@ -0,0 +1,44 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package connlimit + +import ( + . "github.com/smartystreets/goconvey/convey" + "testing" + "time" +) + +// 可以正常解析配置测试 +func TestParseConnLimitConfig(t *testing.T) { + Convey("可以正常解析配置", t, func() { + options := map[interface{}]interface{}{ + "openConnLimit": true, + "maxConnPerHost": 16, + "maxConnLimit": 128, + "whiteList": "127.0.0.1,127.0.0.2,127.0.0.3", + "readTimeout": "120s", + } + config, err := ParseConnLimitConfig(options) + So(err, ShouldBeNil) + So(config.OpenConnLimit, ShouldBeTrue) + So(config.MaxConnPerHost, ShouldEqual, 16) + So(config.MaxConnLimit, ShouldEqual, 128) + So(config.WhiteList, ShouldEqual, "127.0.0.1,127.0.0.2,127.0.0.3") + So(config.ReadTimeout, ShouldEqual, time.Second*120) + }) +} diff --git a/common/connlimit/conn.go b/common/connlimit/conn.go new file mode 100644 index 000000000..8f6dc6f50 --- /dev/null +++ b/common/connlimit/conn.go @@ -0,0 +1,83 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package connlimit + +import ( + "github.com/polarismesh/polaris-server/common/log" + "net" + "sync" + "time" +) + +// 包装net.Conn +// 目的:拦截Close操作,用于listener计数的Release以及activeConns的删除 +type Conn struct { + net.Conn + releaseOnce sync.Once + closed bool + host string + address string + lastAccess time.Time + listener *Listener +} + +// 包装net.Conn.Close, 用于连接计数 +func (c *Conn) Close() error { + if c.closed { + return nil + } + + err := c.Conn.Close() + c.releaseOnce.Do(func() { + // 调用监听的listener,释放计数以及activeConns + // 保证只执行一次 + c.closed = true + c.listener.release(c) + }) + return err +} + +// 封装net.Conn Read方法,处理readTimeout的场景 +func (c *Conn) Read(b []byte) (int, error) { + if c.listener.readTimeout <= 0 { + return c.Conn.Read(b) + } + + c.lastAccess = time.Now() + if err := c.Conn.SetReadDeadline(time.Now().Add(c.listener.readTimeout)); err != nil { + log.Errorf("[connLimit][%s] connection(%s) set read deadline err: %s", + c.listener.protocol, c.address, err.Error()) + } + n, err := c.Conn.Read(b) + if err == nil { + return n, nil + } + + if e, ok := err.(net.Error); ok && e.Timeout() { + if time.Now().Sub(c.lastAccess) >= c.listener.readTimeout { + log.Errorf("[connLimit][%s] read timeout(%v): %s, connection(%s) will be closed by server", + c.listener.protocol, c.listener.readTimeout, err.Error(), c.address) + } + } + return n, err +} + +// 判断conn是否还有效 +func (c *Conn) isValid() bool { + return c.closed == false +} diff --git a/common/connlimit/listener.go b/common/connlimit/listener.go new file mode 100644 index 000000000..ba68713c0 --- /dev/null +++ b/common/connlimit/listener.go @@ -0,0 +1,413 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package connlimit + +import ( + "context" + "fmt" + "github.com/polarismesh/polaris-server/common/log" + "github.com/pkg/errors" + "net" + "strings" + "sync" + "sync/atomic" + "time" +) + +const ( + // 最少连接数 + minHostConnLimit = 1 +) + +// 计数器 +// limit connections for every ip +type counter struct { + size int32 + actives map[string]*Conn // 活跃的连接 + mu sync.Mutex + lastAccess int64 +} + +// 新增计数器 +func newCounter() *counter { + return &counter{ + size: 1, + actives: make(map[string]*Conn), + mu: sync.Mutex{}, + lastAccess: time.Now().Unix(), + } +} + +/** + * @brief 包装 net.Listener + */ +type Listener struct { + net.Listener + protocol string // 协议,主要用以日志记录与全局对象索引 + conns *sync.Map // 保存 ip -> counter + maxConnPerHost int32 // 每个IP最多的连接数 + maxConnLimit int32 // 当前listener最大的连接数限制 + whiteList map[string]bool // 白名单列表 + readTimeout time.Duration // 读超时 + connCount int32 // 当前listener保持连接的个数 + purgeCounterInterval time.Duration // 回收过期counter的 + purgeCounterExpire int64 // counter过期的秒数 + purgeCancel context.CancelFunc // 停止purge协程的ctx +} + +// returns a new listener +// @param l 网络连接 +// @param protocol 当前listener的七层协议,比如http,grpc等 +func NewListener(l net.Listener, protocol string, config *Config) (net.Listener, error) { + // 参数校验 + if protocol == "" { + log.Errorf("[ConnLimit] listener is missing protocol") + return nil, errors.New("listener is missing protocol") + } + if config == nil || config.OpenConnLimit == false { + log.Infof("[ConnLimit][%s] apiserver is not open conn limit", protocol) + return l, nil + } + if config.PurgeCounterInterval == 0 || config.PurgeCounterExpire == 0 { + log.Errorf("[ConnLimit][%s] purge params invalid", protocol) + return nil, errors.New("purge params invalid") + } + + hostConnLimit := int32(config.MaxConnPerHost) + lisConnLimit := int32(config.MaxConnLimit) + // 参数校验, perHost阈值不能小于1 + if hostConnLimit < minHostConnLimit { + return nil, fmt.Errorf("invalid conn limit: %d, can't be smaller than %d", hostConnLimit, minHostConnLimit) + } + + whiteList := make(map[string]bool) + whites := strings.Split(config.WhiteList, ",") + for _, entry := range whites { + if entry == "" { + continue + } + whiteList[entry] = true + } + log.Infof("[ConnLimit] host conn limit white list: %+v", whites) + + lis := &Listener{ + Listener: l, + protocol: protocol, + conns: new(sync.Map), + maxConnPerHost: hostConnLimit, + maxConnLimit: lisConnLimit, + whiteList: whiteList, + readTimeout: config.ReadTimeout, + purgeCounterInterval: config.PurgeCounterInterval, + purgeCounterExpire: int64(config.PurgeCounterExpire / time.Second), + } + // 把listener放到全局变量中,方便外部访问 + if err := SetLimitListener(lis); err != nil { + return nil, err + } + // 启动回收协程,定时回收过期counter + ctx, cancel := context.WithCancel(context.Background()) + lis.purgeExpireCounter(ctx) + lis.purgeCancel = cancel + return lis, nil +} + +// 接收连接 +func (l *Listener) Accept() (net.Conn, error) { + c, err := l.Listener.Accept() + if err != nil { + return nil, err + } + return l.accept(c), nil +} + +// 关闭连接 +func (l *Listener) Close() error { + log.Infof("[Listener][%s] close the listen fd", l.protocol) + l.purgeCancel() + return l.Listener.Close() +} + +// 查看对应ip的连接数 +func (l *Listener) GetHostConnCount(host string) int32 { + var connNum int32 + if value, ok := l.conns.Load(host); ok { + c := value.(*counter) + c.mu.Lock() + connNum = c.size + c.mu.Unlock() + } + + return connNum +} + +// 遍历当前持有连接的host +func (l *Listener) Range(fn func(host string, count int32) bool) { + l.conns.Range(func(key, value interface{}) bool { + host := key.(string) + return fn(host, l.GetHostConnCount(host)) + }) +} + +// 查看当前监听server保持的连接数 +func (l *Listener) GetListenerConnCount() int32 { + return atomic.LoadInt32(&l.connCount) +} + +// 获取当前缓存的host的个数 +func (l *Listener) GetDistinctHostCount() int32 { + var count int32 + l.conns.Range(func(key, value interface{}) bool { + count++ + return true + }) + return count +} + +// 获取指定host的活跃的连接 +func (l *Listener) GetHostActiveConns(host string) map[string]*Conn { + obj, ok := l.conns.Load(host) + if !ok { + return nil + } + + ct := obj.(*counter) + ct.mu.Lock() + out := make(map[string]*Conn, len(ct.actives)) + for address, conn := range ct.actives { + out[address] = conn + } + ct.mu.Unlock() + + return out +} + +// 获取客户端连接的stat信息 +func (l *Listener) GetHostConnStats(host string) []*HostConnStat { + loadStat := func(h string, ct *counter) *HostConnStat { + ct.mu.Lock() + stat := &HostConnStat{ + Host: h, + Amount: ct.size, + LastAccess: time.Unix(ct.lastAccess, 0), + Actives: make([]string, 0, len(ct.actives)), + } + for client := range ct.actives { + stat.Actives = append(stat.Actives, client) + } + ct.mu.Unlock() + return stat + } + + var out []*HostConnStat + // 只获取一个,推荐每次只获取一个 + if host != "" { + if obj, ok := l.conns.Load(host); ok { + out = append(out, loadStat(host, obj.(*counter))) + return out + } + return nil + } + + // 全量扫描,比较耗时 + l.conns.Range(func(key, value interface{}) bool { + out = append(out, loadStat(key.(string), value.(*counter))) + return true + }) + return out +} + +// 获取指定host和port的连接 +func (l *Listener) GetHostConnection(host string, port int) *Conn { + obj, ok := l.conns.Load(host) + if !ok { + return nil + } + + ct := obj.(*counter) + target := fmt.Sprintf("%s:%d", host, port) + ct.mu.Lock() + defer ct.mu.Unlock() + for address, conn := range ct.actives { + if address == target { + return conn + } + } + + return nil +} + +// 封装一层,让关键函数acquire的更具备可测试性(不需要mock net.Conn) +func (l *Listener) accept(conn net.Conn) net.Conn { + address := conn.RemoteAddr().String() + // addr解析失败, 不做限制 + ipPort := strings.Split(address, ":") + if len(ipPort) != 2 || ipPort[0] == "" { + return conn + } + return l.acquire(conn, address, ipPort[0]) +} + +// 包裹一下conn +// 增加ip的连接计数,如果发现ip连接达到上限,则关闭 +// conn 原始连接 +// address 客户端地址 +// host 处理后的客户端IP地址 +func (l *Listener) acquire(conn net.Conn, address string, host string) *Conn { + limiterConn := &Conn{ + Conn: conn, + closed: false, + address: address, + host: host, + listener: l, + } + + log.Debugf("acquire conn for: %s", address) + if ok := l.incConnCount(); !ok { + log.Errorf("[ConnLimit][%s] host(%s) reach apiserver conn limit(%d)", l.protocol, host, l.maxConnLimit) + limiterConn.closed = true + _ = limiterConn.Conn.Close() + return limiterConn + } + + value, ok := l.conns.Load(host) + // 首次访问, 置1返回ok + if !ok { + ctr := newCounter() + ctr.actives[address] = limiterConn + l.conns.Store(host, ctr) + return limiterConn + } + + c := value.(*counter) + c.mu.Lock() // release是并发的,因此需要加锁 + // 如果连接数已经超过阈值, 则返回失败, 使用方要调用release减少计数 + // 如果在白名单中,则直接忽略host连接限制 + if c.size >= l.maxConnPerHost && !l.ignoreHostConnLimit(host) { + c.mu.Unlock() + l.descConnCount() // 前面已经增加了计数,因此这里失败,必须减少计数 + log.Errorf("[ConnLimit][%s] host(%s) reach host conn limit(%d)", l.protocol, host, l.maxConnPerHost) + limiterConn.closed = true + _ = limiterConn.Conn.Close() + return limiterConn + } + + // 单个IP的连接,还有冗余,则增加计数 + c.size++ + c.actives[address] = limiterConn + c.lastAccess = time.Now().Unix() + // map里面存储的是指针,可以不用store,这里直接对指针的内存操作 + //l.conns.Store(host, c) + c.mu.Unlock() + return limiterConn +} + +// 减少连接计数 +func (l *Listener) release(conn *Conn) { + log.Debugf("release conn for: %s", conn.host) + l.descConnCount() + + if value, ok := l.conns.Load(conn.host); ok { + c := value.(*counter) + c.mu.Lock() + c.size-- + // map里面存储的是指针,可以不用store,这里直接对指针的内存操作 + //l.conns.Store(host, c) + delete(c.actives, conn.address) + c.mu.Unlock() + } + +} + +// 增加监听server的连接计数 +// 这里使用了原子变量来增加计数,先判断是否超过最大限制 +// 如果超过了,则立即返回false,否则计数+1 +// 在计数+1的过程中,即使有Desc释放过程,也不影响 +func (l *Listener) incConnCount() bool { + if l.maxConnLimit <= 0 { + return true + } + if count := atomic.LoadInt32(&l.connCount); count >= l.maxConnLimit { + return false + } + + atomic.AddInt32(&l.connCount, 1) + return true +} + +// 释放监听server的连接计数 +func (l *Listener) descConnCount() { + if l.maxConnLimit <= 0 { + return + } + + atomic.AddInt32(&l.connCount, -1) +} + +// 判断host是否在白名单中 +// 如果host在白名单中,则忽略host连接限制 +func (l *Listener) ignoreHostConnLimit(host string) bool { + if l.whiteList == nil { + return false + } + if _, ok := l.whiteList[host]; !ok { + return false + } + + return true +} + +// 回收长时间没有访问的IP +// 定时扫描 +func (l *Listener) purgeExpireCounter(ctx context.Context) { + go func() { + ticker := time.NewTicker(l.purgeCounterInterval) + defer ticker.Stop() + log.Infof("[Listener][%s] start doing purge expire counter", l.protocol) + for { + select { + case <-ticker.C: + l.purgeExpireCounterHandler() + case <-ctx.Done(): + log.Infof("[Listener][%s] purge expire counter exit", l.protocol) + return + } + } + }() +} + +// 回收过期counter执行函数 +func (l *Listener) purgeExpireCounterHandler() { + start := time.Now() + scanCount := 0 + purgeCount := 0 + l.conns.Range(func(key, value interface{}) bool { + scanCount++ + ct := value.(*counter) + ct.mu.Lock() + if ct.size == 0 && time.Now().Unix()-ct.lastAccess > l.purgeCounterExpire { + // log.Infof("[Listener][%s] purge expire counter: %s", l.protocol, key.(string)) + l.conns.Delete(key) + purgeCount++ + } + ct.mu.Unlock() + return true + }) + log.Infof("[Listener][%s] purge expire counter total(%d), use time: %+v, scan total(%d), scan qps: %.2f", + l.protocol, purgeCount, time.Now().Sub(start), scanCount, float64(scanCount)/time.Now().Sub(start).Seconds()) +} diff --git a/common/connlimit/listener_test.go b/common/connlimit/listener_test.go new file mode 100644 index 000000000..9a3c241c4 --- /dev/null +++ b/common/connlimit/listener_test.go @@ -0,0 +1,448 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package connlimit + +import ( + "context" + "fmt" + "github.com/polarismesh/polaris-server/common/connlimit/mock_net" + "github.com/golang/mock/gomock" + . "github.com/smartystreets/goconvey/convey" + "math/rand" + "net" + "sync" + "sync/atomic" + "testing" + "time" +) + +// 模拟一下连接限制 +func TestConnLimit(t *testing.T) { + addr := "127.0.0.1:44444" + host := "127.0.0.1" + config := &Config{ + OpenConnLimit: true, + MaxConnPerHost: 5, + MaxConnLimit: 3, + PurgeCounterInterval: time.Hour, + PurgeCounterExpire: time.Minute, + } + connCount := 100 + lis, err := net.Listen("tcp", addr) + if err != nil { + t.Fatalf("%s", err) + } + + lis, err = NewListener(lis, "tcp", config) + if err != nil { + t.Fatalf("%s", err) + } + + if lis.(*Listener).GetHostConnCount(host) != 0 { + t.Fatalf("%s connNum should be 0 when no connections", host) + } + + // 启动Server + go func() { + for { + conn, _ := lis.Accept() + go func(c net.Conn) { + buf := make([]byte, 10) + if _, err := c.Read(buf); err != nil { + t.Logf("server read err: %s", err.Error()) + _ = c.Close() + return + } + t.Logf("server read data: %s", string(buf)) + time.Sleep(time.Millisecond * 200) + _ = c.Close() + }(conn) + } + }() + time.Sleep(1 * time.Second) + + var total int32 + for i := 0; i < connCount; i++ { + go func(index int) { + conn, err := net.Dial("tcp", addr) + atomic.AddInt32(&total, 1) + if err != nil { + t.Logf("client conn server error: %s", err.Error()) + return + } + buf := []byte("hello") + if _, err := conn.Write(buf); err != nil { + t.Logf("client write error: %s", err.Error()) + _ = conn.Close() + return + } + }(i) + } + + // 等待连接全部关闭 + //time.Sleep(5 * time.Second) + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + for { + select { + case <-ticker.C: + if atomic.LoadInt32(&total) != int32(connCount) { + t.Logf("connection is not finished") + continue + } + hostCnt := lis.(*Listener).GetHostConnCount(host) + lisCnt := lis.(*Listener).GetListenerConnCount() + if hostCnt == 0 && lisCnt == 0 { + t.Logf("pass") + return + } + + t.Logf("host conn count:%d: lis conn count:%d", hostCnt, lisCnt) + } + } +} + +// test readTimeout场景 +/*func TestConnLimiterReadTimeout(t *testing.T) { + lis, err := net.Listen("tcp", "127.0.0.1:55555") + if err != nil { + t.Fatalf("%s", err) + } + + cfg := &Config{ + OpenConnLimit: true, + MaxConnLimit: 16, + MaxConnPerHost: 8, + ReadTimeout: time.Millisecond * 500, + } + lis, err = NewListener(lis, "http", cfg) + if err != nil { + t.Fatalf("error: %s", err.Error()) + } + defer lis.Close() + handler := func(conn net.Conn) { + for { + reader := bufio.NewReader(conn) + buf := make([]byte, 12) + if _, err := io.ReadFull(reader, buf); err != nil { + t.Logf("read full return: %s", err.Error()) + if e, ok := err.(net.Error); ok && e.Timeout() { + t.Logf("pass") + } else { + t.Fatalf("error") + } + return + } + t.Logf("%s", string(buf)) + go func() {conn.Close()}() + } + } + go func() { + conn, err := lis.Accept() + if err != nil { + t.Fatalf("error: %s", err.Error()) + } + go handler(conn) + }() + + conn, err := net.Dial("tcp", "127.0.0.1:55555") + if err != nil { + t.Fatalf("error: %s", err.Error()) + } + //time.Sleep(time.Second * 1) + _, err = conn.Write([]byte("hello world!")) + if err != nil { + t.Logf("%s", err.Error()) + } + time.Sleep(time.Second) + conn.Close() + time.Sleep(time.Second) +}*/ + +// test invalid conn limit param +func TestInvalidParams(t *testing.T) { + lis, err := net.Listen("tcp", "127.0.0.1:44445") + if err != nil { + t.Fatalf("%s", err) + } + defer func() { _ = lis.Close() }() + config := &Config{ + OpenConnLimit: true, + MaxConnPerHost: 0, + MaxConnLimit: 10, + PurgeCounterInterval: time.Hour, + PurgeCounterExpire: time.Minute, + } + + t.Run("host连接限制小于1", func(t *testing.T) { + if _, newErr := NewListener(lis, "tcp", config); newErr == nil { + t.Fatalf("must be wrong for invalidMaxConnNum") + } + }) + t.Run("protocol为空", func(t *testing.T) { + config.MaxConnPerHost = 10 + if _, err := NewListener(lis, "", config); err == nil { + t.Fatalf("error") + } else { + t.Logf("%s", err.Error()) + } + }) + t.Run("purge参数错误", func(t *testing.T) { + config.PurgeCounterInterval = 0 + if _, err := NewListener(lis, "tcp1", config); err == nil { + t.Fatalf("error") + } + config.PurgeCounterInterval = time.Hour + config.PurgeCounterExpire = 0 + if _, err := NewListener(lis, "tcp2", config); err == nil { + t.Fatalf("error") + } else { + t.Logf("%s", err.Error()) + } + }) +} + +// 测试accept +func TestListener_Accept(t *testing.T) { + Convey("正常accept", t, func() { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + addr := mock_net.NewMockAddr(ctrl) + conn := mock_net.NewMockConn(ctrl) + conn.EXPECT().Close().Return(nil).AnyTimes() + addr.EXPECT().String().Return("1.2.3.4:8080").AnyTimes() + conn.EXPECT().RemoteAddr().Return(addr).AnyTimes() + lis := NewTestLimitListener(100, 10) + So(lis.accept(conn).(*Conn).isValid(), ShouldBeTrue) + }) +} + +// 测试acquire +func TestLimitListener_Acquire(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + conn := mock_net.NewMockConn(ctrl) + conn.EXPECT().Close().Return(nil).AnyTimes() + Convey("acquire测试", t, func() { + Convey("超过server监听的最大限制,返回false", func() { + lis := &Listener{maxConnPerHost: 1, maxConnLimit: 10, connCount: 10} + c := lis.acquire(conn, "1.2.3.4:8080", "1.2.3.4") + So(c.isValid(), ShouldBeFalse) + }) + Convey("host首次请求,可以正常获取连接", func() { + lis := NewTestLimitListener(100, 10) + c := lis.acquire(conn, "2.3.4.5:8080", "2.3.4.5") + So(c.isValid(), ShouldBeTrue) + }) + Convey("host多次获取,正常", func() { + lis := NewTestLimitListener(15, 10) + for i := 0; i < 10; i++ { + So(lis.acquire(conn, fmt.Sprintf("1.2.3.4:%d", i), "1.2.3.4").isValid(), ShouldBeTrue) + } + So(lis.acquire(conn, fmt.Sprintf("1.2.3.4:%d", 20), "1.2.3.4").isValid(), ShouldBeFalse) + + // 其他host没有超过限制,true + So(lis.acquire(conn, fmt.Sprintf("1.2.3.9:%d", 200), "1.2.3.9").isValid(), ShouldBeTrue) + // 占满listen的最大连接,前面成功了11个,剩下4个还没有满 + for i := 0; i < 4; i++ { + So(lis.acquire(conn, fmt.Sprintf("1.2.3.8:%d", i), "1.2.3.8").isValid(), ShouldBeTrue) + } + + // 总连接数被占满,false + So(lis.acquire(conn, fmt.Sprintf("1.2.3.19:%d", 123), "1.2.3.9").isValid(), ShouldBeFalse) + }) + }) +} + +// release +func TestLimitListener_ReLease(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + conn := mock_net.NewMockConn(ctrl) + conn.EXPECT().Close().Return(nil).AnyTimes() + t.Run("并发释放测试", func(t *testing.T) { + lis := NewTestLimitListener(2048000, 204800) + conns := make([]net.Conn, 0, 10240) + for i := 0; i < 10240; i++ { + c := lis.acquire(conn, "1.2.3.4:8080", "1.2.3.4") + conns = append(conns, c) + } + + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + for i := 0; i < 10240; i++ { + lis.acquire(conn, "1.2.3.4:8080", "1.2.3.4") + } + }() + + for i := 0; i < 2048; i++ { + wg.Add(1) + go func(index int) { + for j := 0; j < 5; j++ { + c := conns[index*5+j] + _ = c.Close() + } + wg.Done() + }(i) + } + + wg.Wait() + var remain int32 = 10240 + 10240 - 2048*5 + if lis.GetListenerConnCount() == remain && lis.GetHostConnCount("1.2.3.4") == remain { + t.Logf("pass") + } else { + t.Fatalf("error: %d, %d", lis.GetListenerConnCount(), lis.GetHostConnCount("1.2.3.4")) + } + }) +} + +// 白名单测试 +func TestWhiteList(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + conn := mock_net.NewMockConn(ctrl) + conn.EXPECT().Close().Return(nil).AnyTimes() + + Convey("白名单下,限制不生效", t, func() { + listener := NewTestLimitListener(100, 2) + listener.whiteList = map[string]bool{ + "8.8.8.8": true, + } + for i := 0; i < 100; i++ { + So(listener.acquire(conn, "8.8.8.8:123", "8.8.8.8").isValid(), ShouldBeTrue) + } + // 超过了机器的100限制,白名单也不放过 + So(listener.acquire(conn, "8.8.8.8:123", "8.8.8.8").isValid(), ShouldBeFalse) + So(listener.acquire(conn, "8.8.8.9:123", "8.8.8.9").isValid(), ShouldBeFalse) + So(listener.acquire(conn, "8.8.8.10:123", "8.8.8.10").isValid(), ShouldBeFalse) + }) +} + +// 测试activeConns +func TestActiveConns(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + conn := mock_net.NewMockConn(ctrl) + conn.EXPECT().Close().Return(nil).AnyTimes() + listener := NewTestLimitListener(1024, 64) + var conns []*Conn + Convey("初始化", t, func() { + for i := 0; i < 32; i++ { + c := listener.acquire(conn, fmt.Sprintf("8.8.8.8:%d", i), "8.8.8.8") + So(c.isValid(), ShouldBeTrue) + conns = append(conns, c) + } + }) + Convey("测试活跃连接", t, func() { + Convey("已活跃的连接可以正常存储", func() { + actives := listener.GetHostActiveConns("8.8.8.8") + So(actives, ShouldNotBeNil) + So(len(actives), ShouldEqual, 32) + }) + Convey("连接关闭,活跃连接map会剔除", func() { + for i := 0; i < 8; i++ { + _ = conns[i].Close() + } + actives := listener.GetHostActiveConns("8.8.8.8") + So(actives, ShouldNotBeNil) + So(len(actives), ShouldEqual, 24) // 32 - 8 + }) + Convey("重复关闭连接,活跃连接map不受影响,size不受影响", func() { + for i := 0; i < 8; i++ { + _ = conns[i].Close() + } + actives := listener.GetHostActiveConns("8.8.8.8") + So(actives, ShouldNotBeNil) + So(len(actives), ShouldEqual, 24) + So(listener.GetHostConnCount("8.8.8.8"), ShouldEqual, 24) + }) + Convey("多主机数据,可以正常存储", func() { + for i := 0; i < 16; i++ { + c := listener.acquire(conn, fmt.Sprintf("8.8.8.16:%d", i), "8.8.8.16") + So(c.isValid(), ShouldBeTrue) + conns = append(conns, c) + } + actives := listener.GetHostActiveConns("8.8.8.16") + So(actives, ShouldNotBeNil) + So(len(actives), ShouldEqual, 16) + }) + }) +} + +// 测试回收过期Counter函数 +func TestPurgeExpireCounterHandler(t *testing.T) { + Convey("可以正常purge", t, func() { + listener := NewTestLimitListener(1024, 16) + listener.purgeCounterExpire = 3 + for i := 0; i < 102400; i++ { + ct := newCounter() + ct.size = 0 + listener.conns.Store(fmt.Sprintf("127.0.0.:%d", i), ct) + } + time.Sleep(time.Second * 4) + for i := 0; i < 102400; i++ { + ct := newCounter() + ct.size = 0 + listener.conns.Store(fmt.Sprintf("127.0.1.%d", i), ct) + } + So(listener.GetDistinctHostCount(), ShouldEqual, 204800) + listener.purgeExpireCounterHandler() + So(listener.GetDistinctHostCount(), ShouldEqual, 102400) + }) + Convey("并发store和range,扫描的速度测试", t, func() { + listener := NewTestLimitListener(1024, 16) + listener.purgeCounterInterval = time.Microsecond * 10 + listener.purgeCounterExpire = 1 + rand.Seed(time.Now().UnixNano()) + ctx, cancel := context.WithCancel(context.Background()) + for i := 0; i < 10240; i++ { + go func(index int) { + for { + select { + case <-ctx.Done(): + return + default: + } + for j := 0; j < 100; j++ { + ct := newCounter() + ct.size = 0 + listener.conns.Store(fmt.Sprintf("%d.%d", index, j), ct) + time.Sleep(time.Millisecond) + } + } + + }(i) + } + listener.purgeExpireCounter(ctx) + <-time.After(time.Second * 5) + cancel() + }) +} + +// 返回一个测试listener +func NewTestLimitListener(maxLimit int32, hostLimit int32) *Listener { + return &Listener{ + maxConnLimit: maxLimit, + maxConnPerHost: hostLimit, + conns: new(sync.Map), + purgeCounterInterval: time.Hour, + purgeCounterExpire: 300, + } +} diff --git a/common/connlimit/mock_net/mock_conn.go b/common/connlimit/mock_net/mock_conn.go new file mode 100644 index 000000000..eddb9918f --- /dev/null +++ b/common/connlimit/mock_net/mock_conn.go @@ -0,0 +1,200 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: net (interfaces: Conn,Addr) + +// Package mock_net is a generated GoMock package. +package mock_net + +import ( + gomock "github.com/golang/mock/gomock" + net "net" + reflect "reflect" + time "time" +) + +// MockConn is a mock of Conn interface +type MockConn struct { + ctrl *gomock.Controller + recorder *MockConnMockRecorder +} + +// MockConnMockRecorder is the mock recorder for MockConn +type MockConnMockRecorder struct { + mock *MockConn +} + +// NewMockConn creates a new mock instance +func NewMockConn(ctrl *gomock.Controller) *MockConn { + mock := &MockConn{ctrl: ctrl} + mock.recorder = &MockConnMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockConn) EXPECT() *MockConnMockRecorder { + return m.recorder +} + +// Close mocks base method +func (m *MockConn) Close() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Close") + ret0, _ := ret[0].(error) + return ret0 +} + +// Close indicates an expected call of Close +func (mr *MockConnMockRecorder) Close() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockConn)(nil).Close)) +} + +// LocalAddr mocks base method +func (m *MockConn) LocalAddr() net.Addr { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LocalAddr") + ret0, _ := ret[0].(net.Addr) + return ret0 +} + +// LocalAddr indicates an expected call of LocalAddr +func (mr *MockConnMockRecorder) LocalAddr() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LocalAddr", reflect.TypeOf((*MockConn)(nil).LocalAddr)) +} + +// Read mocks base method +func (m *MockConn) Read(arg0 []byte) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Read", arg0) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Read indicates an expected call of Read +func (mr *MockConnMockRecorder) Read(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockConn)(nil).Read), arg0) +} + +// RemoteAddr mocks base method +func (m *MockConn) RemoteAddr() net.Addr { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RemoteAddr") + ret0, _ := ret[0].(net.Addr) + return ret0 +} + +// RemoteAddr indicates an expected call of RemoteAddr +func (mr *MockConnMockRecorder) RemoteAddr() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoteAddr", reflect.TypeOf((*MockConn)(nil).RemoteAddr)) +} + +// SetDeadline mocks base method +func (m *MockConn) SetDeadline(arg0 time.Time) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetDeadline", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetDeadline indicates an expected call of SetDeadline +func (mr *MockConnMockRecorder) SetDeadline(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDeadline", reflect.TypeOf((*MockConn)(nil).SetDeadline), arg0) +} + +// SetReadDeadline mocks base method +func (m *MockConn) SetReadDeadline(arg0 time.Time) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetReadDeadline", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetReadDeadline indicates an expected call of SetReadDeadline +func (mr *MockConnMockRecorder) SetReadDeadline(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetReadDeadline", reflect.TypeOf((*MockConn)(nil).SetReadDeadline), arg0) +} + +// SetWriteDeadline mocks base method +func (m *MockConn) SetWriteDeadline(arg0 time.Time) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetWriteDeadline", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetWriteDeadline indicates an expected call of SetWriteDeadline +func (mr *MockConnMockRecorder) SetWriteDeadline(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetWriteDeadline", reflect.TypeOf((*MockConn)(nil).SetWriteDeadline), arg0) +} + +// Write mocks base method +func (m *MockConn) Write(arg0 []byte) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Write", arg0) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Write indicates an expected call of Write +func (mr *MockConnMockRecorder) Write(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockConn)(nil).Write), arg0) +} + +// MockAddr is a mock of Addr interface +type MockAddr struct { + ctrl *gomock.Controller + recorder *MockAddrMockRecorder +} + +// MockAddrMockRecorder is the mock recorder for MockAddr +type MockAddrMockRecorder struct { + mock *MockAddr +} + +// NewMockAddr creates a new mock instance +func NewMockAddr(ctrl *gomock.Controller) *MockAddr { + mock := &MockAddr{ctrl: ctrl} + mock.recorder = &MockAddrMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockAddr) EXPECT() *MockAddrMockRecorder { + return m.recorder +} + +// Network mocks base method +func (m *MockAddr) Network() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Network") + ret0, _ := ret[0].(string) + return ret0 +} + +// Network indicates an expected call of Network +func (mr *MockAddrMockRecorder) Network() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Network", reflect.TypeOf((*MockAddr)(nil).Network)) +} + +// String mocks base method +func (m *MockAddr) String() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "String") + ret0, _ := ret[0].(string) + return ret0 +} + +// String indicates an expected call of String +func (mr *MockAddrMockRecorder) String() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "String", reflect.TypeOf((*MockAddr)(nil).String)) +} diff --git a/common/connlimit/stats.go b/common/connlimit/stats.go new file mode 100644 index 000000000..085c2c4c1 --- /dev/null +++ b/common/connlimit/stats.go @@ -0,0 +1,28 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package connlimit + +import "time" + +// 连接的统计信息 +type HostConnStat struct { + Host string + Amount int32 + LastAccess time.Time + Actives []string +} diff --git a/common/log/config.go b/common/log/config.go new file mode 100644 index 000000000..b53fab967 --- /dev/null +++ b/common/log/config.go @@ -0,0 +1,339 @@ +// Copyright 2017 Istio Authors +// +// 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. + +// Package log provides the canonical logging functionality used by Go-based +// Istio components. +// +// Istio's logging subsystem is built on top of the [Zap](https://godoc.org/go.uber.org/zap) package. +// High performance scenarios should use the Error, Warn, Info, and Debug methods. Lower perf +// scenarios can use the more expensive convenience methods such as Debugf and Warnw. +// +// The package provides direct integration with the Cobra command-line processor which makes it +// easy to build programs that use a consistent interface for logging. Here's an example +// of a simple Cobra-based program using this log package: +// +// func main() { +// // get the default logging options +// options := log.DefaultOptions() +// +// rootCmd := &cobra.Command{ +// Run: func(cmd *cobra.Command, args []string) { +// +// // configure the logging system +// if err := log.Configure(options); err != nil { +// // print an error and quit +// } +// +// // output some logs +// log.Info("Hello") +// log.Sync() +// }, +// } +// +// // add logging-specific flags to the cobra command +// options.AttachCobraFlags(rootCmd) +// rootCmd.SetArgs(os.Args[1:]) +// rootCmd.Execute() +// } +// +// Once configured, this package intercepts the output of the standard golang "log" package as well as anything +// sent to the global zap logger (zap.L()). +package log + +import ( + "fmt" + "os" + "strings" + "sync/atomic" + "time" + + "github.com/natefinch/lumberjack" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zapgrpc" + "google.golang.org/grpc/grpclog" +) + +// none is used to disable logging output as well as to disable stack tracing. +const none zapcore.Level = 100 + +var levelToZap = map[Level]zapcore.Level{ + DebugLevel: zapcore.DebugLevel, + InfoLevel: zapcore.InfoLevel, + WarnLevel: zapcore.WarnLevel, + ErrorLevel: zapcore.ErrorLevel, + FatalLevel: zapcore.FatalLevel, + NoneLevel: none, +} + +// functions that can be replaced in a test setting +type patchTable struct { + write func(ent zapcore.Entry, fields []zapcore.Field) error + sync func() error + exitProcess func(code int) + errorSink zapcore.WriteSyncer +} + +// function table that can be replaced by tests +var funcs = &atomic.Value{} + +func init() { + // use our defaults for starters so that logging works even before everything is fully configured + _ = Configure(DefaultOptions()) +} + +// prepZap is a utility function used by the Configure function. +func prepZap(options *Options) (zapcore.Core, zapcore.Core, zapcore.WriteSyncer, error) { + encCfg := zapcore.EncoderConfig{ + TimeKey: "time", + LevelKey: "level", + NameKey: "scope", + CallerKey: "caller", + MessageKey: "msg", + StacktraceKey: "stack", + LineEnding: zapcore.DefaultLineEnding, + EncodeLevel: zapcore.LowercaseLevelEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + EncodeDuration: zapcore.StringDurationEncoder, + EncodeTime: formatDate, + } + + var enc zapcore.Encoder + if options.JSONEncoding { + enc = zapcore.NewJSONEncoder(encCfg) + } else { + enc = zapcore.NewConsoleEncoder(encCfg) + } + + var rotaterSink zapcore.WriteSyncer + if options.RotateOutputPath != "" { + rotaterSink = zapcore.AddSync(&lumberjack.Logger{ + Filename: options.RotateOutputPath, + MaxSize: options.RotationMaxSize, + MaxBackups: options.RotationMaxBackups, + MaxAge: options.RotationMaxAge, + }) + } + + errSink, closeErrorSink, err := zap.Open(options.ErrorOutputPaths...) + if err != nil { + return nil, nil, nil, err + } + + var outputSink zapcore.WriteSyncer + if len(options.OutputPaths) > 0 { + outputSink, _, err = zap.Open(options.OutputPaths...) + if err != nil { + closeErrorSink() + return nil, nil, nil, err + } + } + + var sink zapcore.WriteSyncer + if rotaterSink != nil && outputSink != nil { + sink = zapcore.NewMultiWriteSyncer(outputSink, rotaterSink) + } else if rotaterSink != nil { + sink = rotaterSink + } else { + sink = outputSink + } + + var enabler zap.LevelEnablerFunc = func(lvl zapcore.Level) bool { + switch lvl { + case zapcore.ErrorLevel: + return defaultScope.ErrorEnabled() + case zapcore.WarnLevel: + return defaultScope.WarnEnabled() + case zapcore.InfoLevel: + return defaultScope.InfoEnabled() + } + return defaultScope.DebugEnabled() + } + + return zapcore.NewCore(enc, sink, zap.NewAtomicLevelAt(zapcore.DebugLevel)), + zapcore.NewCore(enc, sink, enabler), + errSink, nil +} + +func formatDate(t time.Time, enc zapcore.PrimitiveArrayEncoder) { + //t = t.UTC() 不用utc时间 + year, month, day := t.Date() + hour, minute, second := t.Clock() + micros := t.Nanosecond() / 1000 + + buf := make([]byte, 27) + + buf[0] = byte((year/1000)%10) + '0' + buf[1] = byte((year/100)%10) + '0' + buf[2] = byte((year/10)%10) + '0' + buf[3] = byte(year%10) + '0' + buf[4] = '-' + buf[5] = byte((month)/10) + '0' + buf[6] = byte((month)%10) + '0' + buf[7] = '-' + buf[8] = byte((day)/10) + '0' + buf[9] = byte((day)%10) + '0' + buf[10] = 'T' + buf[11] = byte((hour)/10) + '0' + buf[12] = byte((hour)%10) + '0' + buf[13] = ':' + buf[14] = byte((minute)/10) + '0' + buf[15] = byte((minute)%10) + '0' + buf[16] = ':' + buf[17] = byte((second)/10) + '0' + buf[18] = byte((second)%10) + '0' + buf[19] = '.' + buf[20] = byte((micros/100000)%10) + '0' + buf[21] = byte((micros/10000)%10) + '0' + buf[22] = byte((micros/1000)%10) + '0' + buf[23] = byte((micros/100)%10) + '0' + buf[24] = byte((micros/10)%10) + '0' + buf[25] = byte((micros)%10) + '0' + buf[26] = 'Z' + + enc.AppendString(string(buf)) +} + +func updateScopes(options *Options) error { + // snapshot what's there + allScopes := Scopes() + + // update the output levels of all listed scopes + if err := processLevels(allScopes, options.outputLevels, func(s *Scope, l Level) { s.SetOutputLevel(l) }); err != nil { + return err + } + + // update the stack tracing levels of all listed scopes + if err := processLevels( + allScopes, options.stackTraceLevels, func(s *Scope, l Level) { s.SetStackTraceLevel(l) }); err != nil { + return err + } + + // update the caller location setting of all listed scopes + sc := strings.Split(options.logCallers, ",") + for _, s := range sc { + if s == "" { + continue + } + + if s == OverrideScopeName { + // ignore everything else and just apply the override value + for _, scope := range allScopes { + scope.SetLogCallers(true) + } + + return nil + } + + if scope, ok := allScopes[s]; ok { + scope.SetLogCallers(true) + } else { + return fmt.Errorf("unknown scope '%s' specified", s) + } + } + + return nil +} + +// processLevels breaks down an argument string into a set of scope & levels and then +// tries to apply the result to the scopes. It supports the use of a global override. +func processLevels(allScopes map[string]*Scope, arg string, setter func(*Scope, Level)) error { + levels := strings.Split(arg, ",") + for _, sl := range levels { + s, l, err := convertScopedLevel(sl) + if err != nil { + return err + } + + if scope, ok := allScopes[s]; ok { + setter(scope, l) + } else if s == OverrideScopeName { + // override replaces everything + for _, scope := range allScopes { + setter(scope, l) + } + return nil + } else { + return fmt.Errorf("unknown scope '%s' specified", s) + } + } + + return nil +} + +// Configure initializes Istio's logging subsystem. +// +// You typically call this once at process startup. +// Once this call returns, the logging system is ready to accept data. +// nolint: staticcheck +func Configure(options *Options) error { + core, captureCore, errSink, err := prepZap(options) + if err != nil { + return err + } + + if err = updateScopes(options); err != nil { + return err + } + + pt := patchTable{ + write: func(ent zapcore.Entry, fields []zapcore.Field) error { + err := core.Write(ent, fields) + if ent.Level == zapcore.FatalLevel { + funcs.Load().(patchTable).exitProcess(1) + } + + return err + }, + sync: core.Sync, + exitProcess: os.Exit, + errorSink: errSink, + } + funcs.Store(pt) + + opts := []zap.Option{ + zap.ErrorOutput(errSink), + zap.AddCallerSkip(1), + } + + if defaultScope.GetLogCallers() { + opts = append(opts, zap.AddCaller()) + } + + l := defaultScope.GetStackTraceLevel() + if l != NoneLevel { + opts = append(opts, zap.AddStacktrace(levelToZap[l])) + } + + captureLogger := zap.New(captureCore, opts...) + + // capture global zap logging and force it through our logger + _ = zap.ReplaceGlobals(captureLogger) + + // capture standard golang "log" package output and force it through our logger + _ = zap.RedirectStdLog(captureLogger) + + // capture gRPC logging + if options.LogGrpc { + grpclog.SetLogger(zapgrpc.NewLogger(captureLogger.WithOptions(zap.AddCallerSkip(2)))) + } + + return nil +} + +// Sync flushes any buffered log entries. +// Processes should normally take care to call Sync before exiting. +func Sync() error { + return funcs.Load().(patchTable).sync() +} diff --git a/common/log/default.go b/common/log/default.go new file mode 100644 index 000000000..076ec1830 --- /dev/null +++ b/common/log/default.go @@ -0,0 +1,173 @@ +// Copyright 2017 Istio Authors +// +// 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. + +package log + +import ( + "fmt" + + "go.uber.org/zap/zapcore" +) + +var defaultScope = RegisterScope(DefaultScopeName, "Unscoped logging messages.", 0) + +// Fatal outputs a message at fatal level. +func Fatal(msg string, fields ...zapcore.Field) { + if defaultScope.GetOutputLevel() >= FatalLevel { + defaultScope.emit(zapcore.FatalLevel, defaultScope.GetStackTraceLevel() >= FatalLevel, msg, fields) + } +} + +// Fatala uses fmt.Sprint to construct and log a message at fatal level. +func Fatala(args ...interface{}) { + if defaultScope.GetOutputLevel() >= FatalLevel { + defaultScope.emit(zapcore.FatalLevel, defaultScope.GetStackTraceLevel() >= FatalLevel, fmt.Sprint(args...), nil) + } +} + +// Fatalf uses fmt.Sprintf to construct and log a message at fatal level. +func Fatalf(template string, args ...interface{}) { + if defaultScope.GetOutputLevel() >= FatalLevel { + msg := template + if len(args) > 0 { + msg = fmt.Sprintf(template, args...) + } + defaultScope.emit(zapcore.FatalLevel, defaultScope.GetStackTraceLevel() >= FatalLevel, msg, nil) + } +} + +// FatalEnabled returns whether output of messages using this scope is currently enabled for fatal-level output. +func FatalEnabled() bool { + return defaultScope.GetOutputLevel() >= FatalLevel +} + +// Error outputs a message at error level. +func Error(msg string, fields ...zapcore.Field) { + if defaultScope.GetOutputLevel() >= ErrorLevel { + defaultScope.emit(zapcore.ErrorLevel, defaultScope.GetStackTraceLevel() >= ErrorLevel, msg, fields) + } +} + +// Errora uses fmt.Sprint to construct and log a message at error level. +func Errora(args ...interface{}) { + if defaultScope.GetOutputLevel() >= ErrorLevel { + defaultScope.emit(zapcore.ErrorLevel, defaultScope.GetStackTraceLevel() >= ErrorLevel, fmt.Sprint(args...), nil) + } +} + +// Errorf uses fmt.Sprintf to construct and log a message at error level. +func Errorf(template string, args ...interface{}) { + if defaultScope.GetOutputLevel() >= ErrorLevel { + msg := template + if len(args) > 0 { + msg = fmt.Sprintf(template, args...) + } + defaultScope.emit(zapcore.ErrorLevel, defaultScope.GetStackTraceLevel() >= ErrorLevel, msg, nil) + } +} + +// ErrorEnabled returns whether output of messages using this scope is currently enabled for error-level output. +func ErrorEnabled() bool { + return defaultScope.GetOutputLevel() >= ErrorLevel +} + +// Warn outputs a message at warn level. +func Warn(msg string, fields ...zapcore.Field) { + if defaultScope.GetOutputLevel() >= WarnLevel { + defaultScope.emit(zapcore.WarnLevel, defaultScope.GetStackTraceLevel() >= WarnLevel, msg, fields) + } +} + +// Warna uses fmt.Sprint to construct and log a message at warn level. +func Warna(args ...interface{}) { + if defaultScope.GetOutputLevel() >= WarnLevel { + defaultScope.emit(zapcore.WarnLevel, defaultScope.GetStackTraceLevel() >= WarnLevel, fmt.Sprint(args...), nil) + } +} + +// Warnf uses fmt.Sprintf to construct and log a message at warn level. +func Warnf(template string, args ...interface{}) { + if defaultScope.GetOutputLevel() >= WarnLevel { + msg := template + if len(args) > 0 { + msg = fmt.Sprintf(template, args...) + } + defaultScope.emit(zapcore.WarnLevel, defaultScope.GetStackTraceLevel() >= WarnLevel, msg, nil) + } +} + +// WarnEnabled returns whether output of messages using this scope is currently enabled for warn-level output. +func WarnEnabled() bool { + return defaultScope.GetOutputLevel() >= WarnLevel +} + +// Info outputs a message at info level. +func Info(msg string, fields ...zapcore.Field) { + if defaultScope.GetOutputLevel() >= InfoLevel { + defaultScope.emit(zapcore.InfoLevel, defaultScope.GetStackTraceLevel() >= InfoLevel, msg, fields) + } +} + +// Infoa uses fmt.Sprint to construct and log a message at info level. +func Infoa(args ...interface{}) { + if defaultScope.GetOutputLevel() >= InfoLevel { + defaultScope.emit(zapcore.InfoLevel, defaultScope.GetStackTraceLevel() >= InfoLevel, fmt.Sprint(args...), nil) + } +} + +// Infof uses fmt.Sprintf to construct and log a message at info level. +func Infof(template string, args ...interface{}) { + if defaultScope.GetOutputLevel() >= InfoLevel { + msg := template + if len(args) > 0 { + msg = fmt.Sprintf(template, args...) + } + defaultScope.emit(zapcore.InfoLevel, defaultScope.GetStackTraceLevel() >= InfoLevel, msg, nil) + } +} + +// InfoEnabled returns whether output of messages using this scope is currently enabled for info-level output. +func InfoEnabled() bool { + return defaultScope.GetOutputLevel() >= InfoLevel +} + +// Debug outputs a message at debug level. +func Debug(msg string, fields ...zapcore.Field) { + if defaultScope.GetOutputLevel() >= DebugLevel { + defaultScope.emit(zapcore.DebugLevel, defaultScope.GetStackTraceLevel() >= DebugLevel, msg, fields) + } +} + +// Debuga uses fmt.Sprint to construct and log a message at debug level. +func Debuga(args ...interface{}) { + if defaultScope.GetOutputLevel() >= DebugLevel { + defaultScope.emit(zapcore.DebugLevel, defaultScope.GetStackTraceLevel() >= DebugLevel, fmt.Sprint(args...), nil) + } +} + +// Debugf uses fmt.Sprintf to construct and log a message at debug level. +func Debugf(template string, args ...interface{}) { + if defaultScope.GetOutputLevel() >= DebugLevel { + msg := template + if len(args) > 0 { + msg = fmt.Sprintf(template, args...) + } + defaultScope.emit(zapcore.DebugLevel, defaultScope.GetStackTraceLevel() >= DebugLevel, msg, nil) + } +} + +// DebugEnabled returns whether output of messages using this scope is currently enabled for debug-level output. +func DebugEnabled() bool { + return defaultScope.GetOutputLevel() >= DebugLevel +} diff --git a/common/log/options.go b/common/log/options.go new file mode 100644 index 000000000..85a29f8ac --- /dev/null +++ b/common/log/options.go @@ -0,0 +1,295 @@ +// Copyright 2017 Istio Authors +// +// 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. + +package log + +import ( + "errors" + "fmt" + "strings" +) + +const ( + DefaultScopeName = "default" + OverrideScopeName = "all" + defaultOutputLevel = InfoLevel + defaultStackTraceLevel = NoneLevel + defaultOutputPath = "stdout" + defaultErrorOutputPath = "stderr" + defaultRotationMaxAge = 30 + defaultRotationMaxSize = 100 * 1024 * 1024 + defaultRotationMaxBackups = 1000 +) + +// Level is an enumeration of all supported log levels. +type Level int + +const ( + // NoneLevel disables logging + NoneLevel Level = iota + // FatalLevel enables fatal level logging + FatalLevel + // ErrorLevel enables error level logging + ErrorLevel + // WarnLevel enables warn level logging + WarnLevel + // InfoLevel enables info level logging + InfoLevel + // DebugLevel enables debug level logging + DebugLevel +) + +var levelToString = map[Level]string{ + DebugLevel: "debug", + InfoLevel: "info", + WarnLevel: "warn", + ErrorLevel: "error", + FatalLevel: "fatal", + NoneLevel: "none", +} + +var stringToLevel = map[string]Level{ + "debug": DebugLevel, + "info": InfoLevel, + "warn": WarnLevel, + "error": ErrorLevel, + "fatal": FatalLevel, + "none": NoneLevel, +} + +// Options defines the set of options supported by Istio's component logging package. +type Options struct { + // OutputPaths is a list of file system paths to write the log data to. + // The special values stdout and stderr can be used to output to the + // standard I/O streams. This defaults to stdout. + OutputPaths []string + + // ErrorOutputPaths is a list of file system paths to write logger errors to. + // The special values stdout and stderr can be used to output to the + // standard I/O streams. This defaults to stderr. + ErrorOutputPaths []string + + // RotateOutputPath is the path to a rotating log file. This file should + // be automatically rotated over time, based on the rotation parameters such + // as RotationMaxSize and RotationMaxAge. The default is to not rotate. + // + // This path is used as a foundational path. This is where log output is normally + // saved. When a rotation needs to take place because the file got too big or too + // old, then the file is renamed by appending a timestamp to the name. Such renamed + // files are called backups. Once a backup has been created, + // output resumes to this path. + RotateOutputPath string `yaml:"RotateOutputPath"` + + // RotationMaxSize is the maximum size in megabytes of a log file before it gets + // rotated. It defaults to 100 megabytes. + RotationMaxSize int `yaml:"RotationMaxSize"` + + // RotationMaxAge is the maximum number of days to retain old log files based on the + // timestamp encoded in their filename. Note that a day is defined as 24 + // hours and may not exactly correspond to calendar days due to daylight + // savings, leap seconds, etc. The default is to remove log files + // older than 30 days. + RotationMaxAge int `yaml:"RotationMaxAge"` + + // RotationMaxBackups is the maximum number of old log files to retain. The default + // is to retain at most 1000 logs. + RotationMaxBackups int `yaml:"RotationMaxBackups"` + + // JSONEncoding controls whether the log is formatted as JSON. + JSONEncoding bool + + // LogGrpc indicates that Grpc logs should be captured. The default is true. + // This is not exposed through the command-line flags, as this flag is mainly useful for testing: Grpc + // stack will hold on to the logger even though it gets closed. This causes data races. + LogGrpc bool + + Level string + + outputLevels string + logCallers string + stackTraceLevels string +} + +// DefaultOptions returns a new set of options, initialized to the defaults +func DefaultOptions() *Options { + return &Options{ + OutputPaths: []string{defaultOutputPath}, + ErrorOutputPaths: []string{defaultErrorOutputPath}, + RotationMaxSize: defaultRotationMaxSize, + RotationMaxAge: defaultRotationMaxAge, + RotationMaxBackups: defaultRotationMaxBackups, + outputLevels: DefaultScopeName + ":" + levelToString[defaultOutputLevel], + stackTraceLevels: DefaultScopeName + ":" + levelToString[defaultStackTraceLevel], + LogGrpc: false, + } +} + +// SetOutputLevel sets the minimum log output level for a given scope. +func (o *Options) SetOutputLevel(scope, level string) error { + _, exist := stringToLevel[level] + if !exist { + return errors.New("invalid log level") + } + sl := scope + ":" + level + + levels := strings.Split(o.outputLevels, ",") + + if scope == DefaultScopeName { + // see if we have an entry without a scope prefix (which represents the default scope) + for i, ol := range levels { + if !strings.Contains(ol, ":") { + levels[i] = sl + o.outputLevels = strings.Join(levels, ",") + return nil + } + } + } + + prefix := scope + ":" + for i, ol := range levels { + if strings.HasPrefix(ol, prefix) { + levels[i] = sl + o.outputLevels = strings.Join(levels, ",") + return nil + } + } + + levels = append(levels, sl) + o.outputLevels = strings.Join(levels, ",") + + return nil +} + +// GetOutputLevel returns the minimum log output level for a given scope. +func (o *Options) GetOutputLevel(scope string) (Level, error) { + return o.GetStackTraceLevel(scope) +} + +// SetStackTraceLevel sets the minimum stack tracing level for a given scope. +func (o *Options) SetStackTraceLevel(scope, level string) { + sl := scope + ":" + level + + levels := strings.Split(o.stackTraceLevels, ",") + + if scope == DefaultScopeName { + // see if we have an entry without a scope prefix (which represents the default scope) + for i, ol := range levels { + if !strings.Contains(ol, ":") { + levels[i] = sl + o.stackTraceLevels = strings.Join(levels, ",") + return + } + } + } + + prefix := scope + ":" + for i, ol := range levels { + if strings.HasPrefix(ol, prefix) { + levels[i] = sl + o.stackTraceLevels = strings.Join(levels, ",") + return + } + } + + levels = append(levels, sl) + o.stackTraceLevels = strings.Join(levels, ",") +} + +// GetStackTraceLevel returns the minimum stack tracing level for a given scope. +func (o *Options) GetStackTraceLevel(scope string) (Level, error) { + levels := strings.Split(o.stackTraceLevels, ",") + + if scope == DefaultScopeName { + // see if we have an entry without a scope prefix (which represents the default scope) + for _, ol := range levels { + if !strings.Contains(ol, ":") { + _, l, err := convertScopedLevel(ol) + return l, err + } + } + } + + prefix := scope + ":" + for _, ol := range levels { + if strings.HasPrefix(ol, prefix) { + _, l, err := convertScopedLevel(ol) + return l, err + } + } + + return NoneLevel, fmt.Errorf("no level defined for scope '%s'", scope) +} + +// SetLogCallers sets whether to output the caller's source code location for a given scope. +func (o *Options) SetLogCallers(scope string, include bool) { + scopes := strings.Split(o.logCallers, ",") + + // remove any occurrence of the scope + for i, s := range scopes { + if s == scope { + scopes[i] = "" + } + } + + if include { + // find a free slot if there is one + for i, s := range scopes { + if s == "" { + scopes[i] = scope + o.logCallers = strings.Join(scopes, ",") + return + } + } + + scopes = append(scopes, scope) + } + + o.logCallers = strings.Join(scopes, ",") +} + +// GetLogCallers returns whether the caller's source code location is output for a given scope. +func (o *Options) GetLogCallers(scope string) bool { + scopes := strings.Split(o.logCallers, ",") + + for _, s := range scopes { + if s == scope { + return true + } + } + + return false +} + +func convertScopedLevel(sl string) (string, Level, error) { + var s string + var l string + + pieces := strings.Split(sl, ":") + if len(pieces) == 1 { + s = DefaultScopeName + l = pieces[0] + } else if len(pieces) == 2 { + s = pieces[0] + l = pieces[1] + } else { + return "", NoneLevel, fmt.Errorf("invalid output level format '%s'", sl) + } + + level, ok := stringToLevel[l] + if !ok { + return "", NoneLevel, fmt.Errorf("invalid output level '%s'", sl) + } + + return s, level, nil +} diff --git a/common/log/scope.go b/common/log/scope.go new file mode 100644 index 000000000..d1688f486 --- /dev/null +++ b/common/log/scope.go @@ -0,0 +1,317 @@ +// Copyright 2018 Istio Authors +// +// 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. + +package log + +import ( + "fmt" + "runtime" + "strings" + "sync" + "sync/atomic" + "time" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// Scope let's you log data for an area of code, enabling the user full control over +// the level of logging output produced. +type Scope struct { + // immutable, set at creation + name string + nameToEmit string + description string + callerSkip int + + // set by the Configure method and adjustable dynamically + outputLevel atomic.Value + stackTraceLevel atomic.Value + logCallers atomic.Value +} + +var scopes = make(map[string]*Scope) +var lock = sync.RWMutex{} + +// RegisterScope registers a new logging scope. If the same name is used multiple times +// for a single process, the same Scope struct is returned. +// +// Scope names cannot include colons, commas, or periods. +func RegisterScope(name string, description string, callerSkip int) *Scope { + if strings.ContainsAny(name, ":,.") { + panic(fmt.Sprintf("scope name %s is invalid, it cannot contain colons, commas, or periods", name)) + } + + lock.Lock() + defer lock.Unlock() + + s, ok := scopes[name] + if !ok { + s = &Scope{ + name: name, + description: description, + callerSkip: callerSkip, + } + s.SetOutputLevel(InfoLevel) + s.SetStackTraceLevel(NoneLevel) + s.SetLogCallers(false) + + if name != DefaultScopeName { + s.nameToEmit = name + } + + scopes[name] = s + } + + return s +} + +// FindScope returns a previously registered scope, or nil if the named scope wasn't previously registered +func FindScope(scope string) *Scope { + lock.RLock() + defer lock.RUnlock() + + s := scopes[scope] + return s +} + +// Scopes returns a snapshot of the currently defined set of scopes +func Scopes() map[string]*Scope { + lock.RLock() + defer lock.RUnlock() + + s := make(map[string]*Scope, len(scopes)) + for k, v := range scopes { + s[k] = v + } + + return s +} + +// Fatal outputs a message at fatal level. +func (s *Scope) Fatal(msg string, fields ...zapcore.Field) { + if s.GetOutputLevel() >= FatalLevel { + s.emit(zapcore.FatalLevel, s.GetStackTraceLevel() >= FatalLevel, msg, fields) + } +} + +// Fatala uses fmt.Sprint to construct and log a message at fatal level. +func (s *Scope) Fatala(args ...interface{}) { + if s.GetOutputLevel() >= FatalLevel { + s.emit(zapcore.FatalLevel, s.GetStackTraceLevel() >= FatalLevel, fmt.Sprint(args...), nil) + } +} + +// Fatalf uses fmt.Sprintf to construct and log a message at fatal level. +func (s *Scope) Fatalf(template string, args ...interface{}) { + if s.GetOutputLevel() >= FatalLevel { + msg := template + if len(args) > 0 { + msg = fmt.Sprintf(template, args...) + } + s.emit(zapcore.FatalLevel, s.GetStackTraceLevel() >= FatalLevel, msg, nil) + } +} + +// FatalEnabled returns whether output of messages using this scope is currently enabled for fatal-level output. +func (s *Scope) FatalEnabled() bool { + return s.GetOutputLevel() >= FatalLevel +} + +// Error outputs a message at error level. +func (s *Scope) Error(msg string, fields ...zapcore.Field) { + if s.GetOutputLevel() >= ErrorLevel { + s.emit(zapcore.ErrorLevel, s.GetStackTraceLevel() >= ErrorLevel, msg, fields) + } +} + +// Errora uses fmt.Sprint to construct and log a message at error level. +func (s *Scope) Errora(args ...interface{}) { + if s.GetOutputLevel() >= ErrorLevel { + s.emit(zapcore.ErrorLevel, s.GetStackTraceLevel() >= ErrorLevel, fmt.Sprint(args...), nil) + } +} + +// Errorf uses fmt.Sprintf to construct and log a message at error level. +func (s *Scope) Errorf(template string, args ...interface{}) { + if s.GetOutputLevel() >= ErrorLevel { + msg := template + if len(args) > 0 { + msg = fmt.Sprintf(template, args...) + } + s.emit(zapcore.ErrorLevel, s.GetStackTraceLevel() >= ErrorLevel, msg, nil) + } +} + +// ErrorEnabled returns whether output of messages using this scope is currently enabled for error-level output. +func (s *Scope) ErrorEnabled() bool { + return s.GetOutputLevel() >= ErrorLevel +} + +// Warn outputs a message at warn level. +func (s *Scope) Warn(msg string, fields ...zapcore.Field) { + if s.GetOutputLevel() >= WarnLevel { + s.emit(zapcore.WarnLevel, s.GetStackTraceLevel() >= ErrorLevel, msg, fields) + } +} + +// Warna uses fmt.Sprint to construct and log a message at warn level. +func (s *Scope) Warna(args ...interface{}) { + if s.GetOutputLevel() >= WarnLevel { + s.emit(zapcore.WarnLevel, s.GetStackTraceLevel() >= ErrorLevel, fmt.Sprint(args...), nil) + } +} + +// Warnf uses fmt.Sprintf to construct and log a message at warn level. +func (s *Scope) Warnf(template string, args ...interface{}) { + if s.GetOutputLevel() >= WarnLevel { + msg := template + if len(args) > 0 { + msg = fmt.Sprintf(template, args...) + } + s.emit(zapcore.WarnLevel, s.GetStackTraceLevel() >= ErrorLevel, msg, nil) + } +} + +// WarnEnabled returns whether output of messages using this scope is currently enabled for warn-level output. +func (s *Scope) WarnEnabled() bool { + return s.GetOutputLevel() >= WarnLevel +} + +// Info outputs a message at info level. +func (s *Scope) Info(msg string, fields ...zapcore.Field) { + if s.GetOutputLevel() >= InfoLevel { + s.emit(zapcore.InfoLevel, s.GetStackTraceLevel() >= ErrorLevel, msg, fields) + } +} + +// Infoa uses fmt.Sprint to construct and log a message at info level. +func (s *Scope) Infoa(args ...interface{}) { + if s.GetOutputLevel() >= InfoLevel { + s.emit(zapcore.InfoLevel, s.GetStackTraceLevel() >= ErrorLevel, fmt.Sprint(args...), nil) + } +} + +// Infof uses fmt.Sprintf to construct and log a message at info level. +func (s *Scope) Infof(template string, args ...interface{}) { + if s.GetOutputLevel() >= InfoLevel { + msg := template + if len(args) > 0 { + msg = fmt.Sprintf(template, args...) + } + s.emit(zapcore.InfoLevel, s.GetStackTraceLevel() >= ErrorLevel, msg, nil) + } +} + +// InfoEnabled returns whether output of messages using this scope is currently enabled for info-level output. +func (s *Scope) InfoEnabled() bool { + return s.GetOutputLevel() >= InfoLevel +} + +// Debug outputs a message at debug level. +func (s *Scope) Debug(msg string, fields ...zapcore.Field) { + if s.GetOutputLevel() >= DebugLevel { + s.emit(zapcore.DebugLevel, s.GetStackTraceLevel() >= ErrorLevel, msg, fields) + } +} + +// Debuga uses fmt.Sprint to construct and log a message at debug level. +func (s *Scope) Debuga(args ...interface{}) { + if s.GetOutputLevel() >= DebugLevel { + s.emit(zapcore.DebugLevel, s.GetStackTraceLevel() >= ErrorLevel, fmt.Sprint(args...), nil) + } +} + +// Debugf uses fmt.Sprintf to construct and log a message at debug level. +func (s *Scope) Debugf(template string, args ...interface{}) { + if s.GetOutputLevel() >= DebugLevel { + msg := template + if len(args) > 0 { + msg = fmt.Sprintf(template, args...) + } + s.emit(zapcore.DebugLevel, s.GetStackTraceLevel() >= ErrorLevel, msg, nil) + } +} + +// DebugEnabled returns whether output of messages using this scope is currently enabled for debug-level output. +func (s *Scope) DebugEnabled() bool { + return s.GetOutputLevel() >= DebugLevel +} + +// Name returns this scope's name. +func (s *Scope) Name() string { + return s.name +} + +// Description returns this scope's description +func (s *Scope) Description() string { + return s.description +} + +const callerSkipOffset = 2 + +func (s *Scope) emit(level zapcore.Level, dumpStack bool, msg string, fields []zapcore.Field) { + e := zapcore.Entry{ + Message: msg, + Level: level, + Time: time.Now(), + LoggerName: s.nameToEmit, + } + + if s.GetLogCallers() { + e.Caller = zapcore.NewEntryCaller(runtime.Caller(s.callerSkip + callerSkipOffset)) + } + + if dumpStack { + e.Stack = zap.Stack("").String + } + + pt := funcs.Load().(patchTable) + if pt.write != nil { + if err := pt.write(e, fields); err != nil { + _, _ = fmt.Fprintf(pt.errorSink, "%v log write error: %v\n", time.Now(), err) + _ = pt.errorSink.Sync() + } + } +} + +// SetOutputLevel adjusts the output level associated with the scope. +func (s *Scope) SetOutputLevel(l Level) { + s.outputLevel.Store(l) +} + +// GetOutputLevel returns the output level associated with the scope. +func (s *Scope) GetOutputLevel() Level { + return s.outputLevel.Load().(Level) +} + +// SetStackTraceLevel adjusts the stack tracing level associated with the scope. +func (s *Scope) SetStackTraceLevel(l Level) { + s.stackTraceLevel.Store(l) +} + +// GetStackTraceLevel returns the stack tracing level associated with the scope. +func (s *Scope) GetStackTraceLevel() Level { + return s.stackTraceLevel.Load().(Level) +} + +// SetLogCallers adjusts the output level associated with the scope. +func (s *Scope) SetLogCallers(logCallers bool) { + s.logCallers.Store(logCallers) +} + +// GetLogCallers returns the output level associated with the scope. +func (s *Scope) GetLogCallers() bool { + return s.logCallers.Load().(bool) +} diff --git a/common/model/instance.go b/common/model/instance.go new file mode 100644 index 000000000..6f03a6687 --- /dev/null +++ b/common/model/instance.go @@ -0,0 +1,302 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package model + +import ( + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/golang/protobuf/ptypes/wrappers" + "time" +) + +// 组合了api的Instance对象 +type Instance struct { + Proto *api.Instance + ServiceID string + ServicePlatformID string + Valid bool + ModifyTime time.Time +} + +// get id +func (i *Instance) ID() string { + if i.Proto == nil { + return "" + } + return i.Proto.GetId().GetValue() +} + +// get service +func (i *Instance) Service() string { + if i.Proto == nil { + return "" + } + return i.Proto.GetService().GetValue() +} + +// get namespace +func (i *Instance) Namespace() string { + if i.Proto == nil { + return "" + } + return i.Proto.GetNamespace().GetValue() +} + +// get vpcid +func (i *Instance) VpcID() string { + if i.Proto == nil { + return "" + } + return i.Proto.GetVpcId().GetValue() +} + +// get host +func (i *Instance) Host() string { + if i.Proto == nil { + return "" + } + return i.Proto.GetHost().GetValue() +} + +// get port +func (i *Instance) Port() uint32 { + if i.Proto == nil { + return 0 + } + return i.Proto.GetPort().GetValue() +} + +// get protocol +func (i *Instance) Protocol() string { + if i.Proto == nil { + return "" + } + return i.Proto.GetProtocol().GetValue() +} + +// get version +func (i *Instance) Version() string { + if i.Proto == nil { + return "" + } + return i.Proto.GetVersion().GetValue() +} + +// get priority +func (i *Instance) Priority() uint32 { + if i.Proto == nil { + return 0 + } + return i.Proto.GetPriority().GetValue() +} + +// get weight +func (i *Instance) Weight() uint32 { + if i.Proto == nil { + return 0 + } + return i.Proto.GetWeight().GetValue() +} + +// get enable health check +func (i *Instance) EnableHealthCheck() bool { + if i.Proto == nil { + return false + } + return i.Proto.GetEnableHealthCheck().GetValue() +} + +// get health check +func (i *Instance) HealthCheck() *api.HealthCheck { + if i.Proto == nil { + return nil + } + return i.Proto.GetHealthCheck() +} + +// get healthy +func (i *Instance) Healthy() bool { + if i.Proto == nil { + return false + } + return i.Proto.GetHealthy().GetValue() +} + +// get isolate +func (i *Instance) Isolate() bool { + if i.Proto == nil { + return false + } + return i.Proto.GetIsolate().GetValue() +} + +// get location +func (i *Instance) Location() *api.Location { + if i.Proto == nil { + return nil + } + return i.Proto.GetLocation() +} + +// get metadata +func (i *Instance) Metadata() map[string]string { + if i.Proto == nil { + return nil + } + return i.Proto.GetMetadata() +} + +// get logic set +func (i *Instance) LogicSet() string { + if i.Proto == nil { + return "" + } + return i.Proto.GetLogicSet().GetValue() +} + +// get ctime +func (i *Instance) Ctime() string { + if i.Proto == nil { + return "" + } + return i.Proto.GetCtime().GetValue() +} + +// get mtime +func (i *Instance) Mtime() string { + if i.Proto == nil { + return "" + } + return i.Proto.GetMtime().GetValue() +} + +// get revision +func (i *Instance) Revision() string { + if i.Proto == nil { + return "" + } + return i.Proto.GetRevision().GetValue() +} + +// get service token +func (i *Instance) ServiceToken() string { + if i.Proto == nil { + return "" + } + return i.Proto.GetServiceToken().GetValue() +} + +// malloc proto if proto is null +func (i *Instance) MallocProto() { + if i.Proto == nil { + i.Proto = &api.Instance{} + } +} + +// 对应store层(database)的对象 +type InstanceStore struct { + ID string + ServiceID string + Host string + VpcID string + Port uint32 + Protocol string + Version string + HealthStatus int + Isolate int + Weight uint32 + EnableHealthCheck int + CheckType int32 + TTL uint32 + Priority uint32 + Revision string + LogicSet string + Region string + Zone string + Campus string + Meta map[string]string + Flag int + CreateTime int64 + ModifyTime int64 +} + +// 包含服务名的store信息 +type ExpandInstanceStore struct { + ServiceName string + Namespace string + ServiceToken string + ServicePlatformID string + ServiceInstance *InstanceStore +} + +// store的数据转换为组合了api的数据结构 +func Store2Instance(is *InstanceStore) *Instance { + ins := &Instance{ + Proto: &api.Instance{ + Id: &wrappers.StringValue{Value: is.ID}, + VpcId: &wrappers.StringValue{Value: is.VpcID}, + Host: &wrappers.StringValue{Value: is.Host}, + Port: &wrappers.UInt32Value{Value: is.Port}, + Protocol: &wrappers.StringValue{Value: is.Protocol}, + Version: &wrappers.StringValue{Value: is.Version}, + Priority: &wrappers.UInt32Value{Value: is.Priority}, + Weight: &wrappers.UInt32Value{Value: is.Weight}, + EnableHealthCheck: &wrappers.BoolValue{Value: int2bool(is.EnableHealthCheck)}, + Healthy: &wrappers.BoolValue{Value: int2bool(is.HealthStatus)}, + Isolate: &wrappers.BoolValue{Value: int2bool(is.Isolate)}, + Metadata: is.Meta, + LogicSet: &wrappers.StringValue{Value: is.LogicSet}, + Ctime: &wrappers.StringValue{Value: int64Time2String(is.CreateTime)}, + Mtime: &wrappers.StringValue{Value: int64Time2String(is.ModifyTime)}, + Revision: &wrappers.StringValue{Value: is.Revision}, + }, + ServiceID: is.ServiceID, + Valid: flag2valid(is.Flag), + ModifyTime: time.Unix(is.ModifyTime, 0), + } + // 如果不存在checkType,即checkType==-1。HealthCheck置为nil + if is.CheckType != -1 { + ins.Proto.HealthCheck = &api.HealthCheck{ + Type: api.HealthCheck_HealthCheckType(is.CheckType), + Heartbeat: &api.HeartbeatHealthCheck{ + Ttl: &wrappers.UInt32Value{Value: is.TTL}, + }, + } + } + // 如果location不为空,那么填充一下location + if is.Region != "" { + ins.Proto.Location = &api.Location{ + Region: &wrappers.StringValue{Value: is.Region}, + Zone: &wrappers.StringValue{Value: is.Zone}, + Campus: &wrappers.StringValue{Value: is.Campus}, + } + } + + return ins +} + +// 扩展store转换 +func ExpandStore2Instance(es *ExpandInstanceStore) *Instance { + out := Store2Instance(es.ServiceInstance) + out.Proto.Service = &wrappers.StringValue{Value: es.ServiceName} + out.Proto.Namespace = &wrappers.StringValue{Value: es.Namespace} + if es.ServiceToken != "" { + out.Proto.ServiceToken = &wrappers.StringValue{Value: es.ServiceToken} + } + out.ServicePlatformID = es.ServicePlatformID + return out +} diff --git a/common/model/l5.go b/common/model/l5.go new file mode 100644 index 000000000..91e01a00b --- /dev/null +++ b/common/model/l5.go @@ -0,0 +1,99 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package model + +/** + * @brief 访问关系 + */ +type Route struct { + IP uint32 + ModID uint32 + CmdID uint32 + SetID string + Valid bool + Flow uint32 +} + +/** + * @brief 有状态规则路由策略信息 + */ +type Policy struct { + ModID uint32 + Div uint32 + Mod uint32 + Valid bool + Flow uint32 +} + +/** + * @brief 有状态规则路由分段信息 + */ +type Section struct { + ModID uint32 + From uint32 + To uint32 + Xid uint32 + Valid bool + Flow uint32 +} + +/** + * @brief IP的区域信息 + */ +type IPConfig struct { + IP uint32 + AreaID uint32 + CityID uint32 + IdcID uint32 + Valid bool + Flow uint32 +} + +/** + * @brief sid信息 + */ +type Sid struct { + ModID uint32 + CmdID uint32 +} + +/** + * @brief 被调信息,对应t_server+t_ip_config + */ +type Callee struct { + ModID uint32 + CmdID uint32 + SetID string + IP uint32 + Port uint32 + Weight uint32 + Location *Location + //AreaID uint32 + //CityID uint32 + //IdcID uint32 +} + +/** + * @brief sid信息,对应t_sid表 + */ +type SidConfig struct { + ModID uint32 + CmdID uint32 + Name string + Policy uint32 +} diff --git a/common/model/naming.go b/common/model/naming.go new file mode 100644 index 000000000..410ddca3f --- /dev/null +++ b/common/model/naming.go @@ -0,0 +1,416 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package model + +import ( + "time" + + v1 "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/golang/protobuf/ptypes/wrappers" +) + +/** + * @brief 命名空间结构体 + */ +type Namespace struct { + Name string + Comment string + Token string + Owner string + Valid bool + CreateTime time.Time + ModifyTime time.Time +} + +/** + * @brief 业务集 + */ +type Business struct { + ID string + Name string + Token string + Owner string + Valid bool + CreateTime time.Time + ModifyTime time.Time +} + +/** + * @brief 服务数据 + */ +type Service struct { + ID string + Name string + Namespace string + Business string + Ports string + Meta map[string]string + Comment string + Department string + CmdbMod1 string + CmdbMod2 string + CmdbMod3 string + Token string + Owner string + Revision string + Reference string + ReferFilter string + PlatformID string + Valid bool + CreateTime time.Time + ModifyTime time.Time +} + +/** + * @brief 服务名 + */ +type ServiceKey struct { + Namespace string + Name string +} + +// 便捷函数封装 +func (s *Service) IsAlias() bool { + if s.Reference != "" { + return true + } + + return false +} + +// 服务别名结构体 +type ServiceAlias struct { + ID string + Alias string + ServiceID string + Service string + Namespace string + Owner string + Comment string + CreateTime time.Time + ModifyTime time.Time +} + +/** + * @brief 服务下实例的权重类型 + */ +type WeightType uint32 + +const ( + // 动态权重 + WEIGHTDYNAMIC WeightType = 0 + + // 静态权重 + WEIGHTSTATIC WeightType = 1 +) + +var WeightString = map[WeightType]string{ + WEIGHTDYNAMIC: "dynamic", + WEIGHTSTATIC: "static", +} + +var WeightEnum = map[string]WeightType{ + "dynamic": WEIGHTDYNAMIC, + "static": WEIGHTSTATIC, +} + +/** + * @brief 地域信息,对应数据库字段 + */ +type LocationStore struct { + IP string + Region string + Zone string + Campus string + RegionID uint32 + ZoneID uint32 + CampusID uint32 + Flag int + ModifyTime int64 +} + +// cmdb信息,对应内存结构体 +type Location struct { + Proto *v1.Location + RegionID uint32 + ZoneID uint32 + CampusID uint32 + Valid bool +} + +// 转成内存数据结构 +func Store2Location(s *LocationStore) *Location { + return &Location{ + Proto: &v1.Location{ + Region: &wrappers.StringValue{Value: s.Region}, + Zone: &wrappers.StringValue{Value: s.Zone}, + Campus: &wrappers.StringValue{Value: s.Campus}, + }, + RegionID: s.RegionID, + ZoneID: s.ZoneID, + CampusID: s.CampusID, + Valid: flag2valid(s.Flag), + } +} + +/** + * @brief 客户端上报信息表 + */ +type Client struct { + VpcID string + Host string + Typ int + Version string + CreateTime time.Time + ModifyTime time.Time +} + +/** + * @brief 路由配置 + */ +type RoutingConfig struct { + ID string + InBounds string + OutBounds string + Revision string + Valid bool + CreateTime time.Time + ModifyTime time.Time +} + +/** + * @brief 路由配置的扩展结构体 + */ +type ExtendRoutingConfig struct { + ServiceName string + NamespaceName string + Config *RoutingConfig +} + +/** + * @brief 限流规则 + */ +type RateLimit struct { + ID string + ServiceID string + ClusterID string + Labels string + Priority uint32 + Rule string + Revision string + Valid bool + CreateTime time.Time + ModifyTime time.Time +} + +/** + * @brief 包含服务信息的限流规则 + */ +type ExtendRateLimit struct { + ServiceName string + NamespaceName string + RateLimit *RateLimit +} + +/** + * @brief 包含最新版本号的限流规则 + */ +type RateLimitRevision struct { + ServiceID string + LastRevision string +} + +/** + * @brief 熔断规则 + */ +type CircuitBreaker struct { + ID string + Version string + Name string + Namespace string + Business string + Department string + Comment string + Inbounds string + Outbounds string + Token string + Owner string + Revision string + Valid bool + CreateTime time.Time + ModifyTime time.Time +} + +/** + * @brief 与服务关系绑定的熔断规则 + */ +type ServiceWithCircuitBreaker struct { + ServiceID string + CircuitBreaker *CircuitBreaker + Valid bool + CreateTime time.Time + ModifyTime time.Time +} + +/** + * @brief 熔断规则绑定关系 + */ +type CircuitBreakerRelation struct { + ServiceID string + RuleID string + RuleVersion string + Valid bool + CreateTime time.Time + ModifyTime time.Time +} + +/** + * @brief 返回给控制台的熔断规则及服务数据 + */ +type CircuitBreakerDetail struct { + Total uint32 + CircuitBreakerInfos []*CircuitBreakerInfo +} + +/** + * @brief 熔断规则及绑定服务 + */ +type CircuitBreakerInfo struct { + CircuitBreaker *CircuitBreaker + Services []*Service +} + +/** + * @brief 平台信息 + */ +type Platform struct { + ID string + Name string + Domain string + QPS uint32 + Token string + Owner string + Department string + Comment string + Valid bool + CreateTime time.Time + ModifyTime time.Time +} + +// 整数转换为bool值 +func int2bool(entry int) bool { + if entry == 0 { + return false + } + return true +} + +// store的flag转换为valid +// flag==1为无效,其他情况为有效 +func flag2valid(flag int) bool { + if flag == 1 { + return false + } + return true + +} + +// int64的时间戳转为字符串时间 +func int64Time2String(t int64) string { + return time.Unix(t, 0).Format("2006-01-02 15:04:05") +} + +// 操作类型 +type OperationType string + +// 定义包含的操作类型 +const ( + // 新建 + OCreate OperationType = "Create" + + // 删除 + ODelete OperationType = "Delete" + + // 更新 + OUpdate OperationType = "Update" + + // 更新隔离状态 + OUpdateIsolate OperationType = "UpdateIsolate" + + // 查看token + OGetToken OperationType = "GetToken" // nolint + + // 更新token + OUpdateToken OperationType = "UpdateToken" // nolint +) + +// 操作资源 +type Resource string + +// 定义包含的资源类型 +const ( + RNamespace Resource = "Namespace" + RService Resource = "Service" + RRouting Resource = "Routing" + RInstance Resource = "Instance" + RRateLimit Resource = "RateLimit" + RMeshResource Resource = "MeshResource" + RMesh Resource = "Mesh" + RMeshService Resource = "MeshService" + RFluxRateLimit Resource = "FluxRateLimit" +) + +// 资源类型 +type ResourceType int + +const ( + // 网格类型资源 + MeshType ResourceType = 0 + // 北极星服务类型资源 + ServiceType ResourceType = 1 +) + +var ResourceTypeMap = map[Resource]ResourceType{ + RNamespace: ServiceType, + RService: ServiceType, + RRouting: ServiceType, + RInstance: ServiceType, + RRateLimit: ServiceType, + RMesh: MeshType, + RMeshResource: MeshType, + RMeshService: MeshType, +} + +// 获取资源的大类型 +func GetResourceType(r Resource) ResourceType { + return ResourceTypeMap[r] +} + +// 操作记录entry +type RecordEntry struct { + ResourceType Resource + OperationType OperationType + Namespace string + Service string + MeshID string + MeshName string + Context string + Operator string + Revision string + CreateTime time.Time +} diff --git a/common/redispool/redisPool.go b/common/redispool/redisPool.go new file mode 100644 index 000000000..73c9dd1d0 --- /dev/null +++ b/common/redispool/redisPool.go @@ -0,0 +1,356 @@ +/* + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2020. Lorem THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package redispool + +import ( + "fmt" + "github.com/gomodule/redigo/redis" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" + "hash/crc32" + "math/rand" + "strconv" + "sync" + "sync/atomic" + "time" +) + +const ( + Get = 0 + Set = 1 + Del = 2 +) + +/** + * @brief ckv任务请求结构体 + */ +type Task struct { + taskType int + id string + status int + beatTime int64 + respCh chan *Resp +} + +/** + * @brief ckv任务结果 + */ +type Resp struct { + Value string + Err error + Local bool +} + +/** + * @brief ckv连接池元数据 + */ +type MetaData struct { + insConnNum int + kvPasswd string + localHost string + MaxIdle int + IdleTimeout int +} + +/** + * @brief ckv节点结构体 + */ +type Instance struct { + // 节点在连接池中的序号 + index uint32 + addr string + redisPool *redis.Pool + ch []chan *Task + stopCh chan bool +} + +/** + * @brief ckv连接池结构体 + */ +type Pool struct { + mu sync.Mutex + meta *MetaData + instances []*Instance + instanceNum int32 +} + +/** + * @brief 初始化一个redis连接池实例 + */ +func NewPool(insConnNum int, kvPasswd, localHost string, redisInstances []*model.Instance, + maxIdle, idleTimeout int) (*Pool, error) { + var instances []*Instance + if len(redisInstances) > 0 { + for _, instance := range redisInstances { + instance := &Instance{ + redisPool: genRedisPool(insConnNum, kvPasswd, instance, maxIdle, idleTimeout), + stopCh: make(chan bool), + } + instance.ch = make([]chan *Task, 0, 100*insConnNum) + for i := 0; i < 100*insConnNum; i++ { + instance.ch = append(instance.ch, make(chan *Task)) + } + rand.Seed(time.Now().Unix()) + // 从一个随机位置开始,防止所有server都从一个ckv开始 + instance.index = uint32(rand.Intn(100 * insConnNum)) + instances = append(instances, instance) + } + } + + pool := &Pool{ + meta: &MetaData{ + insConnNum: insConnNum, + kvPasswd: kvPasswd, + localHost: localHost, + MaxIdle: maxIdle, + IdleTimeout: idleTimeout, + }, + instances: instances, + instanceNum: int32(len(redisInstances)), + } + + return pool, nil +} + +func genRedisPool(insConnNum int, kvPasswd string, instance *model.Instance, maxIdle, idleTimeout int) *redis.Pool { + pool := &redis.Pool{ + MaxIdle: maxIdle, + MaxActive: 0, + IdleTimeout: time.Duration(idleTimeout), + Dial: func() (redis.Conn, error) { + conn, err := redis.Dial("tcp", instance.Host()+":"+ + strconv.Itoa(int(instance.Port())), redis.DialPassword(kvPasswd)) + if err != nil { + log.Infof("ERROR: fail init redis: %s", err.Error()) + return nil, err + } + return conn, err + }, + TestOnBorrow: func(c redis.Conn, t time.Time) error { + _, err := c.Do("PING") + return err + }, + } + return pool +} + +/** + * @brief 更新ckv连接池中的节点 + * 重新建立ckv连接 + * 对业务无影响 + */ +func (p *Pool) Update(newKvInstances []*model.Instance) error { + p.mu.Lock() + defer p.mu.Unlock() + + change := len(newKvInstances) - int(atomic.LoadInt32(&p.instanceNum)) + log.Infof("[ckv] update, old ins num:%d, new ins num:%d, change:%d", p.instanceNum, len(newKvInstances), change) + + // 新建一个pool.instances数组 + var instances []*Instance + for _, instance := range newKvInstances { + instance := &Instance{ + redisPool: genRedisPool(p.meta.insConnNum, p.meta.kvPasswd, instance, p.meta.MaxIdle, p.meta.IdleTimeout), + stopCh: make(chan bool), + } + instance.ch = make([]chan *Task, 0, 100*p.meta.insConnNum) + for i := 0; i < 100*p.meta.insConnNum; i++ { + instance.ch = append(instance.ch, make(chan *Task)) + } + instance.index = uint32(rand.Intn(100 * p.meta.insConnNum)) + instances = append(instances, instance) + } + + // 关闭前一个连接池 + for i := 0; i < len(p.instances); i++ { + close(p.instances[i].stopCh) + time.Sleep(10 * time.Millisecond) + for j := 0; j < len(p.instances[i].ch); j++ { + close(p.instances[i].ch[j]) + } + err := p.instances[i].redisPool.Close() + if err != nil { + log.Errorf("close redis pool :%s", err) + } + } + + time.Sleep(10 * time.Millisecond) + // 结构体属性重新赋值,并重新开始消费 + p.instances = instances + atomic.StoreInt32(&p.instanceNum, int32(len(p.instances))) + + for i := 0; i < len(p.instances); i++ { + for k := 0; k < len(p.instances[i].ch); k++ { + go p.worker(i, k) + } + } + + log.Infof("[redis] update success, node num:%d", len(p.instances)) + + return nil +} + +func (p *Pool) checkHasKvInstances(ch chan *Resp) bool { + if atomic.LoadInt32(&p.instanceNum) == 0 { + go func() { + ch <- &Resp{ + Local: true, + } + }() + return true + } + return false +} + +/** + * @brief 使用连接池,向redis发起Get请求 + */ +func (p *Pool) Get(id string, ch chan *Resp) { // nolint + if p.checkHasKvInstances(ch) { + return + } + task := &Task{ + taskType: Get, + id: id, + respCh: ch, + } + + insIndex, chIndex := p.genInsChIndex(id) + p.instances[insIndex].ch[chIndex] <- task +} + +/** + * @brief 使用连接池,向redis发起Set请求 + */ +func (p *Pool) Set(id string, status int, beatTime int64, ch chan *Resp) { // nolint + if p.checkHasKvInstances(ch) { + return + } + task := &Task{ + taskType: Set, + id: id, + status: status, + beatTime: beatTime, + respCh: ch, + } + + insIndex, chIndex := p.genInsChIndex(id) + p.instances[insIndex].ch[chIndex] <- task +} + +/** + * @brief 使用连接池,向redis发起Del请求 + */ +func (p *Pool) Del(id string, ch chan *Resp) { // nolint + task := &Task{ + taskType: Del, + id: id, + respCh: ch, + } + + insIndex, chIndex := p.genInsChIndex(id) + p.instances[insIndex].ch[chIndex] <- task +} + +/** + * @brief 生成index公共方法 + */ +func (p *Pool) genInsChIndex(id string) (int, uint32) { + insIndex := String(id) % int(atomic.LoadInt32(&p.instanceNum)) + + chIndex := atomic.AddUint32(&p.instances[insIndex].index, 1) % uint32(p.meta.insConnNum*100) + return insIndex, chIndex +} + +/** + * @brief 启动ckv连接池工作 + */ +func (p *Pool) Start() { + p.mu.Lock() + for i := 0; i < len(p.instances); i++ { + for k := 0; k < len(p.instances[i].ch); k++ { + go p.worker(i, k) + } + } + p.mu.Unlock() + log.Infof("[redis] redis pool start") +} + +/** + * @brief 接收任务worker + */ +func (p *Pool) worker(instanceIndex, chIndex int) { + for { + select { + case task := <-p.instances[instanceIndex].ch[chIndex]: + p.handleTask(task, instanceIndex) + case <-p.instances[instanceIndex].stopCh: + return + } + } +} + +/** + * @brief 任务处理函数 + */ +func (p *Pool) handleTask(task *Task, index int) { + if task == nil { + log.Errorf("receive nil task") + return + } + con := p.instances[index].redisPool.Get() + defer con.Close() + var resp Resp + switch task.taskType { + case Get: + value, err := redis.String(con.Do("GET", task.id)) + if err != nil { + resp.Err = err + } else { + resp.Value = value + } + task.respCh <- &resp + case Set: + value := fmt.Sprintf("%d:%d:%s", task.status, task.beatTime, p.meta.localHost) + _, err := con.Do("SET", task.id, value) + if err != nil { + resp.Err = err + } + task.respCh <- &resp + case Del: + _, err := con.Do("DEL", task.id) + if err != nil { + resp.Err = err + } + task.respCh <- &resp + default: + log.Errorf("[ckv] set key:%s type:%d wrong", task.id, task.taskType) + } +} + +// 字符串转hash值 +func String(s string) int { + v := int(crc32.ChecksumIEEE([]byte(s))) + if v >= 0 { + return v + } + if -v >= 0 { + return -v + } + return 0 +} diff --git a/common/timewheel/README.md b/common/timewheel/README.md new file mode 100644 index 000000000..dc9c5e674 --- /dev/null +++ b/common/timewheel/README.md @@ -0,0 +1,52 @@ +# timewheel + +为心跳上报定制实现的、线程安全的多层时间轮,简化功能以追求更好的性能。 + +基于链表和ticker实现。 +只有插入操作:插入任务后就必须执行。 + +## 测试/压测记录 +覆盖率: 90%+ + +1. 功能测试:正常 + +2. 压力测试: + 16c 16g机器 + + 开启5w个协程并发往同一个slot加10w个任务,单个操作用时280ns + + ``` + goos: windows + goarch: amd64 + pkg: github.com/polarismesh/polaris-server/common/timewheel + BenchmarkAddTask1-8 100000 280 ns/op 103 B/op 2 allocs/op + PASS + ``` + + 开启16w个协程并发往同一个slot加500w个任务,单个操作用时376ns + + ``` + goos: windows + goarch: amd64 + pkg: github.com/polarismesh/polaris-server/common/timewheel + BenchmarkAddTask1-8 5000000 376 ns/op 97 B/op 2 allocs/op + PASS + ``` + + 对比nosix/timewheel: + ``` + goos: linux + goarch: amd64 + BenchmarkAddTask1-8 100000 2021 ns/op 721 B/op 3 allocs/op + --- BENCH: BenchmarkAddTask1-8 + timewheel_test.go:40: N:100000, use time:255.514068ms + timewheel_test.go:40: N:100000, use time:324.360456ms + timewheel_test.go:40: N:100000, use time:402.377702ms + timewheel_test.go:40: N:100000, use time:419.118132ms + timewheel_test.go:40: N:100000, use time:195.719517ms + timewheel_test.go:40: N:100000, use time:215.3815ms + timewheel_test.go:40: N:100000, use time:176.733241ms + timewheel_test.go:40: N:100000, use time:188.846803ms + timewheel_test.go:40: N:100000, use time:164.038559ms + timewheel_test.go:40: N:100000, use time:200.684048ms + ``` \ No newline at end of file diff --git a/common/timewheel/timewheel.go b/common/timewheel/timewheel.go new file mode 100644 index 000000000..ddc231f7a --- /dev/null +++ b/common/timewheel/timewheel.go @@ -0,0 +1,191 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package timewheel + +import ( + "container/list" + "errors" + "sync" + "time" + + "github.com/polarismesh/polaris-server/common/log" +) + +// a simple routine-safe timewheel, can only add task +// not support update/delete + +/** + * @brief 时间轮结构体 + */ +type TimeWheel struct { + name string + interval time.Duration + ticker *time.Ticker + currentPos int + slots []*list.List + locks []sync.Mutex + slotNum int + stopCh chan bool +} + +/** + * @brief 时间轮回调函数定义 + */ +type Callback func(interface{}) + +/** + * @brief 时间轮任务结构体 + */ +type Task struct { + delayTime time.Duration + circle int + callback Callback + taskData interface{} +} + +/** + * @brief 初始化时间轮 + */ +func New(interval time.Duration, slotNum int, name string) *TimeWheel { + if interval <= 0 || slotNum <= 0 { + return nil + } + + timewheel := &TimeWheel{ + name: name, + interval: interval, + slots: make([]*list.List, slotNum), + locks: make([]sync.Mutex, slotNum), + currentPos: 0, + slotNum: slotNum, + stopCh: make(chan bool), + } + + for i := 0; i < slotNum; i++ { + timewheel.slots[i] = list.New() + } + + return timewheel +} + +/** + * @brief 启动时间轮 + */ +func (tw *TimeWheel) Start() { + tw.ticker = time.NewTicker(tw.interval) + go tw.start() +} + +/** + * @brief 停止时间轮 + */ +func (tw *TimeWheel) Stop() { + tw.stopCh <- true +} + +/** + * @brief 时间轮运转函数 + */ +func (tw *TimeWheel) start() { + for { + select { + case <-tw.ticker.C: + tw.taskRunner() + case <-tw.stopCh: + tw.ticker.Stop() + return + } + } +} + +/** + * @brief 时间轮到期处理函数 + */ +func (tw *TimeWheel) taskRunner() { + now := time.Now() + + l := tw.slots[tw.currentPos] + tw.locks[tw.currentPos].Lock() + execNum := tw.scanAddRunTask(l) + tw.locks[tw.currentPos].Unlock() + + duration := time.Now().Sub(now) + log.Debugf("%s task start time:%d, use time:%v, exec num:%d", tw.name, now.Unix(), duration, execNum) + if tw.currentPos == tw.slotNum-1 { + tw.currentPos = 0 + } else { + tw.currentPos++ + } +} + +/** + * @brief 新增时间轮任务 + */ +func (tw *TimeWheel) AddTask(delayTime time.Duration, data interface{}, cb Callback) error { + if delayTime <= 0 { + return errors.New("illegal task delayTime") + } + + task := &Task{delayTime: delayTime, taskData: data, callback: cb} + pos, circle := tw.getSlots(task.delayTime) + task.circle = circle + + tw.locks[pos].Lock() + tw.slots[pos].PushBack(task) + tw.locks[pos].Unlock() + + return nil +} + +/** + * @brief 运行时间轮任务 + */ +func (tw *TimeWheel) scanAddRunTask(l *list.List) int { + if l == nil { + return 0 + } + + execNum := l.Len() + for item := l.Front(); item != nil; { + task := item.Value.(*Task) + + if task.circle > 0 { + task.circle-- + item = item.Next() + continue + } + + go task.callback(task.taskData) + next := item.Next() + l.Remove(item) + item = next + } + + return execNum +} + +/** + * @brief 获取当前时间轮位置 + */ +func (tw *TimeWheel) getSlots(d time.Duration) (pos int, circle int) { + delayTime := int(d.Seconds()) + interval := int(tw.interval.Seconds()) + circle = int(delayTime / interval / tw.slotNum) + pos = int(tw.currentPos+delayTime/interval) % tw.slotNum + return +} diff --git a/common/timewheel/timewheel_test.go b/common/timewheel/timewheel_test.go new file mode 100644 index 000000000..ab8ca0d33 --- /dev/null +++ b/common/timewheel/timewheel_test.go @@ -0,0 +1,215 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package timewheel + +import ( + "fmt" + "strconv" + "testing" + "time" +) + +// test timewheel task run +func TestTaskRun1(t *testing.T) { + tw := New(time.Second, 5, "test tw") + tw.Start() + callback := func(data interface{}) { + fmt.Println(data.(string)) + } + + t.Logf("add task time:%d", time.Now().Unix()) + for i := 0; i < 10; i++ { + tw.AddTask(time.Second, "polaris 1s "+strconv.Itoa(i), callback) + } + t.Logf("add task time end:%d", time.Now().Unix()) + + time.Sleep(2 * time.Second) + t.Logf("add task time:%d", time.Now().Unix()) + for i := 0; i < 10; i++ { + tw.AddTask(3*time.Second, "polaris 3s "+strconv.Itoa(i), callback) + } + t.Logf("add task time end:%d", time.Now().Unix()) + + time.Sleep(5 * time.Second) + t.Logf("add task time:%d", time.Now().Unix()) + for i := 0; i < 10; i++ { + tw.AddTask(10*time.Second, "polaris 10s "+strconv.Itoa(i), callback) + } + t.Logf("add task time end:%d", time.Now().Unix()) + time.Sleep(15 * time.Second) + + tw.Stop() +} + +// test timewheel task run +func TestTaskRun2(t *testing.T) { + tw := New(time.Second, 5, "test tw") + tw.Start() + callback := func(data interface{}) { + now := time.Now().Unix() + if now != 3123124121 { + _ = fmt.Sprintf("%s%+v", data.(string), time.Now()) + } else { + _ = fmt.Sprintf("%s%+v", data.(string), time.Now()) + } + } + + t.Logf("add task time:%d", time.Now().Unix()) + for i := 0; i < 50000; i++ { + tw.AddTask(3*time.Second, "polaris 3s "+strconv.Itoa(i), callback) + } + t.Logf("add task time end:%d", time.Now().Unix()) + time.Sleep(8 * time.Second) + + tw.Stop() +} + +// test timewheel task run +func TestTaskRunBoth(t *testing.T) { + tw := New(time.Second, 5, "test tw") + tw.Start() + callback := func(data interface{}) { + fmt.Println(data.(string)) + } + + for i := 0; i < 10; i++ { + go tw.AddTask(time.Second, "polaris 1s_"+strconv.Itoa(i), callback) + go tw.AddTask(3*time.Second, "polaris 3s_"+strconv.Itoa(i), callback) + go tw.AddTask(7*time.Second, "polaris 10s_"+strconv.Itoa(i), callback) + } + time.Sleep(12 * time.Second) + tw.Stop() +} + +// timewheel task struct +type Info struct { + id string + ttl int + ms int64 +} + +// bench-test timewheel task add +func BenchmarkAddTask1(t *testing.B) { + tw := New(time.Second, 5, "test tw") + info := &Info{ + "abcdefghijklmnopqrstuvwxyz", + 2, + time.Now().Unix(), + } + + callback := func(data interface{}) { + dataInfo := data.(*Info) + if dataInfo.ms < time.Now().Unix() { + fmt.Println("overtime") + } + } + + //t.N = 100000 + t.SetParallelism(10000) + t.RunParallel(func(pb *testing.PB) { + for pb.Next() { + tw.AddTask(2*time.Second, info, callback) + } + }) +} + +// bench-test timewheel task add +// use 2 slot +func BenchmarkAddTask2(t *testing.B) { + tw := New(time.Second, 5, "test tw") + info := &Info{ + "abcdefghijklmnopqrstuvwxyz", + 2, + time.Now().Unix(), + } + + callback := func(data interface{}) { + dataInfo := data.(*Info) + if dataInfo.ms < time.Now().Unix() { + fmt.Println("overtime") + } + } + + t.SetParallelism(10000) + t.RunParallel(func(pb *testing.PB) { + for pb.Next() { + tw.AddTask(2*time.Second, info, callback) + tw.AddTask(3*time.Second, info, callback) + } + }) +} + +// bench-test timewheel task add +//use 2 timewheel +func BenchmarkAddTask3(t *testing.B) { + tw := New(time.Second, 5, "test tw") + tw2 := New(time.Second, 5, "test tw") + + info := &Info{ + "abcdefghijklmnopqrstuvwxyz", + 2, + time.Now().Unix(), + } + + callback := func(data interface{}) { + dataInfo := data.(*Info) + if dataInfo.ms < time.Now().Unix() { + fmt.Println("overtime") + } + } + + t.SetParallelism(10000) + t.RunParallel(func(pb *testing.PB) { + for pb.Next() { + tw.AddTask(2*time.Second, info, callback) + tw2.AddTask(2*time.Second, info, callback) + } + }) +} + +// result:select random get ch +func TestSelect(t *testing.T) { + ch := make(chan int, 20) + ch2 := make(chan int, 20) + stopCh := make(chan bool) + + go func() { + for i := 0; i < 10; i++ { + ch <- i + ch2 <- i + 20 + } + time.Sleep(10 * time.Second) + close(stopCh) + }() + + for { + select { + case i := <-ch: + fmt.Println(i) + time.Sleep(time.Second) + case i := <-ch2: + fmt.Println(i) + time.Sleep(time.Second) + case <-stopCh: + for i := range ch { + fmt.Println(i) + } + return + } + } +} diff --git a/common/utils/cl5.go b/common/utils/cl5.go new file mode 100644 index 000000000..35de410ba --- /dev/null +++ b/common/utils/cl5.go @@ -0,0 +1,65 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package utils + +import ( + "errors" + "fmt" + "github.com/polarismesh/polaris-server/common/model" + "strconv" + "strings" +) + +// cl5集群的ctx的key +type Cl5ServerCluster struct{} + +// cl5.server的协议ctx +type Cl5ServerProtocol struct{} + +// Sid结构体,序列化转为sid字符串 +func MarshalSid(sid *model.Sid) string { + return fmt.Sprintf("%d:%d", sid.ModID, sid.CmdID) +} + +// mod cmd转为sid +func MarshalModCmd(modID uint32, cmdID uint32) string { + return fmt.Sprintf("%d:%d", modID, cmdID) +} + +// 把sid字符串反序列化为结构体Sid +func UnmarshalSid(sidStr string) (*model.Sid, error) { + items := strings.Split(sidStr, ":") + if len(items) != 2 { + return nil, errors.New("invalid format sid string") + } + + modID, err := strconv.ParseUint(items[0], 10, 32) + if err != nil { + return nil, err + } + cmdID, err := strconv.ParseUint(items[1], 10, 32) + if err != nil { + return nil, err + } + + out := &model.Sid{ + ModID: uint32(modID), + CmdID: uint32(cmdID), + } + return out, nil +} diff --git a/common/utils/const.go b/common/utils/const.go new file mode 100644 index 000000000..4431f5a5f --- /dev/null +++ b/common/utils/const.go @@ -0,0 +1,24 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package utils + +const ( + PolarisCode = "X-Polaris-Code" + PolarisMessage = "X-Polaris-Message" + PolarisRequestID = "Request-Id" +) diff --git a/common/utils/context.go b/common/utils/context.go new file mode 100644 index 000000000..d837145bf --- /dev/null +++ b/common/utils/context.go @@ -0,0 +1,44 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package utils + +import "context" + +type StringContext string + +// 存储localhost的ctx +type localhostCtx struct{} + +// 存储localhost +func WithLocalhost(ctx context.Context, localhost string) context.Context { + return context.WithValue(ctx, localhostCtx{}, localhost) +} + +// 获取localhost +func ValueLocalhost(ctx context.Context) string { + if ctx == nil { + return "" + } + + value, ok := ctx.Value(localhostCtx{}).(string) + if !ok { + return "" + } + + return value +} diff --git a/common/utils/funcs.go b/common/utils/funcs.go new file mode 100644 index 000000000..b21a129d3 --- /dev/null +++ b/common/utils/funcs.go @@ -0,0 +1,90 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package utils + +import ( + "encoding/hex" + uuid "github.com/google/uuid" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/model" + "strings" +) + +// 创建存储层服务实例模型 +func CreateInstanceModel(serviceID string, req *api.Instance) *model.Instance { + // 默认为健康的 + healthy := true + if req.GetHealthy() != nil { + healthy = req.GetHealthy().GetValue() + } + + // 默认为不隔离的 + isolate := false + if req.GetIsolate() != nil { + isolate = req.GetIsolate().GetValue() + } + + // 权重默认是100 + var weight uint32 = 100 + if req.GetWeight() != nil { + weight = req.GetWeight().GetValue() + } + + instance := &model.Instance{ + ServiceID: serviceID, + } + uuidBytes := uuid.New() + protoIns := &api.Instance{ + Id: req.GetId(), + Host: NewStringValue(strings.TrimSpace(req.GetHost().GetValue())), + VpcId: req.GetVpcId(), + Port: req.GetPort(), + Protocol: req.GetProtocol(), + Version: req.GetVersion(), + Priority: req.GetPriority(), + Weight: NewUInt32Value(weight), + Healthy: NewBoolValue(healthy), + Isolate: NewBoolValue(isolate), + Location: req.Location, + Metadata: req.Metadata, + LogicSet: req.GetLogicSet(), + Revision: NewStringValue(hex.EncodeToString(uuidBytes[:])), // 更新版本号 + } + + // health Check,healthCheck不能为空,且没有显示把enable_health_check置为false + // 如果create的时候,打开了healthCheck,那么实例模式是unhealthy,必须要一次心跳才会healthy + if req.GetHealthCheck().GetHeartbeat() != nil && + (req.GetEnableHealthCheck() == nil || req.GetEnableHealthCheck().GetValue()) { + protoIns.EnableHealthCheck = NewBoolValue(true) + protoIns.HealthCheck = req.HealthCheck + protoIns.HealthCheck.Type = api.HealthCheck_HEARTBEAT + // ttl range: (0, 60] + ttl := protoIns.GetHealthCheck().GetHeartbeat().GetTtl().GetValue() + if ttl == 0 || ttl > 60 { + if protoIns.HealthCheck.Heartbeat.Ttl == nil { + protoIns.HealthCheck.Heartbeat.Ttl = NewUInt32Value(5) + } + protoIns.HealthCheck.Heartbeat.Ttl.Value = 5 + } + // 开启健康检查,且没有代入健康状态,则健康状态默认都是false + protoIns.Healthy.Value = false + } + + instance.Proto = protoIns + return instance +} diff --git a/common/utils/ptypes.go b/common/utils/ptypes.go new file mode 100644 index 000000000..1b8e5c1f9 --- /dev/null +++ b/common/utils/ptypes.go @@ -0,0 +1,37 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package utils + +import ( + "github.com/golang/protobuf/ptypes/wrappers" +) + +// +func NewStringValue(value string) *wrappers.StringValue { + return &wrappers.StringValue{Value: value} +} + +// +func NewUInt32Value(value uint32) *wrappers.UInt32Value { + return &wrappers.UInt32Value{Value: value} +} + +// +func NewBoolValue(value bool) *wrappers.BoolValue { + return &wrappers.BoolValue{Value: value} +} diff --git a/common/version/version.go b/common/version/version.go new file mode 100644 index 000000000..94a28650b --- /dev/null +++ b/common/version/version.go @@ -0,0 +1,43 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package version + +var ( + Version string + BuildDate string +) + +/** + * @brief 获取版本号 + */ +func Get() string { + if Version == "" { + return "v0.1.0" + } + + return Version +} + +// 获取完整版本号信息,包括时间戳的 +func GetRevision() string { + if Version == "" || BuildDate == "" { + return "v0.1.0" + } + + return Version + "." + BuildDate +} diff --git a/config/README.md b/config/README.md new file mode 100644 index 000000000..df71ccd59 --- /dev/null +++ b/config/README.md @@ -0,0 +1 @@ +# Config Service diff --git a/config/config.go b/config/config.go new file mode 100644 index 000000000..c0098a943 --- /dev/null +++ b/config/config.go @@ -0,0 +1,114 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package config + +import ( + "errors" + "fmt" + "github.com/polarismesh/polaris-server/apiserver" + "github.com/polarismesh/polaris-server/naming" + "github.com/polarismesh/polaris-server/naming/cache" + "os" + + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/plugin" + "github.com/polarismesh/polaris-server/store" + yaml "gopkg.in/yaml.v2" +) + +/** + * @brief 配置 + */ +type Config struct { + Bootstrap Bootstrap `yaml:"bootstrap"` + APIServers []apiserver.Config `yaml:"apiservers"` + Cache cache.Config `yaml:"cache"` + Naming naming.Config `yaml:"naming"` + Store store.Config `yaml:"store"` + Plugin plugin.Config `yaml:"plugin"` +} + +/** + * @brief 启动引导配置 + */ +type Bootstrap struct { + Logger log.Options + StartInOrder map[string]interface{} `yaml:"startInOrder"` + PolarisService PolarisService `yaml:"polaris_service"` +} + +/** + * @brief polaris-server的自注册配置 + */ +type PolarisService struct { + EnableRegister bool `yaml:"enable_register"` + ProbeAddress string `yaml:"probe_address"` + Isolated bool `yaml:"isolated"` + Services []*Service `yaml:"services"` +} + +/** + * @brief 服务的自注册的配置 + */ +type Service struct { + Name string `yaml:"name"` + Namespace string `yaml:"namespace"` + Protocols []string `yaml:"protocols"` +} + +/** + * @brief 对外提供的apiServers + */ +type APIEntries struct { + Name string `yaml:"name"` + Protocols []string `yaml:"protocols"` +} + +const ( + DefaultPolarisName = "polaris-server" + DefaultPolarisNamespace = "Polaris" + DefaultFilePath = "polaris-server.yaml" +) + +/** + * @brief 加载配置 + */ +func Load(filePath string) (*Config, error) { + if filePath == "" { + err := errors.New("invalid config file path") + fmt.Printf("[ERROR] %v\n", err) + return nil, err + } + + fmt.Printf("[INFO] load config from %v\n", filePath) + + file, err := os.Open(filePath) + if err != nil { + fmt.Printf("[ERROR] %v\n", err) + return nil, err + } + + config := &Config{} + err = yaml.NewDecoder(file).Decode(config) + if err != nil { + fmt.Printf("[ERROR] %v\n", err) + return nil, err + } + + return config, nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 000000000..9e1299f10 --- /dev/null +++ b/go.mod @@ -0,0 +1,39 @@ +module github.com/polarismesh/polaris-server + +go 1.12 + +require ( + github.com/emicklei/go-restful v2.9.6+incompatible + github.com/go-sql-driver/mysql v1.5.0 + github.com/gogo/protobuf v1.3.1 + github.com/golang/mock v1.6.0 + github.com/golang/protobuf v1.3.5 + github.com/gomodule/redigo v2.0.0+incompatible + github.com/google/uuid v1.2.0 + github.com/hashicorp/golang-lru v0.5.3 + github.com/json-iterator/go v1.1.9 // indirect + github.com/mitchellh/mapstructure v1.1.2 + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/natefinch/lumberjack v2.0.0+incompatible + github.com/pkg/errors v0.8.1 + github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 + github.com/spf13/cobra v0.0.5 + github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/testify v1.4.0 + go.uber.org/atomic v1.5.1 // indirect + go.uber.org/multierr v1.4.0 // indirect + go.uber.org/zap v1.14.0 + golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f // indirect + golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 + golang.org/x/time v0.0.0-20191024005414-555d28b269f0 + google.golang.org/grpc v1.22.0 + gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect + gopkg.in/yaml.v2 v2.2.8 +) + +replace ( + github.com/golang/protobuf => github.com/golang/protobuf v1.3.4 + google.golang.org/grpc => google.golang.org/grpc v1.22.0 + gopkg.in/yaml.v2 => gopkg.in/yaml.v2 v2.2.2 +) diff --git a/go.sum b/go.sum new file mode 100644 index 000000000..f083dfd31 --- /dev/null +++ b/go.sum @@ -0,0 +1,190 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful v2.9.6+incompatible h1:tfrHha8zJ01ywiOEC1miGY8st1/igzWB8OmvPgoYX7w= +github.com/emicklei/go-restful v2.9.6+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= +github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk= +github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= +github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 h1:N8Bg45zpk/UcpNGnfJt2y/3lRWASHNTUET8owPYCgYI= +github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.5.1 h1:rsqfU5vBkVknbhUGbAUwQKR2H4ItV8tjJ+6kJX4cxHM= +go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.4.0 h1:f3WCSC2KzAcBXGATIxAB1E2XuCpNU255wNKZ505qi3E= +go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.14.0 h1:/pduUoebOeeJzTDFuoMgC6nRkiasr1sBCIEorly7m4o= +go.uber.org/zap v1.14.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f h1:kDxGY2VmgABOe55qheT/TFqUMtcTHnomIPS1iv3G4Ms= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.1 h1:wGiQel/hW0NnEkJUk8lbzkX2gFJU6PFxf1v5OlCfuOs= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.22.0 h1:J0UbZOIrCAl+fpTOf8YLs4dJo8L/owV4LYVtAXQoPkw= +google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/images/polaris_logo_white.png b/images/polaris_logo_white.png new file mode 100644 index 000000000..2fe5087d9 Binary files /dev/null and b/images/polaris_logo_white.png differ diff --git a/main.go b/main.go new file mode 100644 index 000000000..4034a34cd --- /dev/null +++ b/main.go @@ -0,0 +1,29 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package main + +import ( + "github.com/polarismesh/polaris-server/cmd" +) + +/** + * @brief 主函数 + */ +func main() { + cmd.Execute() +} diff --git a/naming/README.md b/naming/README.md new file mode 100644 index 000000000..7f222aacd --- /dev/null +++ b/naming/README.md @@ -0,0 +1 @@ +# Naming Service diff --git a/naming/auth/api.go b/naming/auth/api.go new file mode 100644 index 000000000..d62d93b7a --- /dev/null +++ b/naming/auth/api.go @@ -0,0 +1,46 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package auth + +/** +* @brief 内部鉴权接口 +* +* @note 内部鉴权分为两大类:命名空间和服务的资源鉴权;请求鉴权, +* 比如对于OSS操作,需要全局放通 + */ +type Authority interface { + //检查Token格式是否合法 + VerifyToken(actualToken string) bool + // 校验命名空间是否合法 + VerifyNamespace(expectToken string, actualToken string) bool + + // 校验服务是否合法 + VerifyService(expectToken string, actualToken string) bool + + // 校验实例是否合法 + VerifyInstance(expectToken string, actualToken string) bool + + // 校验规则是否合法 + VerifyRule(expectToken string, actualToken string) bool + + // 校验平台是否合法 + VerifyPlatform(expectToken string, actualToken string) bool + + // 校验网格权限是否合法 + VerifyMesh(expectToken string, actualToken string) bool +} diff --git a/naming/auth/authority.go b/naming/auth/authority.go new file mode 100644 index 000000000..299d96af2 --- /dev/null +++ b/naming/auth/authority.go @@ -0,0 +1,179 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package auth + +import ( + "strings" +) + +/** +* @brief 鉴权数据来源类 + */ +type authority struct { + global string + open bool +} + +const ( + globalToken = "polaris@12345678" + defaultOpen = true +) + +/** +* @brief 新建一个缓存类 + */ +func NewAuthority(opt map[string]interface{}) (Authority, error) { + global, _ := opt["global-token"].(string) + if global == "" { + global = globalToken + } + au := &authority{global: global, open: parseOpen(opt)} + return au, nil +} + +/** +* @brief 解析鉴权功能是否打开的开关 + */ +func parseOpen(opt map[string]interface{}) bool { + var open bool + var ok bool + open, ok = opt["open"].(bool) + if !ok { + return defaultOpen + } + return open +} + +//检查Token格式是否合法 +func (a *authority) VerifyToken(actualToken string) bool { + if !a.open { + return true + } + return len(actualToken) > 0 +} + +/** +* @brief 校验命名空间是否合法 + */ +func (a *authority) VerifyNamespace(expectToken string, actualToken string) bool { + if !a.open || a.global == actualToken { + return true + } + if expectToken == actualToken { + return true + } + + return false +} + +/** +* @brief 校验服务是否合法 + */ +func (a *authority) VerifyService(expectToken string, actualToken string) bool { + if !a.open || a.global == actualToken { + return true + } + + tokens := convertToken(expectToken) + if _, ok := tokens[actualToken]; ok { + return true + } + + return false +} + +/** +* @brief 校验实例是否合法 + */ +func (a *authority) VerifyInstance(expectToken string, actualToken string) bool { + if !a.open || a.global == actualToken { + return true + } + + tokens := convertToken(expectToken) + if _, ok := tokens[actualToken]; ok { + return true + } + + return false +} + +/** + * @brief 校验规则是否合法 + */ +func (a *authority) VerifyRule(expectToken string, actualToken string) bool { + if !a.open || a.global == actualToken { + return true + } + + tokens := convertToken(expectToken) + if _, ok := tokens[actualToken]; ok { + return true + } + + return false +} + +/** + * @brief 校验网格规则是否合法 + */ +func (a *authority) VerifyMesh(expectToken string, actualToken string) bool { + if !a.open || a.global == actualToken { + return true + } + + tokens := convertToken(expectToken) + if _, ok := tokens[actualToken]; ok { + return true + } + + return false +} + +/** + * @brief 校验平台是否合法 + */ +func (a *authority) VerifyPlatform(expectToken string, actualToken string) bool { + if !a.open || a.global == actualToken { + return true + } + + tokens := convertToken(expectToken) + if _, ok := tokens[actualToken]; ok { + return true + } + + return false +} + +/** +* @brief 将string类型的token转化为map类型 + */ +func convertToken(token string) map[string]bool { + if token == "" { + return nil + } + + strSlice := strings.Split(token, ",") + strMap := make(map[string]bool) + for _, value := range strSlice { + strMap[value] = true + } + + return strMap +} diff --git a/naming/auth/mock/api_mock.go b/naming/auth/mock/api_mock.go new file mode 100644 index 000000000..b4bc22b62 --- /dev/null +++ b/naming/auth/mock/api_mock.go @@ -0,0 +1,131 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: api.go + +// Package mock is a generated GoMock package. +package mock + +import ( + gomock "github.com/golang/mock/gomock" + reflect "reflect" +) + +// MockAuthority is a mock of Authority interface +type MockAuthority struct { + ctrl *gomock.Controller + recorder *MockAuthorityMockRecorder +} + +// MockAuthorityMockRecorder is the mock recorder for MockAuthority +type MockAuthorityMockRecorder struct { + mock *MockAuthority +} + +// NewMockAuthority creates a new mock instance +func NewMockAuthority(ctrl *gomock.Controller) *MockAuthority { + mock := &MockAuthority{ctrl: ctrl} + mock.recorder = &MockAuthorityMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockAuthority) EXPECT() *MockAuthorityMockRecorder { + return m.recorder +} + +// VerifyToken mocks base method +func (m *MockAuthority) VerifyToken(actualToken string) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VerifyToken", actualToken) + ret0, _ := ret[0].(bool) + return ret0 +} + +// VerifyToken indicates an expected call of VerifyToken +func (mr *MockAuthorityMockRecorder) VerifyToken(actualToken interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyToken", reflect.TypeOf((*MockAuthority)(nil).VerifyToken), actualToken) +} + +// VerifyNamespace mocks base method +func (m *MockAuthority) VerifyNamespace(expectToken, actualToken string) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VerifyNamespace", expectToken, actualToken) + ret0, _ := ret[0].(bool) + return ret0 +} + +// VerifyNamespace indicates an expected call of VerifyNamespace +func (mr *MockAuthorityMockRecorder) VerifyNamespace(expectToken, actualToken interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyNamespace", reflect.TypeOf((*MockAuthority)(nil).VerifyNamespace), expectToken, actualToken) +} + +// VerifyService mocks base method +func (m *MockAuthority) VerifyService(expectToken, actualToken string) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VerifyService", expectToken, actualToken) + ret0, _ := ret[0].(bool) + return ret0 +} + +// VerifyService indicates an expected call of VerifyService +func (mr *MockAuthorityMockRecorder) VerifyService(expectToken, actualToken interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyService", reflect.TypeOf((*MockAuthority)(nil).VerifyService), expectToken, actualToken) +} + +// VerifyInstance mocks base method +func (m *MockAuthority) VerifyInstance(expectToken, actualToken string) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VerifyInstance", expectToken, actualToken) + ret0, _ := ret[0].(bool) + return ret0 +} + +// VerifyInstance indicates an expected call of VerifyInstance +func (mr *MockAuthorityMockRecorder) VerifyInstance(expectToken, actualToken interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyInstance", reflect.TypeOf((*MockAuthority)(nil).VerifyInstance), expectToken, actualToken) +} + +// VerifyRule mocks base method +func (m *MockAuthority) VerifyRule(expectToken, actualToken string) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VerifyRule", expectToken, actualToken) + ret0, _ := ret[0].(bool) + return ret0 +} + +// VerifyRule indicates an expected call of VerifyRule +func (mr *MockAuthorityMockRecorder) VerifyRule(expectToken, actualToken interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyRule", reflect.TypeOf((*MockAuthority)(nil).VerifyRule), expectToken, actualToken) +} + +// VerifyPlatform mocks base method +func (m *MockAuthority) VerifyPlatform(expectToken, actualToken string) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VerifyPlatform", expectToken, actualToken) + ret0, _ := ret[0].(bool) + return ret0 +} + +// VerifyPlatform indicates an expected call of VerifyPlatform +func (mr *MockAuthorityMockRecorder) VerifyPlatform(expectToken, actualToken interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyPlatform", reflect.TypeOf((*MockAuthority)(nil).VerifyPlatform), expectToken, actualToken) +} + +// VerifyMesh mocks base method +func (m *MockAuthority) VerifyMesh(expectToken, actualToken string) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VerifyMesh", expectToken, actualToken) + ret0, _ := ret[0].(bool) + return ret0 +} + +// VerifyMesh indicates an expected call of VerifyMesh +func (mr *MockAuthorityMockRecorder) VerifyMesh(expectToken, actualToken interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyMesh", reflect.TypeOf((*MockAuthority)(nil).VerifyMesh), expectToken, actualToken) +} diff --git a/naming/batch/batch.go b/naming/batch/batch.go new file mode 100644 index 000000000..b001f861b --- /dev/null +++ b/naming/batch/batch.go @@ -0,0 +1,114 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package batch + +import ( + "context" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/naming/auth" + "github.com/polarismesh/polaris-server/plugin" + "github.com/polarismesh/polaris-server/store" +) + +// 批量控制器 +type Controller struct { + register *InstanceCtrl + deregister *InstanceCtrl +} + +// 根据配置文件创建一个批量控制器 +func NewBatchCtrlWithConfig(storage store.Store, authority auth.Authority, auth plugin.Auth, + config *Config) (*Controller, error) { + if config == nil { + return nil, nil + } + + register, err := NewBatchRegisterCtrl(storage, authority, auth, config.Register) + if err != nil { + log.Errorf("[Batch] new batch register instance ctrl err: %s", err.Error()) + return nil, err + } + deregister, err := NewBatchDeregisterCtrl(storage, authority, auth, config.Deregister) + if err != nil { + log.Errorf("[Batch] new batch deregister instance ctrl err: %s", err.Error()) + return nil, err + } + + bc := &Controller{ + register: register, + deregister: deregister, + } + return bc, nil +} + +// 开启批量控制器 +// 启动多个协程,接受外部create/delete请求 +func (bc *Controller) Start(ctx context.Context) { + if bc.CreateInstanceOpen() { + bc.register.Start(ctx) + } + if bc.DeleteInstanceOpen() { + bc.deregister.Start(ctx) + } +} + +// 创建是否开启 +func (bc *Controller) CreateInstanceOpen() bool { + if bc.register != nil { + return true + } + + return false +} + +// 删除实例是否开启 +func (bc *Controller) DeleteInstanceOpen() bool { + if bc.deregister != nil { + return true + } + + return false +} + +// 异步创建实例,返回一个future,根据future获取创建结果 +func (bc *Controller) AsyncCreateInstance(instance *api.Instance, platformID, platformToken string) *InstanceFuture { + future := &InstanceFuture{ + request: instance, + result: make(chan error), + platformID: platformID, + platformToken: platformToken, + } + + // 发送到注册请求队列 + bc.register.queue <- future + return future +} + +// 异步合并反注册 +func (bc *Controller) AsyncDeleteInstance(instance *api.Instance, platformID, platformToken string) *InstanceFuture { + future := &InstanceFuture{ + request: instance, + result: make(chan error), + platformID: platformID, + platformToken: platformToken, + } + + bc.deregister.queue <- future + return future +} diff --git a/naming/batch/batch_test.go b/naming/batch/batch_test.go new file mode 100644 index 000000000..6c1b281e0 --- /dev/null +++ b/naming/batch/batch_test.go @@ -0,0 +1,162 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package batch + +import ( + "context" + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/common/utils" + amock "github.com/polarismesh/polaris-server/naming/auth/mock" + smock "github.com/polarismesh/polaris-server/store/mock" + "github.com/golang/mock/gomock" + . "github.com/smartystreets/goconvey/convey" + "sync" + "testing" +) + +// 测试New +func TestNewBatchCtrlWithConfig(t *testing.T) { + Convey("正常新建", t, func() { + ctrlConfig := &CtrlConfig{ + Open: true, + QueueSize: 1024, + WaitTime: "20ms", + MaxBatchCount: 32, + Concurrency: 64, + } + config := &Config{ + Register: ctrlConfig, + Deregister: ctrlConfig, + } + bc, err := NewBatchCtrlWithConfig(nil, nil, nil, config) + So(err, ShouldBeNil) + So(bc, ShouldNotBeNil) + So(bc.register, ShouldNotBeNil) + So(bc.deregister, ShouldNotBeNil) + }) + Convey("可以关闭register和deregister的batch操作", t, func() { + bc, err := NewBatchCtrlWithConfig(nil, nil, nil, nil) + So(err, ShouldBeNil) + So(bc, ShouldBeNil) + + config := &Config{ + Register: &CtrlConfig{Open: false}, + Deregister: &CtrlConfig{Open: false}, + } + bc, err = NewBatchCtrlWithConfig(nil, nil, nil, config) + So(err, ShouldBeNil) + So(bc, ShouldNotBeNil) + So(bc.register, ShouldBeNil) + So(bc.deregister, ShouldBeNil) + }) +} + +func newCreateInstanceController(t *testing.T) (*Controller, *smock.MockStore, *amock.MockAuthority, + context.CancelFunc) { + ctl := gomock.NewController(t) + storage := smock.NewMockStore(ctl) + authority := amock.NewMockAuthority(ctl) + defer ctl.Finish() + config := &Config{ + Register: &CtrlConfig{ + Open: true, + QueueSize: 1024, + WaitTime: "16ms", + MaxBatchCount: 8, + Concurrency: 4, + }, + } + bc, err := NewBatchCtrlWithConfig(storage, authority, nil, config) + if bc == nil || err != nil { + t.Fatalf("error: %+v", err) + } + ctx, cancel := context.WithCancel(context.Background()) + bc.Start(ctx) + return bc, storage, authority, cancel + //defer cancel() +} + +func sendAsyncCreateInstance(bc *Controller) error { + var wg sync.WaitGroup + ch := make(chan error, 100) + for i := 0; i < 100; i++ { + wg.Add(1) + go func(index int) { + defer wg.Done() + future := bc.AsyncCreateInstance(&api.Instance{ + Id: utils.NewStringValue(fmt.Sprintf("%d", index)), + ServiceToken: utils.NewStringValue(fmt.Sprintf("%d", index)), + }, + "", "") + if err := future.Wait(); err != nil { + fmt.Printf("%+v\n", err) + ch <- err + } + }(i) + } + wg.Wait() + select { + case err := <-ch: + if err != nil { + return err + } + default: + return nil + } + return nil +} + +// test AsyncCreateInstance +func TestAsyncCreateInstance(t *testing.T) { + Convey("正常创建实例", t, func() { + bc, storage, authority, cancel := newCreateInstanceController(t) + defer cancel() + storage.EXPECT().CheckInstancesExisted(gomock.Any()).Return(nil, nil).AnyTimes() + storage.EXPECT().GetSourceServiceToken(gomock.Any(), gomock.Any()). + Return(&model.Service{ID: "1"}, nil).AnyTimes() + authority.EXPECT().VerifyInstance(gomock.Any(), gomock.Any()).Return(true).AnyTimes() + storage.EXPECT().BatchAddInstances(gomock.Any()).Return(nil).AnyTimes() + So(sendAsyncCreateInstance(bc), ShouldBeNil) + }) + Convey("鉴权失败", t, func() { + bc, storage, authority, cancel := newCreateInstanceController(t) + defer cancel() + storage.EXPECT().CheckInstancesExisted(gomock.Any()).Return(nil, nil).AnyTimes() + storage.EXPECT().GetSourceServiceToken(gomock.Any(), gomock.Any()). + Return(&model.Service{ID: "1"}, nil).AnyTimes() + authority.EXPECT().VerifyInstance(gomock.Any(), gomock.Any()).Return(false).AnyTimes() + err := sendAsyncCreateInstance(bc) + So(err, ShouldNotBeNil) + t.Logf("%+v", err) + }) +} + +// 测试reply +func TestSendReply(t *testing.T) { + Convey("可以正常获取类型", t, func() { + SendReply(make([]*InstanceFuture, 0, 10), 1, nil) + }) + Convey("可以正常获取类型2", t, func() { + SendReply(make(map[string]*InstanceFuture, 10), 1, nil) + }) + Convey("其他类型不通过", t, func() { + SendReply("test string", 1, nil) + }) +} diff --git a/naming/batch/config.go b/naming/batch/config.go new file mode 100644 index 000000000..8fa88b7c7 --- /dev/null +++ b/naming/batch/config.go @@ -0,0 +1,82 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package batch + +import ( + "errors" + "github.com/polarismesh/polaris-server/common/log" + "github.com/mitchellh/mapstructure" +) + +// 批量配置,控制最大的条目,批量等待时间等 +type Config struct { + Register *CtrlConfig `mapstructure:"register"` + Deregister *CtrlConfig `mapstructure:"deregister"` +} + +// batch控制配置项 +type CtrlConfig struct { + // 是否开启Batch工作模式 + Open bool `mapstructure:"open"` + // 注册请求队列的长度 + QueueSize int `mapstructure:"queueSize"` + // 最长多久一次批量操作 + WaitTime string `mapstructure:"waitTime"` + // 每次操作最大的批量数 + MaxBatchCount int `mapstructure:"maxBatchCount"` + // 写store的并发协程数 + Concurrency int `mapstructure:"concurrency"` +} + +// 解析配置文件为config +func ParseBatchConfig(opt map[string]interface{}) (*Config, error) { + if opt == nil { + return nil, nil + } + + var config Config + if err := mapstructure.Decode(opt, &config); err != nil { + log.Errorf("[Batch] parse config(%+v) err: %s", opt, err.Error()) + return nil, err + } + + // 对配置文件做校验 + if !checkCtrlConfig(config.Register) { + log.Errorf("[Controller] batch register config is invalid: %+v", config) + return nil, errors.New("batch register config is invalid") + } + if !checkCtrlConfig(config.Deregister) { + log.Errorf("[Controller] batch deregister config is invalid: %+v", config) + return nil, errors.New("batch deregister config is invalid") + } + + return &config, nil +} + +// 配置文件校验 +func checkCtrlConfig(ctrl *CtrlConfig) bool { + if ctrl == nil { + return true + } + + if ctrl.QueueSize <= 0 || ctrl.MaxBatchCount <= 0 || ctrl.Concurrency <= 0 { + return false + } + + return true +} diff --git a/naming/batch/future.go b/naming/batch/future.go new file mode 100644 index 000000000..671407574 --- /dev/null +++ b/naming/batch/future.go @@ -0,0 +1,83 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package batch + +import ( + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" +) + +// 创建实例的异步结构体 +type InstanceFuture struct { + request *api.Instance // api请求对象 + instance *model.Instance // 从数据库中读取到的model信息 + code uint32 // 记录对外API的错误码 + result chan error // 执行成功/失败的应答chan + platformID string // 平台id + platformToken string // 平台Token +} + +// future的应答 +func (future *InstanceFuture) Reply(code uint32, result error) { + future.code = code + + select { + case future.result <- result: + default: + log.Warnf("[Batch] instance(%s) future is not captured", future.request.GetId().GetValue()) + } + + return +} + +// 外部调用者,需要调用Wait等待执行结果 +func (future *InstanceFuture) Wait() error { + return <-future.result +} + +// 设置ins +func (future *InstanceFuture) SetInstance(instance *model.Instance) { + future.instance = instance +} + +// 获取ins +func (future *InstanceFuture) Instance() *model.Instance { + return future.instance +} + +// 获取code +func (future *InstanceFuture) Code() uint32 { + return future.code +} + +// 批量答复futures +func SendReply(futures interface{}, code uint32, result error) { + switch futures.(type) { + case []*InstanceFuture: + for _, entry := range futures.([]*InstanceFuture) { + entry.Reply(code, result) + } + case map[string]*InstanceFuture: + for _, entry := range futures.(map[string]*InstanceFuture) { + entry.Reply(code, result) + } + default: + log.Errorf("[Controller] not found reply futures type: %T", futures) + } +} diff --git a/naming/batch/instance.go b/naming/batch/instance.go new file mode 100644 index 000000000..219df4be5 --- /dev/null +++ b/naming/batch/instance.go @@ -0,0 +1,460 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package batch + +import ( + "context" + "errors" + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/common/utils" + "github.com/polarismesh/polaris-server/naming/auth" + "github.com/polarismesh/polaris-server/plugin" + "github.com/polarismesh/polaris-server/store" + "time" +) + +// 批量操作实例的类 +type InstanceCtrl struct { + config *CtrlConfig + storage store.Store + authority auth.Authority + auth plugin.Auth + storeThreadCh []chan []*InstanceFuture // store协程,负责写操作 + instanceHandler func([]*InstanceFuture) error // store协程里面调用的instance处理函数,可以是注册和反注册 + idleStoreThread chan int // 空闲的store协程,记录每一个空闲id + waitDuration time.Duration + queue chan *InstanceFuture // 请求接受协程 + label string + hbOpen bool //是否开启了心跳上报功能 +} + +// 注册实例批量操作对象 +func NewBatchRegisterCtrl(storage store.Store, authority auth.Authority, auth plugin.Auth, + config *CtrlConfig) (*InstanceCtrl, error) { + register, err := newBatchInstanceCtrl(storage, authority, auth, config) + if err != nil { + return nil, err + } + if register == nil { + return nil, nil + } + + log.Infof("[Batch] open batch register") + register.label = "register" + register.instanceHandler = register.registerHandler + return register, nil +} + +// 实例反注册的操作对象 +func NewBatchDeregisterCtrl(storage store.Store, authority auth.Authority, auth plugin.Auth, config *CtrlConfig) ( + *InstanceCtrl, error) { + deregister, err := newBatchInstanceCtrl(storage, authority, auth, config) + if err != nil { + return nil, err + } + if deregister == nil { + return nil, nil + } + + log.Infof("[Batch] open batch deregister") + deregister.label = "deregister" + deregister.instanceHandler = deregister.deregisterHandler + + return deregister, nil +} + +// 开始启动批量操作实例的相关协程 +func (ctrl *InstanceCtrl) Start(ctx context.Context) { + log.Infof("[Batch] Start batch instance, config: %+v", ctrl.config) + + // 初始化并且启动多个store协程,并发对数据库写 + for i := 0; i < ctrl.config.Concurrency; i++ { + ctrl.storeThreadCh = append(ctrl.storeThreadCh, make(chan []*InstanceFuture)) + } + for i := 0; i < ctrl.config.Concurrency; i++ { + go ctrl.storeWorker(ctx, i) + } + + // 进入主循环 + ctrl.mainLoop(ctx) +} + +// 创建批量控制instance的对象 +func newBatchInstanceCtrl(storage store.Store, authority auth.Authority, auth plugin.Auth, + config *CtrlConfig) (*InstanceCtrl, error) { + if config == nil || config.Open == false { + return nil, nil + } + + duration, err := time.ParseDuration(config.WaitTime) + if err != nil { + log.Errorf("[Batch] parse waitTime(%s) err: %s", config.WaitTime, err.Error()) + return nil, err + } + if duration == 0 { + log.Errorf("[Batch] config waitTime is invalid") + return nil, errors.New("config waitTime is invalid") + } + + instance := &InstanceCtrl{ + config: config, + storage: storage, + authority: authority, + auth: auth, + storeThreadCh: make([]chan []*InstanceFuture, 0, config.Concurrency), + idleStoreThread: make(chan int, config.Concurrency), + queue: make(chan *InstanceFuture, config.QueueSize), + waitDuration: duration, + } + return instance, nil +} + +// 注册主协程 +// 从注册队列中获取注册请求,当达到b.config.MaxBatchCount, +// 或当到了一个超时时间b.waitDuration,则发起一个写请求 +// 写请求发送到store协程,规则:从空闲的管道idleStoreThread中挑选一个 +func (ctrl *InstanceCtrl) mainLoop(ctx context.Context) { + futures := make([]*InstanceFuture, 0, ctrl.config.MaxBatchCount) + idx := 0 + triggerConsume := func(data []*InstanceFuture) { + if idx == 0 { + return + } + // 选择一个idle的store协程写数据 TODO 这里需要统计一下 + idleIdx := <-ctrl.idleStoreThread + ctrl.storeThreadCh[idleIdx] <- data + futures = make([]*InstanceFuture, 0, ctrl.config.MaxBatchCount) + idx = 0 + return + } + // 启动接受注册请求的协程 + go func() { + ticker := time.NewTicker(ctrl.waitDuration) + defer ticker.Stop() + for { + select { + case future := <-ctrl.queue: + futures = append(futures, future) + idx++ + if idx == ctrl.config.MaxBatchCount { + triggerConsume(futures[0:idx]) + } + case <-ticker.C: + triggerConsume(futures[0:idx]) + case <-ctx.Done(): + log.Infof("[Batch] %s main loop exited", ctrl.label) + return + } + } + }() +} + +// store写协程的主循环 +// 从chan中获取数据,直接写数据库 +// 每次写完,设置协程为空闲 +func (ctrl *InstanceCtrl) storeWorker(ctx context.Context, index int) { + log.Infof("[Batch] %s worker(%d) running in main loop", ctrl.label, index) + // store协程启动,先把自己注册到idle中 + ctrl.idleStoreThread <- index + // 主循环 + for { + select { + case futures := <-ctrl.storeThreadCh[index]: + if err := ctrl.instanceHandler(futures); err != nil { + // 所有的错误都在instanceHandler函数里面进行答复和处理,这里只需记录一条日志 + log.Errorf("[Batch] %s instances err: %s", ctrl.label, err.Error()) + } + ctrl.idleStoreThread <- index + case <-ctx.Done(): + // idle is not ready + log.Infof("[Batch] %s worker(%d) exited", ctrl.label, index) + return + } + } +} + +// 外部应该把鉴权完成 +// 判断实例是否存在,也可以提前判断,减少batch复杂度 +// 提前通过token判断,再进入batch操作 +// batch操作,只是写操作 +func (ctrl *InstanceCtrl) registerHandler(futures []*InstanceFuture) error { + if len(futures) == 0 { + return nil + } + + log.Infof("[Batch] Start batch creating instances count: %d", len(futures)) + remains := make(map[string]*InstanceFuture, len(futures)) + for _, entry := range futures { + if _, ok := remains[entry.request.GetId().GetValue()]; ok { + entry.Reply(api.SameInstanceRequest, errors.New("there is the same instance request")) + continue + } + + remains[entry.request.GetId().GetValue()] = entry + } + + // 统一判断实例是否存在 + remains, err := ctrl.batchCheckInstancesExisted(remains) + if err != nil { + log.Errorf("[Batch] batch check instances existed err: %s", err.Error()) + } + // 这里可能全部都重复了 + if len(remains) == 0 { + log.Infof("[Batch] all instances is existed, return create instances process") + return nil + } + + // 统一鉴权 + remains, serviceIDs, _ := ctrl.batchVerifyInstances(remains) + if len(remains) == 0 { + log.Infof("[Batch] all instances verify failed, no remain any instances") + return nil + } + + // 构造model数据 + for id, entry := range remains { + serviceID, ok := serviceIDs[entry.request.GetId().GetValue()] + if !ok || serviceID == "" { + log.Errorf("[Batch] not found instance(%s) service, ignore it", entry.request.GetId().GetValue()) + delete(remains, id) + entry.Reply(api.NotFoundResource, errors.New("not found service")) + continue + } + entry.SetInstance(utils.CreateInstanceModel(serviceID, entry.request)) + } + + // 调用batch接口,创建实例 + instances := make([]*model.Instance, 0, len(remains)) + for _, entry := range remains { + instances = append(instances, entry.instance) + } + if err := ctrl.storage.BatchAddInstances(instances); err != nil { + SendReply(remains, StoreCode2APICode(err), err) + return err + } + + SendReply(remains, api.ExecuteSuccess, nil) + return nil +} + +// 反注册处理函数 +// 步骤: +// - 从数据库中批量读取实例ID对应的实例简要信息: +// 包括:ID,host,port,serviceName,serviceNamespace,serviceToken +// - 对instance做存在与token的双重校验,较少与数据库的交互 +// - 对于不存在的token,返回notFoundResource +// - 对于token校验失败的,返回校验失败 +// - 调用批量接口删除实例 +func (ctrl *InstanceCtrl) deregisterHandler(futures []*InstanceFuture) error { + if len(futures) == 0 { + return nil + } + + log.Infof("[Batch] Start batch deregister instances count: %d", len(futures)) + remains := make(map[string]*InstanceFuture, len(futures)) + ids := make(map[string]bool, len(futures)) + for _, entry := range futures { + if _, ok := remains[entry.request.GetId().GetValue()]; ok { + entry.Reply(api.SameInstanceRequest, errors.New("there is the same instance request")) + continue + } + + remains[entry.request.GetId().GetValue()] = entry + ids[entry.request.GetId().GetValue()] = false + } + + // 统一鉴权与判断是否存在 + instances, err := ctrl.storage.GetInstancesBrief(ids) + if err != nil { + log.Errorf("[Batch] get instances service token err: %s", err.Error()) + SendReply(remains, api.StoreLayerException, err) + return err + } + for _, future := range futures { + instance, ok := instances[future.request.GetId().GetValue()] + if !ok { + // 不存在,意味着不需要删除了 + future.Reply(api.NotFoundResource, fmt.Errorf("%s", api.Code2Info(api.NotFoundResource))) + delete(remains, future.request.GetId().GetValue()) + continue + } + + future.SetInstance(instance) // 这里保存instance的目的:方便上层使用model数据 + if ok, code := ctrl.verifyInstanceAuth(future.platformID, future.platformToken, instance.ServiceToken(), + instance.ServicePlatformID, future.request); !ok { + future.Reply(code, fmt.Errorf("instances: %s %s", future.request.GetId().GetValue(), + api.Code2Info(code))) + delete(remains, future.request.GetId().GetValue()) + continue + } + } + + if len(remains) == 0 { + log.Infof("[Batch] deregister all instances verify failed or instances is not existed, no remain any instances") + return nil + } + + // 调用storage batch接口,删除实例 + args := make([]interface{}, 0, len(remains)) + for _, entry := range remains { + args = append(args, entry.request.GetId().GetValue()) + } + if err := ctrl.storage.BatchDeleteInstances(args); err != nil { + log.Errorf("[Batch] batch delete instances err: %s", err.Error()) + SendReply(remains, api.StoreLayerException, err) + return err + } + + SendReply(remains, api.ExecuteSuccess, nil) + return nil +} + +// 批量检查实例是否存在 +func (ctrl *InstanceCtrl) batchCheckInstancesExisted(futures map[string]*InstanceFuture) ( + map[string]*InstanceFuture, error) { + + if len(futures) == 0 { + return nil, nil + } + + // 初始化所有的id都是不存在的 + ids := make(map[string]bool, len(futures)) + for _, entry := range futures { + ids[entry.request.GetId().GetValue()] = false + } + if _, err := ctrl.storage.CheckInstancesExisted(ids); err != nil { + log.Errorf("[Batch] check instances existed storage err: %s", err.Error()) + SendReply(futures, api.StoreLayerException, err) + return nil, err + } + + for id, existed := range ids { + if !existed { + continue + } + + entry, ok := futures[id] + if !ok { + // 返回了没有查询的id,告警 + log.Warnf("[Batch] check instances existed get not track id : %s", id) + continue + } + + entry.Reply(api.ExistedResource, fmt.Errorf("instance(%s) is existed", entry.request.GetId().GetValue())) + delete(futures, id) + } + + return futures, nil +} + +// 对请求futures进行统一的鉴权 +// 目的:遇到同名的服务,可以减少getService的次数 +// 返回:过滤后的futures, 实例ID->ServiceID, error +func (ctrl *InstanceCtrl) batchVerifyInstances(futures map[string]*InstanceFuture) ( + map[string]*InstanceFuture, map[string]string, error) { + + if len(futures) == 0 { + return nil, nil, nil + } + + serviceIDs := make(map[string]string) // 实例ID -> ServiceID + services := make(map[string]*model.Service) // 保存Service的鉴权结果 + for id, entry := range futures { + serviceStr := entry.request.GetService().GetValue() + entry.request.GetNamespace().GetValue() + service, ok := services[serviceStr] + if !ok { + // 鉴权,这里拿的是源服务token,如果是别名,service=nil + tmpService, err := ctrl.storage.GetSourceServiceToken(entry.request.GetService().GetValue(), + entry.request.GetNamespace().GetValue()) + if err != nil { + log.Errorf("[Controller] get source service(%s, %s) token err: %s", + entry.request.GetService().GetValue(), entry.request.GetNamespace().GetValue(), err.Error()) + entry.Reply(api.StoreLayerException, err) + delete(futures, id) + continue + } + + // 注册的实例对应的源服务不存在 + if tmpService == nil { + log.Errorf("[Controller] get source service(%s, %s) token is empty, verify failed", + entry.request.GetService().GetValue(), entry.request.GetNamespace().GetValue()) + entry.Reply(api.NotFoundResource, errors.New("not found service")) + delete(futures, id) + continue + } + // 保存查询到的最新服务信息,后续可能会使用到 + service = tmpService + services[serviceStr] = service + } + + if ok, code := ctrl.verifyInstanceAuth(entry.platformID, entry.platformToken, + service.Token, service.PlatformID, entry.request); !ok { + entry.Reply(code, fmt.Errorf("service: %s, namepace: %s, instance: %s %s", + entry.request.GetService().GetValue(), entry.request.GetNamespace().GetValue(), + entry.request.GetId().GetValue(), api.Code2Info(code))) + delete(futures, id) + continue + } + + // 保存每个instance注册到的服务ID + serviceIDs[entry.request.GetId().GetValue()] = service.ID + } + + return futures, serviceIDs, nil +} + +/** + * @brief 实例鉴权 + */ +func (ctrl *InstanceCtrl) verifyInstanceAuth(platformID, platformToken, expectServiceToken, sPlatformID string, + req *api.Instance) (bool, uint32) { + if ok := ctrl.verifyAuthByPlatform(platformID, platformToken, sPlatformID); !ok { + // 检查token是否存在 + actualServiceToken := req.GetServiceToken().GetValue() + if !ctrl.authority.VerifyToken(actualServiceToken) { + return false, api.InvalidServiceToken + } + + // 检查token是否ok + if ok := ctrl.authority.VerifyInstance(expectServiceToken, actualServiceToken); !ok { + return false, api.Unauthorized + } + } + return true, 0 +} + +/** + * @brief 使用平台ID鉴权 + */ +func (ctrl *InstanceCtrl) verifyAuthByPlatform(platformID, platformToken, sPlatformID string) bool { + if ctrl.auth == nil { + return false + } + + if sPlatformID == "" { + return false + } + + if ctrl.auth.Allow(platformID, platformToken) && platformID == sPlatformID { + return true + } + return false +} diff --git a/naming/batch/utils.go b/naming/batch/utils.go new file mode 100644 index 000000000..6c135cb0c --- /dev/null +++ b/naming/batch/utils.go @@ -0,0 +1,50 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package batch + +import ( + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/store" +) + +// store code 2 api code +func StoreCode2APICode(err error) uint32 { + code := store.Code(err) + switch { + case code == store.EmptyParamsErr: + return api.InvalidParameter + case code == store.OutOfRangeErr: + return api.InvalidParameter + case code == store.DataConflictErr: + return api.DataConflict + case code == store.NotFoundNamespace: + return api.NotFoundNamespace + case code == store.NotFoundService: + return api.NotFoundService + case code == store.NotFoundMasterConfig: + return api.NotFoundMasterConfig + case code == store.NotFoundTagConfigOrService: + return api.NotFoundTagConfigOrService + case code == store.ExistReleasedConfig: + return api.ExistReleasedConfig + case code == store.DuplicateEntryErr: + return api.ExistedResource + default: + return api.StoreLayerException + } +} diff --git a/naming/cache/cache.go b/naming/cache/cache.go new file mode 100644 index 000000000..4623f8430 --- /dev/null +++ b/naming/cache/cache.go @@ -0,0 +1,394 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package cache + +import ( + "context" + "crypto/sha1" + "encoding/hex" + "fmt" + "io" + "sort" + "sync" + "time" + + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/store" +) + +var ( + cacheSet = make(map[string]int) +) + +const ( + //CacheNamespace int = iota + //CacheBusiness + CacheService int = iota + CacheInstance + CacheRoutingConfig + CacheCL5 + CacheRateLimit + CacheCircuitBreaker + CacheLast +) + +const ( + DefaultTimeDiff = -1 * time.Second * 5 +) + +/** + * @brief 缓存接口 + */ +type Cache interface { + initialize(c map[string]interface{}) error + update() error + clear() error + name() string +} + +const ( + // 缓存更新时间间隔 + UpdateCacheInterval = time.Second +) + +const ( + // Revision计算的并发线程数 + RevisionConcurrenceCount = 64 + // 存储revision计算的通知管道,可以稍微设置大一点 + RevisionChanCount = 10240 +) + +// 更新revision的结构体 +type revisionNotify struct { + serviceID string + valid bool +} + +// create new revision notify +func newRevisionNotify(serviceID string, valid bool) *revisionNotify { + return &revisionNotify{ + serviceID: serviceID, + valid: valid, + } +} + +/** + * @brief 名字服务缓存 + */ +type NamingCache struct { + storage store.Store + caches []Cache + + comRevisionCh chan *revisionNotify + revisions *sync.Map +} + +/** + * @brief 新建一个缓存对象 + */ +func NewNamingCache(storage store.Store) (*NamingCache, error) { + nc := &NamingCache{ + storage: storage, + caches: make([]Cache, CacheLast), + comRevisionCh: make(chan *revisionNotify, RevisionChanCount), + revisions: new(sync.Map), + } + + sc := newServiceCache(storage, nc.comRevisionCh) + ic := newInstanceCache(storage, nc.comRevisionCh) + + nc.caches[CacheService] = sc + nc.caches[CacheInstance] = ic + nc.caches[CacheRoutingConfig] = newRoutingConfigCache(storage) + nc.caches[CacheCL5] = &l5Cache{ + storage: storage, + ic: ic, + sc: sc, + } + nc.caches[CacheRateLimit] = newRateLimitCache(storage) + nc.caches[CacheCircuitBreaker] = newCircuitBreakerCache(storage) + + if err := nc.initialize(); err != nil { + return nil, err + } + + return nc, nil +} + +/** + * @brief 缓存对象初始化 + */ +func (nc *NamingCache) initialize() error { + for _, obj := range nc.caches { + var option map[string]interface{} + for _, entry := range config.Resources { + if obj.name() == entry.Name { + option = entry.Option + break + } + } + if err := obj.initialize(option); err != nil { + return err + } + } + + return nil +} + +/** + * @brief 缓存更新 + */ +func (nc *NamingCache) update() error { + var wg sync.WaitGroup + for _, entry := range config.Resources { + index, exist := cacheSet[entry.Name] + if !exist { + return fmt.Errorf("cache resource %s not exists", entry.Name) + } + wg.Add(1) + go func(c Cache) { + defer wg.Done() + _ = c.update() + }(nc.caches[index]) + } + + wg.Wait() + return nil +} + +/** + * @brief 清除caches的所有缓存数据 + */ +func (nc *NamingCache) clear() error { + for _, obj := range nc.caches { + if err := obj.clear(); err != nil { + return err + } + } + + return nil +} + +/** + * @brief 缓存对象启动协程,定时更新缓存 + */ +func (nc *NamingCache) Start(ctx context.Context) error { + log.Infof("[Cache] cache goroutine start") + // 先启动revision计算协程 + go nc.revisionWorker(ctx) + + // 启动的时候,先更新一版缓存 + log.Infof("[Cache] cache update now first time") + if err := nc.update(); err != nil { + return err + } + log.Infof("[Cache] cache update done") + + // 启动协程,开始定时更新缓存数据 + go func() { + ticker := time.NewTicker(nc.GetUpdateCacheInterval()) + defer ticker.Stop() + for { + select { + case <-ticker.C: + _ = nc.update() + case <-ctx.Done(): + return + } + } + }() + + return nil +} + +/** + * @brief 主动清除缓存数据 + */ +func (nc *NamingCache) Clear() error { + nc.revisions = new(sync.Map) + return nc.clear() +} + +/** + * @brief Cache中计算服务实例revision的worker + */ +func (nc *NamingCache) revisionWorker(ctx context.Context) { + log.Infof("[Cache] compute revision worker start") + defer log.Infof("[Cache] compute revision worker done") + + processFn := func() { + for { + select { + case req := <-nc.comRevisionCh: + if ok := nc.processRevisionWorker(req); !ok { + continue + } + + // 每个计算完,等待2ms + time.Sleep(time.Millisecond * 2) + case <-ctx.Done(): + return + } + } + } + + // 启动多个协程来计算revision,后续可以通过启动参数控制 + for i := 0; i < RevisionConcurrenceCount; i++ { + go processFn() + } +} + +// 处理revision计算的函数 +func (nc *NamingCache) processRevisionWorker(req *revisionNotify) bool { + if req == nil { + log.Errorf("[Cache][Revision] get null revision request") + return false + } + + if req.serviceID == "" { + log.Errorf("[Cache][Revision] get request service ID is empty") + return false + } + + if req.valid == false { + //log.Infof("[Cache][Revision] service(%s) revision has all been removed", req.serviceID) + nc.revisions.Delete(req.serviceID) + return true + } + + service := nc.Service().GetServiceByID(req.serviceID) + if service == nil { + //log.Errorf("[Cache][Revision] can not found service id(%s)", req.serviceID) + return false + } + + instances := nc.Instance().GetInstancesByServiceID(req.serviceID) + revision, err := ComputeRevision(service.Revision, instances) + if err != nil { + log.Errorf("[Cache] compute service id(%s) instances revision err: %s", req.serviceID, err.Error()) + return false + } + nc.revisions.Store(req.serviceID, revision) // string -> string + return true +} + +// 获取当前cache的更新间隔 +func (nc *NamingCache) GetUpdateCacheInterval() time.Duration { + return UpdateCacheInterval +} + +/** + * @brief 获取服务实例计算之后的revision + */ +func (nc *NamingCache) GetServiceInstanceRevision(serviceID string) string { + value, ok := nc.revisions.Load(serviceID) + if !ok { + return "" + } + + return value.(string) +} + +/** + * @brief 计算一下缓存中的revision的个数 + */ +func (nc *NamingCache) GetServiceRevisionCount() int { + count := 0 + nc.revisions.Range(func(key, value interface{}) bool { + count++ + return true + }) + + return count +} + +/** + * @brief 获取Service缓存信息 + */ +func (nc *NamingCache) Service() ServiceCache { + return nc.caches[CacheService].(ServiceCache) +} + +/** + * @brief 获取Instance缓存信息 + */ +func (nc *NamingCache) Instance() InstanceCache { + return nc.caches[CacheInstance].(InstanceCache) +} + +/** + * @brief 获取路由配置的缓存信息 + */ +func (nc *NamingCache) RoutingConfig() RoutingConfigCache { + return nc.caches[CacheRoutingConfig].(RoutingConfigCache) +} + +/** + * @brief 获取l5缓存信息 + */ +func (nc *NamingCache) CL5() L5Cache { + return nc.caches[CacheCL5].(L5Cache) +} + +/** + * @brief 获取限流规则缓存信息 + */ +func (nc *NamingCache) RateLimit() RateLimitCache { + return nc.caches[CacheRateLimit].(RateLimitCache) +} + +/** + * @brief 获取熔断规则缓存信息 + */ +func (nc *NamingCache) CircuitBreaker() CircuitBreakerCache { + return nc.caches[CacheCircuitBreaker].(CircuitBreakerCache) +} + +// +func ComputeRevision(serviceRevision string, instances []*model.Instance) (string, error) { + h := sha1.New() + if _, err := io.WriteString(h, serviceRevision); err != nil { + return "", err + } + + var slice sort.StringSlice + for _, item := range instances { + slice = append(slice, item.Revision()) + } + slice.Sort() + for _, revision := range slice { + if _, err := io.WriteString(h, revision); err != nil { + return "", err + } + } + + out := hex.EncodeToString(h.Sum(nil)) + return out, nil +} + +/** + * @brief 注册缓存资源 + */ +func RegisterCache(name string, index int) { + if _, exist := cacheSet[name]; exist { + panic(fmt.Sprintf("existed cache resource: name = %v", name)) + } + + cacheSet[name] = index +} \ No newline at end of file diff --git a/naming/cache/cache_test.go b/naming/cache/cache_test.go new file mode 100644 index 000000000..d4ec0f7b4 --- /dev/null +++ b/naming/cache/cache_test.go @@ -0,0 +1,192 @@ +package cache + +import ( + "context" + "fmt" + "testing" + "time" + + v1 "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/common/utils" + "github.com/polarismesh/polaris-server/store/mock" + "github.com/golang/mock/gomock" + . "github.com/smartystreets/goconvey/convey" +) + +// 测试cache函数是否正常 +func TestNamingCache_Start(t *testing.T) { + ctl := gomock.NewController(t) + storage := mock.NewMockStore(ctl) + defer ctl.Finish() + + conf := &Config{ + Open: true, + Resources: []ConfigEntry{ + { + Name: "service", + }, + { + Name: "instance", + }, + { + Name: "routingConfig", + }, + { + Name: "rateLimitConfig", + }, + { + Name: "circuitBreakerConfig", + }, + { + Name: "l5", + }, + }, + } + SetCacheConfig(conf) + + Convey("测试正常的更新缓存逻辑", t, func() { + c, err := NewNamingCache(storage) + So(err, ShouldBeNil) + So(c, ShouldNotBeNil) + + beg := time.Unix(0, 0).Add(DefaultTimeDiff) + storage.EXPECT().GetMoreInstances(beg, true, false, nil).Return(nil, nil).MaxTimes(1) + storage.EXPECT().GetMoreInstances(beg, false, false, nil).Return(nil, nil).MaxTimes(3) + storage.EXPECT().GetMoreServices(beg, true, false, false).Return(nil, nil).MaxTimes(1) + storage.EXPECT().GetMoreServices(beg, false, false, false).Return(nil, nil).MaxTimes(3) + storage.EXPECT().GetRoutingConfigsForCache(beg, true).Return(nil, nil).MaxTimes(3) + storage.EXPECT().GetRoutingConfigsForCache(beg, false).Return(nil, nil).MaxTimes(3) + storage.EXPECT().GetMoreL5Routes(uint32(0)).Return(nil, nil).MaxTimes(3) + storage.EXPECT().GetMoreL5Policies(uint32(0)).Return(nil, nil).MaxTimes(3) + storage.EXPECT().GetMoreL5Sections(uint32(0)).Return(nil, nil).MaxTimes(3) + storage.EXPECT().GetMoreL5IPConfigs(uint32(0)).Return(nil, nil).MaxTimes(3) + storage.EXPECT().GetRateLimitsForCache(beg, true).Return(nil, nil, nil).MaxTimes(1) + storage.EXPECT().GetRateLimitsForCache(beg, false).Return(nil, nil, nil).MaxTimes(3) + storage.EXPECT().GetCircuitBreakerForCache(beg, true).Return(nil, nil).MaxTimes(1) + storage.EXPECT().GetCircuitBreakerForCache(beg, false).Return(nil, nil).MaxTimes(3) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + err = c.initialize() + So(err, ShouldBeNil) + + err = c.Start(ctx) + So(err, ShouldBeNil) + + // 等待cache更新 + time.Sleep(c.GetUpdateCacheInterval() + time.Second) + }) +} + +// 测试revision的管道是否正常 +func TestRevisionWorker(t *testing.T) { + ctl := gomock.NewController(t) + storage := mock.NewMockStore(ctl) + defer ctl.Finish() + + Convey("revision计算,chan可以正常收发", t, func() { + nc, err := NewNamingCache(storage) + defer func() { _ = nc.Clear() }() + So(err, ShouldBeNil) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go nc.revisionWorker(ctx) + + Convey("revision计算,实例有增加有减少,计算正常", func() { + _ = nc.Clear() + // mock一下cache中服务的数据 + maxTotal := 20480 + services := make(map[string]*model.Service) + for i := 0; i < maxTotal; i++ { + item := &model.Service{ + ID: fmt.Sprintf("service-id-%d", i), + Revision: fmt.Sprintf("revision-%d", i), + Valid: true, + } + services[item.ID] = item + } + storage.EXPECT().GetMoreServices(time.Unix(0, 0).Add(DefaultTimeDiff), true, false, false).Return(services, nil) + // 触发计算 + _ = nc.caches[CacheService].update() + time.Sleep(time.Second * 2) + So(nc.GetServiceRevisionCount(), ShouldEqual, maxTotal) + + services = make(map[string]*model.Service) + for i := 0; i < maxTotal; i++ { + if i%2 == 0 { + item := &model.Service{ + ID: fmt.Sprintf("service-id-%d", i), + Revision: fmt.Sprintf("revision-%d", i), + Valid: false, + } + services[item.ID] = item + } + } + storage.EXPECT().GetMoreServices(time.Unix(0, 0).Add(DefaultTimeDiff), false, false, false).Return(services, nil) + // 触发计算 + _ = nc.caches[CacheService].update() + time.Sleep(time.Second * 2) + // 检查是否有正常计算 + So(nc.GetServiceRevisionCount(), ShouldEqual, maxTotal/2) + }) + }) +} + +// 测试计算revision的函数 +func TestComputeRevision(t *testing.T) { + Convey("instances为空,可以正常计算", t, func() { + out, err := ComputeRevision("123", nil) + So(err, ShouldBeNil) + So(out, ShouldNotBeEmpty) + }) + + Convey("instances内容一样,不同顺序,计算出的revision一样", t, func() { + instances := make([]*model.Instance, 0, 6) + for i := 0; i < 6; i++ { + instances = append(instances, &model.Instance{ + Proto: &v1.Instance{ + Revision: utils.NewStringValue(fmt.Sprintf("revision-%d", i)), + }, + }) + } + + lhs, err := ComputeRevision("123", nil) + So(err, ShouldBeNil) + So(lhs, ShouldNotBeEmpty) + + // 交换一下数据,数据内容不变,revision应该保证不变 + tmp := instances[0] + instances[0] = instances[1] + instances[1] = instances[3] + instances[3] = tmp + + rhs, err := ComputeRevision("123", nil) + So(err, ShouldBeNil) + So(lhs, ShouldEqual, rhs) + }) + + Convey("serviceRevision发生改变,返回改变", t, func() { + lhs, err := ComputeRevision("123", nil) + So(err, ShouldBeNil) + So(lhs, ShouldNotBeEmpty) + + rhs, err := ComputeRevision("456", nil) + So(err, ShouldBeNil) + So(lhs, ShouldNotEqual, rhs) + }) + + Convey("instances内容改变,返回改变", t, func() { + instance := &model.Instance{Proto: &v1.Instance{Revision: utils.NewStringValue("123456")}} + lhs, err := ComputeRevision("123", []*model.Instance{instance}) + So(err, ShouldBeNil) + So(lhs, ShouldNotBeEmpty) + + instance.Proto.Revision.Value = "654321" + rhs, err := ComputeRevision("456", []*model.Instance{instance}) + So(err, ShouldBeNil) + So(lhs, ShouldNotEqual, rhs) + }) +} diff --git a/naming/cache/circuitbreaker_config.go b/naming/cache/circuitbreaker_config.go new file mode 100644 index 000000000..ad5a8978d --- /dev/null +++ b/naming/cache/circuitbreaker_config.go @@ -0,0 +1,162 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package cache + +import ( + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/store" + "sync" + "time" +) + +const ( + CircuitBreakerName = "circuitBreakerConfig" +) + +// circuitBreaker配置的cache接口 +type CircuitBreakerCache interface { + Cache + + // 根据ServiceID获取熔断配置 + GetCircuitBreakerConfig(id string) *model.ServiceWithCircuitBreaker +} + +/** + * @brief circuitBreaker的实现 + */ +type circuitBreakerCache struct { + storage store.Store + ids *sync.Map + lastTime time.Time + firstUpdate bool +} + +/** + * @brief 返回一个操作CircuitBreakerCache的对象 + */ +func newCircuitBreakerCache(s store.Store) *circuitBreakerCache { + return &circuitBreakerCache{ + storage: s, + } +} + +/** + * @brief 实现Cache接口的函数 + */ +func (cbc *circuitBreakerCache) initialize(opt map[string]interface{}) error { + cbc.ids = new(sync.Map) + cbc.lastTime = time.Unix(0, 0) + cbc.firstUpdate = true + if opt == nil { + return nil + } + return nil +} + +/** + * @brief 实现Cache接口的函数 + */ +func (cbc *circuitBreakerCache) update() error { + out, err := cbc.storage.GetCircuitBreakerForCache(cbc.lastTime.Add(DefaultTimeDiff), cbc.firstUpdate) + if err != nil { + log.Errorf("[Cache] circuit breaker config cache update err:%s", err.Error()) + return err + } + + cbc.firstUpdate = false + return cbc.setCircuitBreaker(out) +} + +/** + * @brief 实现Cache接口的函数 + */ +func (cbc *circuitBreakerCache) clear() error { + cbc.ids = new(sync.Map) + cbc.lastTime = time.Unix(0, 0) + return nil +} + +/** + * @brief 实现资源名称 + */ +func (cbc *circuitBreakerCache) name() string { + return CircuitBreakerName +} + +/** + * @brief 根据serviceID获取熔断规则 + */ +func (cbc *circuitBreakerCache) GetCircuitBreakerConfig(id string) *model.ServiceWithCircuitBreaker { + if id == "" { + return nil + } + + value, ok := cbc.ids.Load(id) + if !ok { + return nil + } + + return value.(*model.ServiceWithCircuitBreaker) +} + +/** + * @brief 更新store的数据到cache中 + */ +func (cbc *circuitBreakerCache) setCircuitBreaker(cb []*model.ServiceWithCircuitBreaker) error { + if len(cb) == 0 { + return nil + } + + lastTime := cbc.lastTime.Unix() + for _, entry := range cb { + if entry.ServiceID == "" { + continue + } + + if entry.ModifyTime.Unix() > lastTime { + lastTime = entry.ModifyTime.Unix() + } + + if entry.Valid == false { + cbc.ids.Delete(entry.ServiceID) + continue + } + + cbc.ids.Store(entry.ServiceID, entry) + } + + if cbc.lastTime.Unix() < lastTime { + cbc.lastTime = time.Unix(lastTime, 0) + } + return nil +} + +/** + * @brief 获取熔断规则总数 + */ +func (cbc *circuitBreakerCache) GetCircuitBreakerCount(f func(k, v interface{}) bool) { + cbc.ids.Range(f) +} + +/** + * @brief 自注册到缓存列表 + */ +func init() { + RegisterCache(CircuitBreakerName, CacheCircuitBreaker) +} diff --git a/naming/cache/circuitbreaker_config_test.go b/naming/cache/circuitbreaker_config_test.go new file mode 100644 index 000000000..eac6b16c4 --- /dev/null +++ b/naming/cache/circuitbreaker_config_test.go @@ -0,0 +1,235 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package cache + +import ( + "fmt" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/store/mock" + "github.com/golang/mock/gomock" + "testing" + "time" +) + +/** + * @brief 创建一个测试mock circuitBreakerCache + */ +func newTestCircuitBreakerCache(t *testing.T) (*gomock.Controller, *mock.MockStore, *circuitBreakerCache) { + ctl := gomock.NewController(t) + + storage := mock.NewMockStore(ctl) + rlc := newCircuitBreakerCache(storage) + var opt map[string]interface{} + _ = rlc.initialize(opt) + return ctl, storage, rlc +} + +/** + * @brief 生成熔断规则测试数据 + */ +func genModelCircuitBreakers(beginNum, total int) []*model.ServiceWithCircuitBreaker { + out := make([]*model.ServiceWithCircuitBreaker, 0, total) + + for i := beginNum; i < total+beginNum; i++ { + item := &model.ServiceWithCircuitBreaker{ + ServiceID: fmt.Sprintf("id-%d", i), + CircuitBreaker: &model.CircuitBreaker{ + ID: fmt.Sprintf("id-%d", i), + Version: fmt.Sprintf("version-%d", i), + }, + Valid: true, + ModifyTime: time.Unix(int64(i), 0), + } + out = append(out, item) + } + return out +} + +/** + * @brief 统计缓存中的熔断数据 + */ +func getCircuitBreakerCount(cbc *circuitBreakerCache) int { + count := 0 + proc := func(k, v interface{}) bool { + count++ + return true + } + cbc.GetCircuitBreakerCount(proc) + return count +} + +/** + * @brief 生成熔断规则测试数据 + */ +func TestCircuitBreakersUpdate(t *testing.T) { + ctl, storage, cbc := newTestCircuitBreakerCache(t) + defer ctl.Finish() + + total := 10 + serviceWithCircuitBreakers := genModelCircuitBreakers(0, total) + + t.Run("正常更新缓存,可以获取到数据", func(t *testing.T) { + _ = cbc.clear() + + storage.EXPECT().GetCircuitBreakerForCache(cbc.lastTime.Add(DefaultTimeDiff), cbc.firstUpdate). + Return(serviceWithCircuitBreakers, nil) + if err := cbc.update(); err != nil { + t.Fatalf("error: %s", err.Error()) + } + + // 检查数目是否一致 + if getCircuitBreakerCount(cbc) == total { + t.Log("pass") + } else { + t.Fatalf("actual count is %d", getCircuitBreakerCount(cbc)) + } + }) + + t.Run("缓存数据为空", func(t *testing.T) { + _ = cbc.clear() + + storage.EXPECT().GetCircuitBreakerForCache(cbc.lastTime.Add(DefaultTimeDiff), cbc.firstUpdate). + Return(nil, nil) + if err := cbc.update(); err != nil { + t.Fatalf("error: %s", err.Error()) + } + + if getCircuitBreakerCount(cbc) == 0 { + t.Log("pass") + } else { + t.Fatalf("actual count is %d", getCircuitBreakerCount(cbc)) + } + }) + + t.Run("lastMtime正确更新", func(t *testing.T) { + _ = cbc.clear() + + currentTime := time.Unix(100, 0) + serviceWithCircuitBreakers[0].ModifyTime = currentTime + storage.EXPECT().GetCircuitBreakerForCache(cbc.lastTime.Add(DefaultTimeDiff), cbc.firstUpdate). + Return(serviceWithCircuitBreakers, nil) + if err := cbc.update(); err != nil { + t.Fatalf("error: %s", err.Error()) + } + + if cbc.lastTime.Unix() == currentTime.Unix() { + t.Log("pass") + } else { + t.Fatalf("last mtime error") + } + }) + + t.Run("数据库返回错误, update错误", func(t *testing.T) { + storage.EXPECT().GetCircuitBreakerForCache(cbc.lastTime.Add(DefaultTimeDiff), cbc.firstUpdate). + Return(nil, fmt.Errorf("storage error")) + if err := cbc.update(); err != nil { + t.Log("pass") + } else { + t.Fatalf("error") + } + }) +} + +/** + * @brief 统计缓存中的熔断规则数据 + */ +func TestCircuitBreakerUpdate2(t *testing.T) { + ctl, storage, cbc := newTestCircuitBreakerCache(t) + defer ctl.Finish() + + total := 10 + + t.Run("更新缓存后,增加部分数据,缓存正常更新", func(t *testing.T) { + _ = cbc.clear() + + serviceWithCircuitBreakers := genModelCircuitBreakers(0, total) + storage.EXPECT().GetCircuitBreakerForCache(cbc.lastTime.Add(DefaultTimeDiff), cbc.firstUpdate). + Return(serviceWithCircuitBreakers, nil) + if err := cbc.update(); err != nil { + t.Fatalf("error: %s", err.Error()) + } + + serviceWithCircuitBreakers = genModelCircuitBreakers(10, total) + storage.EXPECT().GetCircuitBreakerForCache(cbc.lastTime.Add(DefaultTimeDiff), cbc.firstUpdate). + Return(serviceWithCircuitBreakers, nil) + if err := cbc.update(); err != nil { + t.Fatalf("error: %s", err.Error()) + } + + if getCircuitBreakerCount(cbc) == total*2 { + t.Log("pass") + } else { + t.Fatalf("actual count is %d", getCircuitBreakerCount(cbc)) + } + }) + + t.Run("更新缓存后,删除部分数据,缓存正常更新", func(t *testing.T) { + _ = cbc.clear() + + serviceWithCircuitBreakers := genModelCircuitBreakers(0, total) + storage.EXPECT().GetCircuitBreakerForCache(cbc.lastTime.Add(DefaultTimeDiff), cbc.firstUpdate). + Return(serviceWithCircuitBreakers, nil) + if err := cbc.update(); err != nil { + t.Fatalf("error: %s", err.Error()) + } + + for i := 0; i < total; i += 2 { + serviceWithCircuitBreakers[i].Valid = false + } + + storage.EXPECT().GetCircuitBreakerForCache(cbc.lastTime.Add(DefaultTimeDiff), cbc.firstUpdate). + Return(serviceWithCircuitBreakers, nil) + if err := cbc.update(); err != nil { + t.Fatalf("error: %s", err.Error()) + } + + if getCircuitBreakerCount(cbc) == total/2 { + t.Log("pass") + } else { + t.Fatalf("actual count is %d", getCircuitBreakerCount(cbc)) + } + }) +} + +/** + * @brief 根据服务id获取熔断规则数据 + */ +func TestGetCircuitBreakerByServiceID(t *testing.T) { + ctl, storage, cbc := newTestCircuitBreakerCache(t) + defer ctl.Finish() + + t.Run("通过服务ID获取数据", func(t *testing.T) { + _ = cbc.clear() + + total := 10 + serviceWithCircuitBreakers := genModelCircuitBreakers(0, total) + storage.EXPECT().GetCircuitBreakerForCache(cbc.lastTime.Add(DefaultTimeDiff), cbc.firstUpdate). + Return(serviceWithCircuitBreakers, nil) + if err := cbc.update(); err != nil { + t.Fatalf("error: %s", err.Error()) + } + + cb := cbc.GetCircuitBreakerConfig(serviceWithCircuitBreakers[0].ServiceID) + expectCb := serviceWithCircuitBreakers[0].CircuitBreaker + if cb.CircuitBreaker.ID == expectCb.ID && cb.CircuitBreaker.Version == expectCb.Version { + t.Log("pass") + } else { + t.Fatalf("error circuit breaker is %+v", cb) + } + }) +} diff --git a/naming/cache/config.go b/naming/cache/config.go new file mode 100644 index 000000000..eee9a8483 --- /dev/null +++ b/naming/cache/config.go @@ -0,0 +1,45 @@ +/** + * Tencent is pleased to support the open source community by making CL5 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package cache + +/** + * @brief 缓存配置 + */ +type Config struct { + Open bool `yaml:"open"` + Resources []ConfigEntry +} + +/* + * @brief 单个缓存资源配置 + */ +type ConfigEntry struct { + Name string `yaml:"name"` + Option map[string]interface{} `yaml:"option"` +} + +var ( + config *Config +) + +/** + * @brief 设置缓存配置 + */ +func SetCacheConfig(conf *Config) { + config = conf +} diff --git a/naming/cache/instance.go b/naming/cache/instance.go new file mode 100644 index 000000000..86431222d --- /dev/null +++ b/naming/cache/instance.go @@ -0,0 +1,312 @@ +/** + * Tencent is pleased to support the open source community by making CL5 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package cache + +import ( + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/store" + "go.uber.org/zap" + "sync" + "time" +) + +const ( + InstanceName = "instance" +) + +type InstanceIterProc func(key string, value *model.Instance) (bool, error) + +/** + * @brief 实例相关的缓存接口 + */ +type InstanceCache interface { + Cache + GetInstance(instanceID string) *model.Instance + // 根据服务名获取实例,先查找服务名对应的服务ID,再找实例列表 + GetInstancesByServiceID(serviceID string) []*model.Instance + // 迭代 + IteratorInstances(iterProc InstanceIterProc) error + // 根据服务ID进行迭代 + IteratorInstancesWithService(serviceID string, iterProc InstanceIterProc) error + // 获取instance的个数 + GetInstancesCount() int +} + +/** + * @brief 实例缓存的类 + */ +type instanceCache struct { + storage store.Store + lastMtime time.Time + firstUpdate bool + ids *sync.Map // id -> instance + services *sync.Map // service id -> [instances] + revisionCh chan *revisionNotify + disableBusiness bool + needMeta bool + systemServiceID []string +} + +// 新建一个instanceCache +func newInstanceCache(storage store.Store, ch chan *revisionNotify) *instanceCache { + return &instanceCache{ + storage: storage, + revisionCh: ch, + } +} + +/** + * @brief 初始化函数 + */ +func (ic *instanceCache) initialize(opt map[string]interface{}) error { + ic.ids = new(sync.Map) + ic.services = new(sync.Map) + ic.lastMtime = time.Unix(0, 0) + ic.firstUpdate = true + if opt == nil { + return nil + } + ic.disableBusiness, _ = opt["disableBusiness"].(bool) + ic.needMeta, _ = opt["needMeta"].(bool) + // 只加载系统服务 + if ic.disableBusiness { + services, err := ic.getSystemServices() + if err != nil { + return err + } + ic.systemServiceID = make([]string, 0, len(services)) + for _, service := range services { + if service.IsAlias() { + continue + } + ic.systemServiceID = append(ic.systemServiceID, service.ID) + } + } + return nil +} + +/** + * @brief 更新缓存函数 + */ +func (ic *instanceCache) update() error { + // 拉取diff前的所有数据 + start := time.Now() + instances, err := ic.storage.GetMoreInstances(ic.lastMtime.Add(DefaultTimeDiff), + ic.firstUpdate, ic.needMeta, ic.systemServiceID) + if err != nil { + log.Errorf("[Cache][Instance] update get storage more err: %s", err.Error()) + return err + } + + ic.firstUpdate = false + update, del := ic.setInstances(instances) + log.Info("[Cache][Instance] get more instances", zap.Int("update", update), zap.Int("delete", del), + zap.Time("last", ic.lastMtime), zap.Duration("used", time.Now().Sub(start))) + return nil +} + +/** + * @brief 清理内部缓存数据 + */ +func (ic *instanceCache) clear() error { + ic.ids = new(sync.Map) + ic.services = new(sync.Map) + ic.lastMtime = time.Unix(0, 0) + return nil +} + +/** + * @brief 获取资源名称 + */ +func (ic *instanceCache) name() string { + return InstanceName +} + +/** + * @brief 获取系统服务ID + */ +func (ic *instanceCache) getSystemServices() ([]*model.Service, error) { + services, err := ic.storage.GetSystemServices() + if err != nil { + log.Errorf("[Cache][Instance] get system services err: %s", err.Error()) + return nil, err + } + return services, nil +} + +// 保存instance到内存中 +// 返回:更新个数,删除个数 +func (ic *instanceCache) setInstances(ins map[string]*model.Instance) (int, int) { + if len(ins) == 0 { + return 0, 0 + } + + lastMtime := ic.lastMtime.Unix() + update := 0 + del := 0 + affect := make(map[string]bool) + progress := 0 + for _, item := range ins { + progress++ + if progress%50000 == 0 { + log.Infof("[Cache][Instance] set instances progress: %d / %d", progress, len(ins)) + } + modifyTime := item.ModifyTime.Unix() + if lastMtime < modifyTime { + lastMtime = modifyTime + } + affect[item.ServiceID] = true + + // 待删除的instance + if !item.Valid { + del++ + ic.ids.Delete(item.ID()) + value, ok := ic.services.Load(item.ServiceID) + if !ok { + continue + } + + value.(*sync.Map).Delete(item.ID()) + continue + } + + // 有修改或者新增的数据 + // 缓存的instance map增加一个version和protocol字段 + update++ + if item.Proto.Metadata == nil { + item.Proto.Metadata = make(map[string]string) + } + item.Proto.Metadata["version"] = item.Version() + item.Proto.Metadata["protocol"] = item.Protocol() + ic.ids.Store(item.ID(), item) + value, ok := ic.services.Load(item.ServiceID) + if !ok { + value = new(sync.Map) + ic.services.Store(item.ServiceID, value) + } + value.(*sync.Map).Store(item.ID(), item) + } + + if ic.lastMtime.Unix() < lastMtime { + ic.lastMtime = time.Unix(lastMtime, 0) + } + + progress = 0 + for serviceID := range affect { + ic.revisionCh <- newRevisionNotify(serviceID, true) + progress++ + if progress%10000 == 0 { + log.Infof("[Cache][Instance] revision notify progress(%d / %d)", progress, len(affect)) + } + } + + return update, del +} + +/** + * @brief 根据实例ID获取实例数据 + */ +func (ic *instanceCache) GetInstance(instanceID string) *model.Instance { + if instanceID == "" { + return nil + } + + value, ok := ic.ids.Load(instanceID) + if !ok { + return nil + } + + return value.(*model.Instance) +} + +/** + * @brief 根据ServiceID获取实例数据 + */ +func (ic *instanceCache) GetInstancesByServiceID(serviceID string) []*model.Instance { + if serviceID == "" { + return nil + } + + value, ok := ic.services.Load(serviceID) + if !ok { + return nil + } + + var out []*model.Instance + value.(*sync.Map).Range(func(k interface{}, v interface{}) bool { + out = append(out, v.(*model.Instance)) + return true + }) + + return out +} + +/** + * @brief 迭代所有的instance的函数 + */ +func (ic *instanceCache) IteratorInstances(iterProc InstanceIterProc) error { + return iteratorInstancesProc(ic.ids, iterProc) +} + +// 根据服务ID进行迭代回调 +func (ic *instanceCache) IteratorInstancesWithService(serviceID string, iterProc InstanceIterProc) error { + if serviceID == "" { + return nil + } + value, ok := ic.services.Load(serviceID) + if !ok { + return nil + } + + return iteratorInstancesProc(value.(*sync.Map), iterProc) +} + +// 获取实例的个数 +func (ic *instanceCache) GetInstancesCount() int { + count := 0 + ic.ids.Range(func(key, value interface{}) bool { + count++ + return true + }) + + return count +} + +// 迭代指定的instance数据,id->instance +func iteratorInstancesProc(data *sync.Map, iterProc InstanceIterProc) error { + var cont bool + var err error + proc := func(k, v interface{}) bool { + cont, err = iterProc(k.(string), v.(*model.Instance)) + if err != nil { + return false + } + return cont + } + + data.Range(proc) + return err +} + +/** + * @brief 自注册到缓存列表 + */ +func init() { + RegisterCache(InstanceName, CacheInstance) +} diff --git a/naming/cache/instance_test.go b/naming/cache/instance_test.go new file mode 100644 index 000000000..c53df4b2c --- /dev/null +++ b/naming/cache/instance_test.go @@ -0,0 +1,256 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package cache + +import ( + "fmt" + v1 "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/common/utils" + "github.com/polarismesh/polaris-server/store/mock" + "github.com/golang/mock/gomock" + "testing" + "time" +) + +// 创建一个测试mock instanceCache +func newTestInstanceCache(t *testing.T) (*gomock.Controller, *mock.MockStore, *instanceCache) { + ctl := gomock.NewController(t) + + storage := mock.NewMockStore(ctl) + ic := newInstanceCache(storage, make(chan *revisionNotify, 1024)) + opt := map[string]interface{}{ + "disableBusiness": false, + "needMeta": true, + } + _ = ic.initialize(opt) + + return ctl, storage, ic +} + +// 生成测试数据 +func genModelInstances(label string, total int) map[string]*model.Instance { + out := make(map[string]*model.Instance) + for i := 0; i < total; i++ { + entry := &model.Instance{ + Proto: &v1.Instance{ + Id: utils.NewStringValue(fmt.Sprintf("instanceID-%s-%d", label, i)), + Host: utils.NewStringValue(fmt.Sprintf("host-%s-%d", label, i)), + Port: utils.NewUInt32Value(uint32(i + 10)), + }, + ServiceID: fmt.Sprintf("serviceID-%s", label), + Valid: true, + } + + out[entry.Proto.Id.GetValue()] = entry + } + + return out +} + +// 对instanceCache的缓存数据进行计数统计 +func iteratorInstances(ic *instanceCache) (int, int) { + instancesCount := 0 + services := make(map[string]bool) + _ = ic.IteratorInstances(func(key string, value *model.Instance) (b bool, e error) { + instancesCount++ + if _, ok := services[value.ServiceID]; !ok { + services[value.ServiceID] = true + } + return true, nil + }) + + return len(services), instancesCount +} + +// 测试正常的更新缓存操作 +func TestInstanceCache_Update(t *testing.T) { + ctl, storage, ic := newTestInstanceCache(t) + defer ctl.Finish() + t.Run("正常更新缓存,缓存数据符合预期", func(t *testing.T) { + _ = ic.clear() + ret := make(map[string]*model.Instance) + instances1 := genModelInstances("service1", 10) // 每次gen为一个服务的 + instances2 := genModelInstances("service2", 5) + + for id, instance := range instances1 { + ret[id] = instance + } + for id, instance := range instances2 { + ret[id] = instance + } + + gomock.InOrder(storage.EXPECT(). + GetMoreInstances(ic.lastMtime.Add(DefaultTimeDiff), ic.firstUpdate, ic.needMeta, ic.systemServiceID). + Return(ret, nil)) + if err := ic.update(); err != nil { + t.Fatalf("error: %s", err.Error()) + } + + servicesCount, instancesCount := iteratorInstances(ic) + if servicesCount == 2 && instancesCount == 10+5 { // gen两次,有两个不同服务 + t.Logf("pass") + } else { + t.Fatalf("error: %d, %d", servicesCount, instancesCount) + } + }) + + t.Run("数据为空,更新的内容为空", func(t *testing.T) { + _ = ic.clear() + gomock.InOrder(storage.EXPECT(). + GetMoreInstances(ic.lastMtime.Add(DefaultTimeDiff), ic.firstUpdate, ic.needMeta, ic.systemServiceID). + Return(nil, nil)) + if err := ic.update(); err != nil { + t.Fatalf("error: %s", err.Error()) + } + + servicesCount, instancesCount := iteratorInstances(ic) + if servicesCount != 0 || instancesCount != 0 { + t.Fatalf("error: %d %d", servicesCount, instancesCount) + } + }) + + t.Run("lastMtime可以正常更新", func(t *testing.T) { + _ = ic.clear() + instances := genModelInstances("services", 10) + maxMtime := time.Unix(1000, 0) + instances[fmt.Sprintf("instanceID-%s-%d", "services", 5)].ModifyTime = maxMtime + + gomock.InOrder(storage.EXPECT(). + GetMoreInstances(ic.lastMtime.Add(DefaultTimeDiff), ic.firstUpdate, ic.needMeta, ic.systemServiceID). + Return(instances, nil)) + if err := ic.update(); err != nil { + t.Fatalf("error: %s", err.Error()) + } + + if ic.lastMtime.Unix() != maxMtime.Unix() { + t.Fatalf("error %d %d", ic.lastMtime.Unix(), maxMtime.Unix()) + } + }) +} + +// 异常场景下的update测试 +func TestInstanceCache_Update2(t *testing.T) { + ctl, storage, ic := newTestInstanceCache(t) + defer ctl.Finish() + t.Run("数据库返回失败,update会返回失败", func(t *testing.T) { + _ = ic.clear() + gomock.InOrder(storage.EXPECT(). + GetMoreInstances(ic.lastMtime.Add(DefaultTimeDiff), ic.firstUpdate, ic.needMeta, ic.systemServiceID). + Return(nil, fmt.Errorf("storage get error"))) + if err := ic.update(); err != nil { + t.Logf("pass: %s", err.Error()) + } else { + t.Errorf("error") + } + }) + t.Run("更新数据,再删除部分数据,缓存正常", func(t *testing.T) { + _ = ic.clear() + instances := genModelInstances("service-a", 20) + gomock.InOrder(storage.EXPECT(). + GetMoreInstances(ic.lastMtime.Add(DefaultTimeDiff), ic.firstUpdate, ic.needMeta, ic.systemServiceID). + Return(instances, nil)) + if err := ic.update(); err != nil { + t.Fatalf("error: %s", err.Error()) + } + + idx := 0 + for _, entry := range instances { + if idx%2 == 0 { + entry.Valid = false + } + idx++ + } + gomock.InOrder(storage.EXPECT(). + GetMoreInstances(ic.lastMtime.Add(DefaultTimeDiff), ic.firstUpdate, ic.needMeta, ic.systemServiceID). + Return(instances, nil)) + if err := ic.update(); err != nil { + t.Fatalf("error: %s", err.Error()) + } + + servicesCount, instancesCount := iteratorInstances(ic) + if servicesCount != 1 || instancesCount != 10 { + t.Fatalf("error: %d %d", servicesCount, instancesCount) + } + }) +} + +// 根据实例ID获取缓存内容 +func TestInstanceCache_GetInstance(t *testing.T) { + ctl, storage, ic := newTestInstanceCache(t) + defer ctl.Finish() + t.Run("缓存有数据,可以正常获取到数据", func(t *testing.T) { + _ = ic.clear() + instances := genModelInstances("my-services", 10) + gomock.InOrder(storage.EXPECT(). + GetMoreInstances(ic.lastMtime.Add(DefaultTimeDiff), ic.firstUpdate, ic.needMeta, ic.systemServiceID). + Return(instances, nil)) + if err := ic.update(); err != nil { + t.Fatalf("error: %s", err.Error()) + } + + if instance := ic.GetInstance(instances[fmt.Sprintf("instanceID-%s-%d", "my-services", 6)].ID()); instance == nil { + t.Fatalf("error") + } + + if instance := ic.GetInstance("test-instance-xx"); instance != nil { + t.Fatalf("error") + } + }) +} + +// 根据ServiceID获取缓存内容 +func TestGetInstancesByServiceID(t *testing.T) { + ctl, storage, ic := newTestInstanceCache(t) + defer ctl.Finish() + t.Run("可以通过serviceID获取实例信息", func(t *testing.T) { + _ = ic.clear() + instances1 := genModelInstances("my-services", 50) + instances2 := genModelInstances("my-services-a", 30) + //instances2 = append(instances2, instances1...) + + ret := make(map[string]*model.Instance) + for id, instance := range instances1 { + ret[id] = instance + } + for id, instance := range instances2 { + ret[id] = instance + } + + gomock.InOrder(storage.EXPECT(). + GetMoreInstances(ic.lastMtime.Add(DefaultTimeDiff), ic.firstUpdate, ic.needMeta, ic.systemServiceID). + Return(ret, nil)) + if err := ic.update(); err != nil { + t.Fatalf("error: %s", err.Error()) + } + + if instances := ic.GetInstancesByServiceID(instances2[fmt.Sprintf("instanceID-%s-%d", "my-services-a", 1)].ServiceID); instances != nil { + if len(instances) == 30 { + t.Logf("pass") + } else { + t.Fatalf("error") + } + } else { + t.Fatalf("error") + } + + if instances := ic.GetInstancesByServiceID("aa"); instances != nil { + t.Fatalf("error") + } + }) +} diff --git a/naming/cache/l5.go b/naming/cache/l5.go new file mode 100644 index 000000000..ec3cde7ce --- /dev/null +++ b/naming/cache/l5.go @@ -0,0 +1,434 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package cache + +import ( + "container/list" + "github.com/polarismesh/polaris-server/common/utils" + "sync" + + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/store" +) + +const ( + L5Name = "l5" +) + +/** + * @brief L5的cache接口 + */ +type L5Cache interface { + Cache + + // 根据IP获取访问关系 + GetRouteByIP(ip uint32) []*model.Route + + // 检查IP对应的SID是否存在访问关系 + CheckRouteExisted(ip uint32, modID uint32, cmdID uint32) bool + + // 获取有状态路由信息policy + GetPolicy(modID uint32) *model.Policy + + // 获取有状态路由信息policy + GetSection(modeID uint32) []*model.Section + + // 获取IpConfig + GetIPConfig(ip uint32) *model.IPConfig +} + +/** + * @brief L5的cache对象 + */ +type l5Cache struct { + storage store.Store + + lastRouteFlow uint32 + lastPolicyFlow uint32 + lastSectionFlow uint32 + lastIPConfigFlow uint32 + + routeList *sync.Map // > + policyList *sync.Map // + sectionList *sync.Map // + ipConfigList *sync.Map // + + // instances的信息 + ic *instanceCache + sc *serviceCache +} + +/** + * @brief 初始化函数 + */ +func (lc *l5Cache) initialize(opt map[string]interface{}) error { + lc.lastRouteFlow = 0 + lc.lastPolicyFlow = 0 + lc.lastSectionFlow = 0 + lc.lastIPConfigFlow = 0 + lc.routeList = new(sync.Map) + lc.policyList = new(sync.Map) + lc.sectionList = new(sync.Map) + lc.ipConfigList = new(sync.Map) + if opt == nil { + return nil + } + return nil +} + +/** + * @brief 更新缓存 + */ +func (lc *l5Cache) update() error { + err := lc.updateCL5Route() + if err != nil { + log.Errorf("[Cache][CL5] update l5 route cache err: %s", err.Error()) + } + err = lc.updateCL5Policy() + if err != nil { + log.Errorf("[Cache][CL5] update l5 policy cache err: %s", err.Error()) + } + err = lc.updateCL5Section() + if err != nil { + log.Errorf("[Cache][CL5] update l5 section cache err: %s", err.Error()) + } + return err +} + +/** + * @brief 清理内部缓存数据 + */ +func (lc *l5Cache) clear() error { + lc.routeList = new(sync.Map) + lc.policyList = new(sync.Map) + lc.sectionList = new(sync.Map) + lc.ipConfigList = new(sync.Map) + lc.lastRouteFlow = 0 + lc.lastPolicyFlow = 0 + lc.lastSectionFlow = 0 + lc.lastIPConfigFlow = 0 + return nil +} + +/** + * @brief 获取资源名称 + */ +func (lc *l5Cache) name() string { + return L5Name +} + +// 根据Ip获取访问关系 +func (lc *l5Cache) GetRouteByIP(ip uint32) []*model.Route { + out := make([]*model.Route, 0) + value, ok := lc.routeList.Load(ip) + if !ok { + // 该ip不存在访问关系,则返回一个空数组 + return out + } + + entry := value.(*sync.Map) + entry.Range(func(key, value interface{}) bool { + // sidStr -> setID + sid, err := utils.UnmarshalSid(key.(string)) + if err != nil { + return true + } + + item := &model.Route{ + IP: ip, + ModID: sid.ModID, + CmdID: sid.CmdID, + SetID: value.(string), + } + out = append(out, item) + return true + }) + + return out +} + +// 检查访问关系是否存在 +func (lc *l5Cache) CheckRouteExisted(ip uint32, modID uint32, cmdID uint32) bool { + value, ok := lc.routeList.Load(ip) + if !ok { + return false + } + + entry := value.(*sync.Map) + found := false + entry.Range(func(key, value interface{}) bool { + sid, err := utils.UnmarshalSid(key.(string)) + if err != nil { + // continue range + return true + } + + if modID == sid.ModID && cmdID == sid.CmdID { + found = true + // break range + return false + } + return true + }) + + return found +} + +// 根据modID获取policy信息 +func (lc *l5Cache) GetPolicy(modID uint32) *model.Policy { + value, ok := lc.policyList.Load(modID) + if !ok { + return nil + } + + return value.(*model.Policy) +} + +// 根据modID获取section信息 +func (lc *l5Cache) GetSection(modeID uint32) []*model.Section { + value, ok := lc.sectionList.Load(modeID) + if !ok { + return nil + } + + obj := value.(*list.List) + out := make([]*model.Section, 0, obj.Len()) + for e := obj.Front(); e != nil; e = e.Next() { + out = append(out, e.Value.(*model.Section)) + } + + return out +} + +// 根据IP获取ipConfig +func (lc *l5Cache) GetIPConfig(ip uint32) *model.IPConfig { + value, ok := lc.ipConfigList.Load(ip) + if !ok { + return nil + } + + return value.(*model.IPConfig) +} + +/** + * @brief 更新l5的route缓存数据 + */ +func (lc *l5Cache) updateCL5Route() error { + routes, err := lc.storage.GetMoreL5Routes(lc.lastRouteFlow) + if err != nil { + log.Errorf("[Cache][CL5] get l5 route from storage err: %s", err.Error()) + return err + } + + return lc.setCL5Route(routes) +} + +/** + * @brief 更新l5的policy缓存数据 + */ +func (lc *l5Cache) updateCL5Policy() error { + policies, err := lc.storage.GetMoreL5Policies(lc.lastPolicyFlow) + if err != nil { + log.Errorf("[Cache][CL5] get l5 policy from storage err: %s", err.Error()) + return err + } + + return lc.setCL5Policy(policies) +} + +/** + * @brief 更新l5的section缓存数据 + */ +func (lc *l5Cache) updateCL5Section() error { + sections, err := lc.storage.GetMoreL5Sections(lc.lastSectionFlow) + if err != nil { + log.Errorf("[Cache][CL5] get l5 section from storage err: %s", err.Error()) + return err + } + + return lc.setCL5Section(sections) +} + +/** + * @brief 更新l5的ip config缓存数据 + */ +func (lc *l5Cache) updateCL5IPConfig() error { + ipConfigs, err := lc.storage.GetMoreL5IPConfigs(lc.lastIPConfigFlow) + if err != nil { + log.Errorf("[Cache][CL5] get l5 ip config from storage err: %s", err.Error()) + return err + } + + return lc.setCL5IPConfig(ipConfigs) +} + +/** + * @brief 更新l5 route的本地缓存 + */ +func (lc *l5Cache) setCL5Route(routes []*model.Route) error { + if len(routes) == 0 { + return nil + } + + lastRouteFlow := lc.lastRouteFlow + for _, item := range routes { + if item.Flow > lastRouteFlow { + lastRouteFlow = item.Flow + } + + sidStr := utils.MarshalModCmd(item.ModID, item.CmdID) + + // 待删除的route + if !item.Valid { + value, ok := lc.routeList.Load(item.IP) + if !ok { + continue + } + + value.(*sync.Map).Delete(sidStr) + continue + } + + value, ok := lc.routeList.Load(item.IP) + if !ok { + value = new(sync.Map) + lc.routeList.Store(item.IP, value) + } + value.(*sync.Map).Store(sidStr, item.SetID) + } + + if lc.lastRouteFlow < lastRouteFlow { + lc.lastRouteFlow = lastRouteFlow + } + + return nil +} + +/** + * @brief 更新l5 policy的本地缓存 + */ +func (lc *l5Cache) setCL5Policy(policies []*model.Policy) error { + if len(policies) == 0 { + return nil + } + + lastPolicyFlow := lc.lastPolicyFlow + for _, item := range policies { + if item.Flow > lastPolicyFlow { + lastPolicyFlow = item.Flow + } + + // 待删除的policy + if !item.Valid { + lc.policyList.Delete(item.ModID) + continue + } + + lc.policyList.Store(item.ModID, item) + } + + if lc.lastPolicyFlow < lastPolicyFlow { + lc.lastPolicyFlow = lastPolicyFlow + } + + return nil +} + +/** + * @brief 更新l5 section的本地缓存 + */ +func (lc *l5Cache) setCL5Section(sections []*model.Section) error { + if len(sections) == 0 { + return nil + } + + lastSectionFlow := lc.lastSectionFlow + for _, item := range sections { + if item.Flow > lastSectionFlow { + lastSectionFlow = item.Flow + } + + // 无论数据是否要删除,都执行删除老数据操作 + var listObj *list.List + if value, ok := lc.sectionList.Load(item.ModID); ok { + listObj = value.(*list.List) + } else { + listObj = list.New() + } + + for ele := listObj.Front(); ele != nil; ele = ele.Next() { + entry := ele.Value.(*model.Section) + if entry.From == item.From && entry.To == item.To { + listObj.Remove(ele) + break + } + } + // 上面已经删除了,这里直接继续迭代 + if !item.Valid { + continue + } + + // 存储有效的数据 + listObj.PushBack(item) + lc.sectionList.Store(item.ModID, listObj) + } + + if lc.lastSectionFlow < lastSectionFlow { + lc.lastSectionFlow = lastSectionFlow + } + + return nil +} + +/** + * @brief 更新l5 ipConfig的本地缓存 + */ +func (lc *l5Cache) setCL5IPConfig(ipConfigs []*model.IPConfig) error { + if len(ipConfigs) == 0 { + return nil + } + + lastIPConfigFlow := lc.lastIPConfigFlow + for _, item := range ipConfigs { + if item.Flow > lastIPConfigFlow { + lastIPConfigFlow = item.Flow + } + + // 待删除的ip config + if !item.Valid { + lc.ipConfigList.Delete(item.IP) + continue + } + + lc.ipConfigList.Store(item.IP, item) + } + + if lc.lastIPConfigFlow < lastIPConfigFlow { + lc.lastIPConfigFlow = lastIPConfigFlow + } + + return nil +} + +/** + * @brief 自注册到缓存列表 + */ +func init() { + RegisterCache(L5Name, CacheCL5) +} diff --git a/naming/cache/ratelimit_config.go b/naming/cache/ratelimit_config.go new file mode 100644 index 000000000..559e76e6b --- /dev/null +++ b/naming/cache/ratelimit_config.go @@ -0,0 +1,261 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package cache + +import ( + "sync" + "time" + + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/store" +) + +const ( + RateLimitConfigName = "rateLimitConfig" +) + +type RateLimitIterProc func(id string, rateLimit *model.RateLimit) (bool, error) + +/** + * @brief rateLimit的cache接口 + */ +type RateLimitCache interface { + Cache + + //根据serviceID进行迭代回调 + GetRateLimit(serviceID string, rateLimitIterProc RateLimitIterProc) error + + // 根据serviceID获取最新revision + GetLastRevision(serviceID string) string + + // 根据serviceID获取限流数据 + GetRateLimitByServiceID(serviceID string) []*model.RateLimit + + // 获取revision总数 + GetRevisionsCount() int + + // 获取限流规则总数 + GetRateLimitsCount() int +} + +/** + * @brief rateLimitCache的实现 + */ +type rateLimitCache struct { + storage store.Store + ids *sync.Map + revisions *sync.Map + lastTime time.Time + firstUpdate bool +} + +/** + * @brief 返回一个操作RateLimitCache的对象 + */ +func newRateLimitCache(s store.Store) *rateLimitCache { + return &rateLimitCache{ + storage: s, + } +} + +/** + * @brief 实现Cache接口的initialize函数 + */ +func (rlc *rateLimitCache) initialize(opt map[string]interface{}) error { + rlc.ids = new(sync.Map) + rlc.revisions = new(sync.Map) + rlc.lastTime = time.Unix(0, 0) + rlc.firstUpdate = true + if opt == nil { + return nil + } + return nil +} + +/** + * @brief 实现Cache接口的update函数 + */ +func (rlc *rateLimitCache) update() error { + rateLimits, revisions, err := rlc.storage.GetRateLimitsForCache(rlc.lastTime.Add(DefaultTimeDiff), + rlc.firstUpdate) + if err != nil { + log.Errorf("[Cache] rate limit cache update err: %s", err.Error()) + return err + } + rlc.firstUpdate = false + return rlc.setRateLimit(rateLimits, revisions) +} + +/** + * @brief 获取资源名称 + */ +func (rlc *rateLimitCache) name() string { + return RateLimitConfigName +} + +/** + * @brief 实现Cache接口的clear函数 + */ +func (rlc *rateLimitCache) clear() error { + rlc.ids = new(sync.Map) + rlc.revisions = new(sync.Map) + rlc.lastTime = time.Unix(0, 0) + return nil +} + +/** + * @brief 更新限流规则到缓存中 + */ +func (rlc *rateLimitCache) setRateLimit(rateLimits []*model.RateLimit, + revisions []*model.RateLimitRevision) error { + if len(rateLimits) == 0 { + return nil + } + + lastMtime := rlc.lastTime.Unix() + for _, item := range rateLimits { + if item.ModifyTime.Unix() > lastMtime { + lastMtime = item.ModifyTime.Unix() + } + + // 待删除的rateLimit + if item.Valid == false { + value, ok := rlc.ids.Load(item.ServiceID) + if !ok { + continue + } + value.(*sync.Map).Delete(item.ID) + continue + } + + value, ok := rlc.ids.Load(item.ServiceID) + if !ok { + value = new(sync.Map) + rlc.ids.Store(item.ServiceID, value) + } + value.(*sync.Map).Store(item.ID, item) + } + + // 更新last revision + for _, item := range revisions { + rlc.revisions.Store(item.ServiceID, item.LastRevision) + } + + if rlc.lastTime.Unix() < lastMtime { + rlc.lastTime = time.Unix(lastMtime, 0) + } + return nil +} + +/** + * @brief 根据serviceID进行迭代回调 + */ +func (rlc *rateLimitCache) GetRateLimit(serviceID string, rateLimitIterProc RateLimitIterProc) error { + if serviceID == "" { + return nil + } + value, ok := rlc.ids.Load(serviceID) + if !ok { + return nil + } + + var result bool + var err error + f := func(k, v interface{}) bool { + result, err = rateLimitIterProc(k.(string), v.(*model.RateLimit)) + if err != nil { + return false + } + return result + } + + value.(*sync.Map).Range(f) + return err +} + +/** + * @brief 根据serviceID获取最新revision + */ +func (rlc *rateLimitCache) GetLastRevision(serviceID string) string { + if serviceID == "" { + return "" + } + value, ok := rlc.revisions.Load(serviceID) + if !ok { + return "" + } + return value.(string) +} + +/** + * @brief 根据serviceID获取限流数据 + */ +func (rlc *rateLimitCache) GetRateLimitByServiceID(serviceID string) []*model.RateLimit { + if serviceID == "" { + return nil + } + value, ok := rlc.ids.Load(serviceID) + if !ok { + return nil + } + + var out []*model.RateLimit + value.(*sync.Map).Range(func(k interface{}, v interface{}) bool { + out = append(out, v.(*model.RateLimit)) + return true + }) + + return out +} + +/** + * @brief 获取revisions总数 + */ +func (rlc *rateLimitCache) GetRevisionsCount() int { + count := 0 + rlc.revisions.Range(func(k interface{}, v interface{}) bool { + count++ + return true + }) + return count +} + +/** + * @brief 获取限流规则总数 + */ +func (rlc *rateLimitCache) GetRateLimitsCount() int { + count := 0 + + rlc.ids.Range(func(k interface{}, v interface{}) bool { + rateLimits := v.(*sync.Map) + rateLimits.Range(func(k interface{}, v interface{}) bool { + count++ + return true + }) + return true + }) + return count +} + +/** + * @brief 自注册到缓存列表 + */ +func init() { + RegisterCache(RateLimitConfigName, CacheRateLimit) +} diff --git a/naming/cache/ratelimit_config_test.go b/naming/cache/ratelimit_config_test.go new file mode 100644 index 000000000..62eb12abb --- /dev/null +++ b/naming/cache/ratelimit_config_test.go @@ -0,0 +1,285 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package cache + +import ( + "fmt" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/store/mock" + "github.com/golang/mock/gomock" + "testing" + "time" +) + +/** + * @brief 创建一个测试mock rateLimitCache + */ +func newTestRateLimitCache(t *testing.T) (*gomock.Controller, *mock.MockStore, *rateLimitCache) { + ctl := gomock.NewController(t) + + storage := mock.NewMockStore(ctl) + rlc := newRateLimitCache(storage) + var opt map[string]interface{} + _ = rlc.initialize(opt) + return ctl, storage, rlc +} + +/** + * @brief 生成限流规则测试数据 + */ +func genModelRateLimits(beginNum, totalServices, totalRateLimits int) ([]*model.RateLimit, []*model.RateLimitRevision) { + rateLimits := make([]*model.RateLimit, 0, totalRateLimits) + revisions := make([]*model.RateLimitRevision, 0, totalServices) + rulePerService := totalRateLimits / totalServices + + for i := beginNum; i < totalServices+beginNum; i++ { + revision := &model.RateLimitRevision{ + ServiceID: fmt.Sprintf("service-%d", i), + LastRevision: fmt.Sprintf("last-revision-%d", i), + } + revisions = append(revisions, revision) + for j := 0; j < rulePerService; j++ { + rateLimit := &model.RateLimit{ + ID: fmt.Sprintf("id-%d-%d", i, j), + ServiceID: fmt.Sprintf("service-%d", i), + ClusterID: fmt.Sprintf("cluster-%d", j), + Rule: fmt.Sprintf("rule-%d-%d", i, j), + Revision: fmt.Sprintf("revision-%d-%d", i, j), + Valid: true, + } + rateLimits = append(rateLimits, rateLimit) + } + } + return rateLimits, revisions +} + +/** + * @brief 统计缓存中的限流数据 + */ +func getRateLimitsCount(serviceID string, rlc *rateLimitCache) int { + rateLimitsCount := 0 + rateLimitIterProc := func(key string, value *model.RateLimit) (bool, error) { + rateLimitsCount++ + return true, nil + } + _ = rlc.GetRateLimit(serviceID, rateLimitIterProc) + return rateLimitsCount +} + +/** + * @brief 测试更新缓存操作 + */ +func TestRateLimitUpdate(t *testing.T) { + ctl, storage, rlc := newTestRateLimitCache(t) + defer ctl.Finish() + + totalServices := 5 + totalRateLimits := 15 + rateLimits, revisions := genModelRateLimits(0, totalServices, totalRateLimits) + + t.Run("正常更新缓存,可以获取到数据", func(t *testing.T) { + _ = rlc.clear() + + storage.EXPECT().GetRateLimitsForCache(rlc.lastTime.Add(DefaultTimeDiff), rlc.firstUpdate). + Return(rateLimits, revisions, nil) + if err := rlc.update(); err != nil { + t.Fatalf("error: %s", err.Error()) + } + + // 检查数目是否一致 + for i := 0; i < totalServices; i++ { + count := getRateLimitsCount(fmt.Sprintf("service-%d", i), rlc) + if count == totalRateLimits/totalServices { + t.Log("pass") + } else { + t.Fatalf("actual count is %d", count) + } + } + + count := rlc.GetRateLimitsCount() + if count == totalRateLimits { + t.Log("pass") + } else { + t.Fatalf("actual count is %d", count) + } + + count = rlc.GetRevisionsCount() + if count == totalServices { + t.Log("pass") + } else { + t.Fatalf("actual count is %d", count) + } + }) + + t.Run("缓存数据为空", func(t *testing.T) { + _ = rlc.clear() + + storage.EXPECT().GetRateLimitsForCache(rlc.lastTime.Add(DefaultTimeDiff), rlc.firstUpdate). + Return(nil, nil, nil) + if err := rlc.update(); err != nil { + t.Fatalf("error: %s", err.Error()) + } + + if rlc.GetRateLimitsCount() == 0 && rlc.GetRevisionsCount() == 0 { + t.Log("pass") + } else { + t.Fatalf("actual rate limits count is %d, revisions count is %d", + rlc.GetRateLimitsCount(), rlc.GetRevisionsCount()) + } + }) + + t.Run("lastMtime正确更新", func(t *testing.T) { + _ = rlc.clear() + + currentTime := time.Unix(100, 0) + rateLimits[0].ModifyTime = currentTime + storage.EXPECT().GetRateLimitsForCache(rlc.lastTime.Add(DefaultTimeDiff), rlc.firstUpdate). + Return(rateLimits, revisions, nil) + if err := rlc.update(); err != nil { + t.Fatalf("error: %s", err.Error()) + } + + if rlc.lastTime.Unix() == currentTime.Unix() { + t.Log("pass") + } else { + t.Fatalf("last mtime error") + } + }) + + t.Run("数据库返回错误,update错误", func(t *testing.T) { + storage.EXPECT().GetRateLimitsForCache(rlc.lastTime.Add(DefaultTimeDiff), rlc.firstUpdate). + Return(nil, nil, fmt.Errorf("stoarge error")) + if err := rlc.update(); err != nil { + t.Log("pass") + } else { + t.Fatalf("error") + } + }) +} + +/** + * @brief 统计缓存中的限流数据 + */ +func TestRateLimitUpdate2(t *testing.T) { + ctl, storage, rlc := newTestRateLimitCache(t) + defer ctl.Finish() + + totalServices := 5 + totalRateLimits := 15 + + t.Run("更新缓存后,增加部分数据,缓存正常更新", func(t *testing.T) { + _ = rlc.clear() + + rateLimits, revisions := genModelRateLimits(0, totalServices, totalRateLimits) + storage.EXPECT().GetRateLimitsForCache(rlc.lastTime.Add(DefaultTimeDiff), rlc.firstUpdate). + Return(rateLimits, revisions, nil) + if err := rlc.update(); err != nil { + t.Fatalf("error: %s", err.Error()) + } + + rateLimits, revisions = genModelRateLimits(5, totalServices, totalRateLimits) + storage.EXPECT().GetRateLimitsForCache(rlc.lastTime.Add(DefaultTimeDiff), rlc.firstUpdate). + Return(rateLimits, revisions, nil) + if err := rlc.update(); err != nil { + t.Fatalf("error: %s", err.Error()) + } + + if rlc.GetRateLimitsCount() == totalRateLimits*2 && rlc.GetRevisionsCount() == totalServices*2 { + t.Log("pass") + } else { + t.Fatalf("actual rate limits count is %d, revisions count is %d", rlc.GetRateLimitsCount(), rlc.GetRevisionsCount()) + } + }) + + t.Run("更新缓存后,删除部分数据,缓存正常更新", func(t *testing.T) { + _ = rlc.clear() + + rateLimits, revisions := genModelRateLimits(0, totalServices, totalRateLimits) + storage.EXPECT().GetRateLimitsForCache(rlc.lastTime.Add(DefaultTimeDiff), rlc.firstUpdate). + Return(rateLimits, revisions, nil) + if err := rlc.update(); err != nil { + t.Fatalf("error: %s", err.Error()) + } + + for i := 0; i < totalRateLimits; i += 2 { + rateLimits[i].Valid = false + } + + storage.EXPECT().GetRateLimitsForCache(rlc.lastTime.Add(DefaultTimeDiff), rlc.firstUpdate). + Return(rateLimits, revisions, nil) + if err := rlc.update(); err != nil { + t.Fatalf("error: %s", err.Error()) + } + + if rlc.GetRateLimitsCount() == totalRateLimits/2 && rlc.GetRevisionsCount() == totalServices { + t.Log("pass") + } else { + t.Fatalf("actual rate limits count is %d, revisions count is %d", + rlc.GetRateLimitsCount(), rlc.GetRevisionsCount()) + } + }) +} + +/** + * @brief 根据服务id获取限流数据和revision + */ +func TestGetRateLimitsByServiceID(t *testing.T) { + ctl, storage, rlc := newTestRateLimitCache(t) + defer ctl.Finish() + + t.Run("通过服务ID获取数据和revision", func(t *testing.T) { + _ = rlc.clear() + + totalServices := 5 + totalRateLimits := 15 + rateLimits, revisions := genModelRateLimits(0, totalServices, totalRateLimits) + + storage.EXPECT().GetRateLimitsForCache(rlc.lastTime.Add(DefaultTimeDiff), rlc.firstUpdate). + Return(rateLimits, revisions, nil) + if err := rlc.update(); err != nil { + t.Fatalf("error: %s", err.Error()) + } + + rateLimits = rlc.GetRateLimitByServiceID("service-1") + if len(rateLimits) == totalRateLimits/totalServices { + t.Log("pass") + } else { + t.Fatalf("expect num is %d, actual num is %d", totalRateLimits/totalServices, len(rateLimits)) + } + lastRevision := rlc.GetLastRevision("service-1") + if lastRevision == "last-revision-1" { + t.Log("pass") + } else { + t.Fatalf("actual last revision is %s", lastRevision) + } + + rateLimits = rlc.GetRateLimitByServiceID("service-11") + if len(rateLimits) == 0 { + t.Log("pass") + } else { + t.Fatalf("expect num is 0, actual num is %d", len(rateLimits)) + } + + lastRevision = rlc.GetLastRevision("service-11") + if lastRevision == "" { + t.Log("pass") + } else { + t.Fatalf("actual last revision is %s", lastRevision) + } + }) +} diff --git a/naming/cache/routing_config.go b/naming/cache/routing_config.go new file mode 100644 index 000000000..4195452f2 --- /dev/null +++ b/naming/cache/routing_config.go @@ -0,0 +1,150 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package cache + +import ( + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/store" + "sync" + "time" +) + +const ( + RoutingConfigName = "routingConfig" +) + +// routing配置的cache接口 +type RoutingConfigCache interface { + Cache + + // 根据ServiceID获取路由配置 + GetRoutingConfig(id string) *model.RoutingConfig + + // 获取路由配置缓存的总个数 + GetRoutingConfigCount() int +} + +// routingCache的实现 +type routingConfigCache struct { + storage store.Store + ids *sync.Map + lastMtime time.Time + firstUpdate bool +} + +// 返回一个操作RoutingConfigCache的对象 +func newRoutingConfigCache(s store.Store) *routingConfigCache { + return &routingConfigCache{ + storage: s, + } +} + +// 实现Cache接口的函数 +func (rc *routingConfigCache) initialize(opt map[string]interface{}) error { + rc.ids = new(sync.Map) + rc.lastMtime = time.Unix(0, 0) + rc.firstUpdate = true + if opt == nil { + return nil + } + return nil +} + +// 实现Cache接口的函数 +func (rc *routingConfigCache) update() error { + out, err := rc.storage.GetRoutingConfigsForCache(rc.lastMtime.Add(DefaultTimeDiff), rc.firstUpdate) + if err != nil { + log.Errorf("[Cache] routing config cache update err: %s", err.Error()) + return err + } + + rc.firstUpdate = false + return rc.setRoutingConfig(out) +} + +// 实现Cache接口的函数 +func (rc *routingConfigCache) clear() error { + return nil +} + +// 实现Cache接口的函数 +func (rc *routingConfigCache) name() string { + return RoutingConfigName +} + +// 根据ServiceID获取路由配置 +func (rc *routingConfigCache) GetRoutingConfig(id string) *model.RoutingConfig { + if id == "" { + } + + value, ok := rc.ids.Load(id) + if !ok { + return nil + } + + return value.(*model.RoutingConfig) +} + +// 获取路由配置缓存的总个数 +func (rc *routingConfigCache) GetRoutingConfigCount() int { + count := 0 + rc.ids.Range(func(key, value interface{}) bool { + count++ + return true + }) + + return count +} + +// 内部函数:更新store的数据到cache中 +func (rc *routingConfigCache) setRoutingConfig(cs []*model.RoutingConfig) error { + if len(cs) == 0 { + return nil + } + + lastMtime := rc.lastMtime.Unix() + for _, entry := range cs { + if entry.ID == "" { + continue + } + + if entry.ModifyTime.Unix() > lastMtime { + lastMtime = entry.ModifyTime.Unix() + } + + if entry.Valid == false { + rc.ids.Delete(entry.ID) + continue + } + + rc.ids.Store(entry.ID, entry) + } + + if rc.lastMtime.Unix() < lastMtime { + rc.lastMtime = time.Unix(lastMtime, 0) + } + return nil +} + +/** + * @brief 自注册到缓存列表 + */ +func init() { + RegisterCache(RoutingConfigName, CacheRoutingConfig) +} diff --git a/naming/cache/service.go b/naming/cache/service.go new file mode 100644 index 000000000..202f9ee85 --- /dev/null +++ b/naming/cache/service.go @@ -0,0 +1,328 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package cache + +import ( + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/store" + "go.uber.org/zap" + "sync" + "time" +) + +const ( + ServiceName = "service" +) + +/** + * @brief 迭代回调函数 + */ +type ServiceIterProc func(key string, value *model.Service) (bool, error) + +/** + * @brief 服务数据缓存接口 + */ +type ServiceCache interface { + Cache + + // 根据ID查询服务信息 + GetServiceByID(id string) *model.Service + + // 根据服务名查询服务信息 + GetServiceByName(name string, namespace string) *model.Service + + // 迭代缓存的服务信息 + IteratorServices(iterProc ServiceIterProc) error + + // 获取缓存中服务的个数 + GetServicesCount() int + + // 根据cl5Name获取对应的sid + GetServiceByCl5Name(cl5Name string) *model.Service +} + +/** + * @brief 服务数据缓存实现类 + */ +type serviceCache struct { + storage store.Store + lastMtime time.Time + firstUpdate bool + ids *sync.Map + names *sync.Map + cl5Sid2Name *sync.Map // 兼容Cl5,sid -> name + cl5Names *sync.Map // 兼容Cl5,name -> service + revisionCh chan *revisionNotify + disableBusiness bool + needMeta bool +} + +/** + * @brief 返回一个serviceCache + */ +func newServiceCache(storage store.Store, ch chan *revisionNotify) *serviceCache { + return &serviceCache{ + storage: storage, + revisionCh: ch, + } +} + +/** + * @brief 缓存对象初始化 + */ +func (sc *serviceCache) initialize(opt map[string]interface{}) error { + sc.lastMtime = time.Unix(0, 0) + sc.ids = new(sync.Map) + sc.names = new(sync.Map) + sc.cl5Sid2Name = new(sync.Map) + sc.cl5Names = new(sync.Map) + sc.firstUpdate = true + if opt == nil { + return nil + } + sc.disableBusiness, _ = opt["disableBusiness"].(bool) + sc.needMeta, _ = opt["needMeta"].(bool) + return nil +} + +/** + * @brief Service缓存更新函数 + * + * @note service + service_metadata作为一个整体获取 + */ +func (sc *serviceCache) update() error { + // 获取几秒前的全部数据 + start := time.Now() + services, err := sc.storage.GetMoreServices(sc.lastMtime.Add(DefaultTimeDiff), + sc.firstUpdate, sc.disableBusiness, sc.needMeta) + if err != nil { + log.Errorf("[Cache][Service] update services err: %s", err.Error()) + return err + } + + sc.firstUpdate = false + update, del := sc.setServices(services) + log.Info("[Cache][Service] get more services", zap.Int("update", update), zap.Int("delete", del), + zap.Time("last", sc.lastMtime), zap.Duration("used", time.Now().Sub(start))) + return nil +} + +/** + * @brief 清理内部缓存数据 + */ +func (sc *serviceCache) clear() error { + sc.ids = new(sync.Map) + sc.names = new(sync.Map) + sc.cl5Sid2Name = new(sync.Map) + sc.cl5Names = new(sync.Map) + sc.lastMtime = time.Unix(0, 0) + return nil +} + +/** + * @brief 获取资源名称 + */ +func (sc *serviceCache) name() string { + return ServiceName +} + +/** + * @brief 根据服务ID获取服务数据 + */ +func (sc *serviceCache) GetServiceByID(id string) *model.Service { + if id == "" { + return nil + } + + value, ok := sc.ids.Load(id) + if !ok { + return nil + } + + return value.(*model.Service) +} + +/** + * @brief 根据服务名获取服务数据 + */ +func (sc *serviceCache) GetServiceByName(name string, namespace string) *model.Service { + if name == "" || namespace == "" { + return nil + } + + spaces, ok := sc.names.Load(namespace) + if !ok { + return nil + } + value, ok := spaces.(*sync.Map).Load(name) + if !ok { + return nil + } + + return value.(*model.Service) +} + +/** + * @brief 对缓存中的服务进行迭代 + */ +func (sc *serviceCache) IteratorServices(iterProc ServiceIterProc) error { + var cont bool + var err error + proc := func(k interface{}, v interface{}) bool { + cont, err = iterProc(k.(string), v.(*model.Service)) + if err != nil { + return false + } + return cont + } + sc.ids.Range(proc) + return err +} + +// 获取缓存中服务的个数 +func (sc *serviceCache) GetServicesCount() int { + count := 0 + sc.ids.Range(func(key, value interface{}) bool { + count++ + return true + }) + + return count +} + +// 根据cl5Name获取对应的sid +func (sc *serviceCache) GetServiceByCl5Name(cl5Name string) *model.Service { + value, ok := sc.cl5Names.Load(genCl5Name(cl5Name)) + if !ok { + return nil + } + + return value.(*model.Service) +} + +// 从缓存中删除service数据 +func (sc *serviceCache) removeServices(service *model.Service) { + // 删除serviceID的索引 + sc.ids.Delete(service.ID) + + // 删除serviceName的索引 + spaceName := service.Namespace + if spaces, ok := sc.names.Load(spaceName); ok { + spaces.(*sync.Map).Delete(service.Name) + } + + /******兼容cl5******/ + if cl5Name, ok := sc.cl5Sid2Name.Load(service.Name); ok { + sc.cl5Sid2Name.Delete(service.Name) + sc.cl5Names.Delete(cl5Name) + } + /******兼容cl5******/ +} + +// 服务缓存更新 +// 返回:更新数量,删除数量 +func (sc *serviceCache) setServices(services map[string]*model.Service) (int, int) { + if len(services) == 0 { + return 0, 0 + } + + progress := 0 + update := 0 + del := 0 + lastMtime := sc.lastMtime.Unix() + for _, service := range services { + progress++ + if progress%20000 == 0 { + log.Infof("[Cache][Service] update service item progress(%d / %d)", progress, len(services)) + } + serviceMtime := service.ModifyTime.Unix() + if lastMtime < serviceMtime { + lastMtime = serviceMtime + } + spaceName := service.Namespace + // 发现有删除操作 + if !service.Valid { + sc.removeServices(service) + sc.revisionCh <- newRevisionNotify(service.ID, false) + del++ + continue + } + + update++ + sc.ids.Store(service.ID, service) + sc.revisionCh <- newRevisionNotify(service.ID, true) + + spaces, ok := sc.names.Load(spaceName) + if !ok { + spaces = new(sync.Map) + sc.names.Store(spaceName, spaces) + } + spaces.(*sync.Map).Store(service.Name, service) + + /******兼容cl5******/ + sc.updateCl5SidAndNames(service) + /******兼容cl5******/ + } + + if sc.lastMtime.Unix() < lastMtime { + sc.lastMtime = time.Unix(lastMtime, 0) + } + + return update, del +} + +// 更新cl5的服务数据 +func (sc *serviceCache) updateCl5SidAndNames(service *model.Service) { + // 不是cl5服务的,不需要更新 + if _, ok := service.Meta["internal-cl5-sid"]; !ok { + return + } + + // service更新 + // service中不存在cl5Name,可以认为是该sid删除了cl5Name,删除缓存 + // service中存在cl5Name,则更新缓存 + cl5NameMeta, ok := service.Meta["internal-cl5-name"] + sid := service.Name + if !ok { + if oldCl5Name, exist := sc.cl5Sid2Name.Load(sid); exist { + sc.cl5Sid2Name.Delete(sid) + sc.cl5Names.Delete(oldCl5Name) + } + return + } + + // 更新的service,有cl5Name + cl5Name := genCl5Name(cl5NameMeta) + sc.cl5Sid2Name.Store(sid, cl5Name) + sc.cl5Names.Store(cl5Name, service) + return +} + +// 兼容cl5Name +// 部分cl5Name与已有服务名存在冲突,因此给cl5Name加上一个前缀 +func genCl5Name(name string) string { + return "cl5." + name +} + +/** + * @brief 自注册到缓存列表 + */ +func init() { + RegisterCache(ServiceName, CacheService) +} diff --git a/naming/cache/service_test.go b/naming/cache/service_test.go new file mode 100644 index 000000000..82340bd63 --- /dev/null +++ b/naming/cache/service_test.go @@ -0,0 +1,256 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package cache + +import ( + "fmt" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/store/mock" + "github.com/golang/mock/gomock" + "testing" + "time" +) + +// 生成一个测试的serviceCache和对应的mock对象 +func newTestServiceCache(t *testing.T) (*gomock.Controller, *mock.MockStore, *serviceCache) { + ctl := gomock.NewController(t) + + storage := mock.NewMockStore(ctl) + sc := newServiceCache(storage, make(chan *revisionNotify, 1024)) + opt := map[string]interface{}{ + "disableBusiness": false, + "needMeta": true, + } + _ = sc.initialize(opt) + + return ctl, storage, sc +} + +// 获取当前缓存中的services总数 +func getServiceCacheCount(sc *serviceCache) int { + sum := 0 + _ = sc.IteratorServices(func(key string, value *model.Service) (bool, error) { + sum++ + return true, nil + }) + return sum +} + +// 生成一些测试的services +func genModelService(total int) map[string]*model.Service { + out := make(map[string]*model.Service) + for i := 0; i < total; i++ { + item := &model.Service{ + ID: fmt.Sprintf("ID-%d", i), + Namespace: fmt.Sprintf("Namespace-%d", i), + Name: fmt.Sprintf("Name-%d", i), + Valid: true, + ModifyTime: time.Unix(int64(i), 0), + } + out[item.ID] = item + } + + return out +} + +// 测试缓存更新函数 +func TestServiceUpdate(t *testing.T) { + ctl, storage, sc := newTestServiceCache(t) + defer ctl.Finish() + + t.Run("所有数据为空,可以正常获取数据", func(t *testing.T) { + gomock.InOrder( + storage.EXPECT(). + GetMoreServices(sc.lastMtime.Add(DefaultTimeDiff), sc.firstUpdate, sc.disableBusiness, sc.needMeta). + Return(nil, nil), + ) + + if err := sc.update(); err != nil { + t.Fatalf("error: %s", err.Error()) + } + + if sum := getServiceCacheCount(sc); sum != 0 { + t.Fatalf("error: %d", sum) + } + }) + t.Run("有数据更新,数据正常", func(t *testing.T) { + _ = sc.clear() + services := genModelService(100) + gomock.InOrder( + storage.EXPECT().GetMoreServices(sc.lastMtime.Add(DefaultTimeDiff), sc.firstUpdate, sc.disableBusiness, sc.needMeta). + Return(services, nil), + ) + + if err := sc.update(); err != nil { + t.Fatalf("error: %s", err.Error()) + } + + if sum := getServiceCacheCount(sc); sum != 100 { + t.Fatalf("error: %d", sum) + } + }) + t.Run("有数据更新,重复更新,数据更新正常", func(t *testing.T) { + _ = sc.clear() + services1 := genModelService(100) + services2 := genModelService(300) + gomock.InOrder( + storage.EXPECT().GetMoreServices(sc.lastMtime.Add(DefaultTimeDiff), sc.firstUpdate, sc.disableBusiness, sc.needMeta). + Return(services1, nil), + ) + + if err := sc.update(); err != nil { + t.Fatalf("error: %s", err.Error()) + } + + gomock.InOrder( + storage.EXPECT().GetMoreServices(sc.lastMtime.Add(DefaultTimeDiff), sc.firstUpdate, sc.disableBusiness, sc.needMeta). + Return(services2, nil), + ) + _ = sc.update() + if sum := getServiceCacheCount(sc); sum != 300 { + t.Fatalf("error: %d", sum) + } + }) +} + +// 测试缓存更新函数1 +func TestServiceUpdate1(t *testing.T) { + ctl, storage, sc := newTestServiceCache(t) + defer ctl.Finish() + + t.Run("服务全部被删除,会被清除掉", func(t *testing.T) { + _ = sc.clear() + services := genModelService(100) + gomock.InOrder(storage.EXPECT(). + GetMoreServices(sc.lastMtime.Add(DefaultTimeDiff), sc.firstUpdate, sc.disableBusiness, sc.needMeta).Return(services, nil)) + _ = sc.update() + + // 把所有的都置为false + for _, service := range services { + service.Valid = false + } + + gomock.InOrder(storage.EXPECT(). + GetMoreServices(sc.lastMtime.Add(DefaultTimeDiff), sc.firstUpdate, sc.disableBusiness, sc.needMeta).Return(services, nil)) + _ = sc.update() + + if sum := getServiceCacheCount(sc); sum != 0 { + t.Fatalf("error: %d", sum) + } + }) + + t.Run("服务部分被删除,缓存内容正常", func(t *testing.T) { + _ = sc.clear() + services := genModelService(100) + gomock.InOrder(storage.EXPECT(). + GetMoreServices(sc.lastMtime.Add(DefaultTimeDiff), sc.firstUpdate, sc.disableBusiness, sc.needMeta).Return(services, nil)) + _ = sc.update() + + // 把所有的都置为false + idx := 0 + for _, service := range services { + if idx%2 == 0 { + service.Valid = false + } + idx++ + } + + gomock.InOrder(storage.EXPECT(). + GetMoreServices(sc.lastMtime.Add(DefaultTimeDiff), sc.firstUpdate, sc.disableBusiness, sc.needMeta).Return(services, nil)) + _ = sc.update() + + if sum := getServiceCacheCount(sc); sum != 50 { // remain half + t.Fatalf("error: %d", sum) + } + }) +} + +// 测试缓存更新 +func TestServiceUpdate2(t *testing.T) { + ctl, storage, sc := newTestServiceCache(t) + defer ctl.Finish() + + t.Run("store返回失败,update会返回失败", func(t *testing.T) { + _ = sc.clear() + gomock.InOrder( + storage.EXPECT().GetMoreServices(sc.lastMtime.Add(DefaultTimeDiff), sc.firstUpdate, sc.disableBusiness, sc.needMeta). + Return(nil, fmt.Errorf("store error")), + ) + + if err := sc.update(); err != nil { + t.Logf("pass: %s", err.Error()) + } else { + t.Fatalf("error") + } + }) +} + +// 根据服务名获取服务缓存信息 +func TestGetServiceByName(t *testing.T) { + ctl, _, sc := newTestServiceCache(t) + defer ctl.Finish() + t.Run("可以根据服务名和命名空间,正常获取缓存服务信息", func(t *testing.T) { + _ = sc.clear() + services := genModelService(20) + sc.setServices(services) + + for _, entry := range services { + service := sc.GetServiceByName(entry.Name, entry.Namespace) + if service == nil { + t.Fatalf("error") + } + } + }) + t.Run("服务不存在,返回为空", func(t *testing.T) { + _ = sc.clear() + services := genModelService(20) + sc.setServices(services) + if service := sc.GetServiceByName("aaa", "bbb"); service != nil { + t.Fatalf("error") + } + }) +} + +// 根据服务ID获取服务缓存信息 +func TestServiceCache_GetServiceByID(t *testing.T) { + ctl, _, sc := newTestServiceCache(t) + defer ctl.Finish() + + t.Run("可以根据服务ID,正常获取缓存的服务信息", func(t *testing.T) { + _ = sc.clear() + services := genModelService(30) + sc.setServices(services) + + for _, entry := range services { + service := sc.GetServiceByID(entry.ID) + if service == nil { + t.Fatalf("error") + } + } + }) + + t.Run("缓存内容为空,根据ID获取数据,会返回为空", func(t *testing.T) { + _ = sc.clear() + services := genModelService(30) + sc.setServices(services) + + if service := sc.GetServiceByID("123456789"); service != nil { + t.Fatalf("error") + } + }) +} diff --git a/naming/cache/type1rule.yaml b/naming/cache/type1rule.yaml new file mode 100644 index 000000000..513800b78 --- /dev/null +++ b/naming/cache/type1rule.yaml @@ -0,0 +1,26 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: reviews-route +spec: + hosts: + - reviews.prod.svc.cluster.local + http: + - name: "reviews-v2-routes" + match: + - uri: + prefix: "/wpcatalog" + - uri: + prefix: "/consumercatalog" + rewrite: + uri: "/newcatalog" + route: + - destination: + host: reviews.prod.svc.cluster.local + subset: v2 + - name: "reviews-v1-route" + route: + - destination: + host: reviews.prod.svc.cluster.local + subset: v1 + diff --git a/naming/cache/type2rule.yaml b/naming/cache/type2rule.yaml new file mode 100644 index 000000000..2612aee28 --- /dev/null +++ b/naming/cache/type2rule.yaml @@ -0,0 +1,51 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: Gateway +metadata: + name: my-gateway + namespace: some-config-namespace +spec: + selector: + app: my-gateway-controller + servers: + - port: + number: 80 + name: http + protocol: HTTP + hosts: + - uk.bookinfo.com + - eu.bookinfo.com + tls: + httpsRedirect: true + - port: + number: 443 + name: https-443 + protocol: HTTPS + hosts: + - uk.bookinfo.com + - eu.bookinfo.com + tls: + mode: SIMPLE + serverCertificate: /etc/certs/servercert.pem + privateKey: /etc/certs/privatekey.pem + - port: + number: 9443 + name: https-9443 + protocol: HTTPS + hosts: + - "bookinfo-namespace/*.bookinfo.com" + tls: + mode: SIMPLE + credentialName: bookinfo-secret + - port: + number: 9080 + name: http-wildcard + protocol: HTTP + hosts: + - "*" + - port: + number: 2379 + name: mongo + protocol: MONGO + hosts: + - "*" + diff --git a/naming/circuitbreaker_config.go b/naming/circuitbreaker_config.go new file mode 100644 index 000000000..ef1dcd201 --- /dev/null +++ b/naming/circuitbreaker_config.go @@ -0,0 +1,1268 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package naming + +import ( + "context" + "encoding/json" + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/common/utils" + "go.uber.org/zap" +) + +const ( + Master = "master" + Service = "service" + Namespace = "namespace" + ID = "id" + Version = "version" +) + +var ( + MasterCircuitBreakers = map[string]bool{ + "id": true, + "namespace": true, + "name": true, + "owner": true, + "business": true, + "department": true, + "offset": true, + "limit": true, + } + + ReleaseCircuitBreakers = map[string]bool{ + "id": true, // 必填参数 + "version": true, + "offset": true, + "limit": true, + } + + ServiceParams = map[string]bool{ + Service: true, + Namespace: true, + } +) + +/** + * @brief 批量创建熔断规则 + */ +func (s *Server) CreateCircuitBreakers(ctx context.Context, req []*api.CircuitBreaker) *api.BatchWriteResponse { + if checkErr := checkBatchCircuitBreakers(req); checkErr != nil { + return checkErr + } + + resps := api.NewBatchWriteResponse(api.ExecuteSuccess) + for _, circuitBreaker := range req { + resp := s.CreateCircuitBreaker(ctx, circuitBreaker) + resps.Collect(resp) + } + return api.FormatBatchWriteResponse(resps) +} + +/** + * @brief 创建单个熔断规则 + */ +func (s *Server) CreateCircuitBreaker(ctx context.Context, req *api.CircuitBreaker) *api.Response { + requestID := ParseRequestID(ctx) + + // 参数校验并生成规则id + id, resp := checkCreateCircuitBreaker(req) + if resp != nil { + return resp + } + + // 生成version + version := Master + + // 检查熔断规则是否存在 + circuitBreaker, err := s.storage.GetCircuitBreaker(id, version) + if err != nil { + log.Error(err.Error(), ZapRequestID(requestID)) + return api.NewCircuitBreakerResponse(api.StoreLayerException, req) + } + if circuitBreaker != nil { + req.Id = utils.NewStringValue(id) + return api.NewCircuitBreakerResponse(api.ExistedResource, req) + } + + // 构造底层数据结构 + token := NewUUID() + + data, err := api2CircuitBreaker(req, id, token, version) + if err != nil { + log.Error(err.Error(), ZapRequestID(requestID)) + return api.NewCircuitBreakerResponse(api.ParseCircuitBreakerException, req) + } + + // 执行存储层操作 + if err := s.storage.CreateCircuitBreaker(data); err != nil { + log.Error(err.Error(), ZapRequestID(requestID)) + return wrapperCircuitBreakerStoreResponse(req, err) + } + + msg := fmt.Sprintf("create circuit breaker: id=%v, version=%v, name=%v, namespace=%v", + data.ID, data.Version, data.Name, data.Namespace) + log.Info(msg, ZapRequestID(requestID)) + + // todo 记录操作记录 + + // 返回请求结果 + req.Id = utils.NewStringValue(data.ID) + req.Token = utils.NewStringValue(data.Token) + req.Version = utils.NewStringValue(Master) + + return api.NewCircuitBreakerResponse(api.ExecuteSuccess, req) +} + +/** + * @brief 批量创建熔断规则版本 + */ +func (s *Server) CreateCircuitBreakerVersions(ctx context.Context, req []*api.CircuitBreaker) *api.BatchWriteResponse { + if checkErr := checkBatchCircuitBreakers(req); checkErr != nil { + return checkErr + } + + resps := api.NewBatchWriteResponse(api.ExecuteSuccess) + for _, circuitBreaker := range req { + resp := s.CreateCircuitBreakerVersion(ctx, circuitBreaker) + resps.Collect(resp) + } + return api.FormatBatchWriteResponse(resps) +} + +/** + * @brief 创建单个熔断规则版本 + */ +func (s *Server) CreateCircuitBreakerVersion(ctx context.Context, req *api.CircuitBreaker) *api.Response { + requestID := ParseRequestID(ctx) + + // 参数检查 + id, resp := checkReviseCircuitBreaker(ctx, req) + if resp != nil { + return resp + } + + // 判断version是否为master + if req.GetVersion().GetValue() == Master { + return api.NewCircuitBreakerResponse(api.InvalidCircuitBreakerVersion, req) + } + + // 判断规则的master版本是否存在并鉴权 + circuitBreaker, resp := s.checkCircuitBreakerValid(ctx, req, id, Master) + if resp != nil { + if resp.GetCode().GetValue() == api.NotFoundCircuitBreaker { + return api.NewCircuitBreakerResponse(api.NotFoundMasterConfig, req) + } + return resp + } + + // 判断此版本是否存在 + tagCircuitBreaker, err := s.storage.GetCircuitBreaker(id, req.GetVersion().GetValue()) + if err != nil { + log.Error(err.Error(), ZapRequestID(requestID)) + return api.NewCircuitBreakerResponse(api.StoreLayerException, req) + } + if tagCircuitBreaker != nil { + return api.NewCircuitBreakerResponse(api.ExistedResource, req) + } + + // 构造底层数据结构 + newReq := &api.CircuitBreaker{ + Id: utils.NewStringValue(circuitBreaker.ID), + Version: req.GetVersion(), + Name: utils.NewStringValue(circuitBreaker.Name), + Namespace: utils.NewStringValue(circuitBreaker.Namespace), + Inbounds: req.GetInbounds(), + Outbounds: req.GetOutbounds(), + Token: req.GetToken(), + Owners: utils.NewStringValue(circuitBreaker.Owner), + Comment: utils.NewStringValue(circuitBreaker.Comment), + Business: utils.NewStringValue(circuitBreaker.Business), + Department: utils.NewStringValue(circuitBreaker.Department), + } + + data, err := api2CircuitBreaker(newReq, circuitBreaker.ID, circuitBreaker.Token, req.GetVersion().GetValue()) + if err != nil { + log.Error(err.Error(), ZapRequestID(requestID)) + return api.NewCircuitBreakerResponse(api.ParseCircuitBreakerException, req) + } + + // 执行存储层操作 + if err := s.storage.TagCircuitBreaker(data); err != nil { + log.Error(err.Error(), ZapRequestID(requestID)) + return wrapperCircuitBreakerStoreResponse(req, err) + } + + msg := fmt.Sprintf("tag circuit breaker: id=%v, version=%v, name=%v, namespace=%v", + data.ID, data.Version, data.Name, data.Namespace) + log.Info(msg, ZapRequestID(requestID)) + + // todo 记录操作记录 + + return api.NewCircuitBreakerResponse(api.ExecuteSuccess, req) +} + +/** + * @brief 批量删除熔断规则 + */ +func (s *Server) DeleteCircuitBreakers(ctx context.Context, req []*api.CircuitBreaker) *api.BatchWriteResponse { + if checkErr := checkBatchCircuitBreakers(req); checkErr != nil { + return checkErr + } + + resps := api.NewBatchWriteResponse(api.ExecuteSuccess) + for _, circuitBreaker := range req { + resp := s.DeleteCircuitBreaker(ctx, circuitBreaker) + resps.Collect(resp) + } + return api.FormatBatchWriteResponse(resps) +} + +/** + * @brief 删除单个熔断规则 + */ +func (s *Server) DeleteCircuitBreaker(ctx context.Context, req *api.CircuitBreaker) *api.Response { + requestID := ParseRequestID(ctx) + + // 参数校验 + id, resp := checkReviseCircuitBreaker(ctx, req) + if resp != nil { + return resp + } + + // 检查熔断规则是否存在并鉴权 + if _, resp := s.checkCircuitBreakerValid(ctx, req, id, req.GetVersion().GetValue()); resp != nil { + if resp.GetCode().GetValue() == api.NotFoundCircuitBreaker { + return api.NewCircuitBreakerResponse(api.ExecuteSuccess, req) + } + return resp + } + + if req.GetVersion().GetValue() == Master { + return s.deleteMasterCircuitBreaker(requestID, id, req) + } + + return s.deleteTagCircuitBreaker(requestID, id, req) +} + +/** + * @brief 删除master熔断规则 + */ +func (s *Server) deleteMasterCircuitBreaker(requestID string, id string, req *api.CircuitBreaker) *api.Response { + // 检查规则是否有绑定服务 + relations, err := s.storage.GetCircuitBreakerMasterRelation(id) + if err != nil { + log.Error(err.Error(), ZapRequestID(requestID)) + return api.NewCircuitBreakerResponse(api.StoreLayerException, req) + } + if len(relations) > 0 { + log.Errorf("the number of services bound to the circuit breaker(id=%s, version=%s) is %d", + id, req.GetVersion().GetValue(), len(relations)) + return api.NewCircuitBreakerResponse(api.ExistReleasedConfig, req) + } + + // 执行存储层操作 + if err := s.storage.DeleteMasterCircuitBreaker(id); err != nil { + log.Error(err.Error(), ZapRequestID(requestID)) + return wrapperCircuitBreakerStoreResponse(req, err) + } + + msg := fmt.Sprintf("delete master circuit breaker: id=%v", id) + log.Info(msg, ZapRequestID(requestID)) + + // todo 操作记录 + + return api.NewCircuitBreakerResponse(api.ExecuteSuccess, req) +} + +/** + * @brief 删除熔断规则版本 + */ +func (s *Server) deleteTagCircuitBreaker(requestID string, id string, req *api.CircuitBreaker) *api.Response { + // 检查规则是否有绑定服务 + relation, err := s.storage.GetCircuitBreakerRelation(id, req.GetVersion().GetValue()) + if err != nil { + log.Error(err.Error(), ZapRequestID(requestID)) + return api.NewCircuitBreakerResponse(api.StoreLayerException, req) + } + if len(relation) > 0 { + log.Errorf("the number of services bound to the circuit breaker(id=%s, version=%s) is %d", + id, req.GetVersion().GetValue(), len(relation)) + return api.NewCircuitBreakerResponse(api.ExistReleasedConfig, req) + } + + // 执行存储层操作 + if err := s.storage.DeleteTagCircuitBreaker(id, req.GetVersion().GetValue()); err != nil { + log.Error(err.Error(), ZapRequestID(requestID)) + return wrapperCircuitBreakerStoreResponse(req, err) + } + + msg := fmt.Sprintf("delete circuit breaker version: id=%v, version=%v", id, req.GetVersion().GetValue()) + log.Info(msg, ZapRequestID(requestID)) + + // todo 操作记录 + + return api.NewCircuitBreakerResponse(api.ExecuteSuccess, req) +} + +/** + * @brief 批量修改熔断规则 + */ +func (s *Server) UpdateCircuitBreakers(ctx context.Context, req []*api.CircuitBreaker) *api.BatchWriteResponse { + if checkErr := checkBatchCircuitBreakers(req); checkErr != nil { + return checkErr + } + + resps := api.NewBatchWriteResponse(api.ExecuteSuccess) + for _, circuitBreaker := range req { + resp := s.UpdateCircuitBreaker(ctx, circuitBreaker) + resps.Collect(resp) + } + return api.FormatBatchWriteResponse(resps) +} + +/** + * @brief 修改单个熔断规则 + */ +func (s *Server) UpdateCircuitBreaker(ctx context.Context, req *api.CircuitBreaker) *api.Response { + requestID := ParseRequestID(ctx) + + // 基础参数校验 + id, resp := checkReviseCircuitBreaker(ctx, req) + if resp != nil { + return resp + } + // 只允许修改master规则 + if req.GetVersion().GetValue() != Master { + return api.NewCircuitBreakerResponse(api.InvalidCircuitBreakerVersion, req) + } + + // 检查熔断规则是否存在并鉴权 + circuitBreaker, resp := s.checkCircuitBreakerValid(ctx, req, id, req.GetVersion().GetValue()) + if resp != nil { + return resp + } + + // 修改 + err, needUpdate := s.updateCircuitBreakerAttribute(req, circuitBreaker) + if err != nil { + return err + } + // 判断是否需要更新 + if !needUpdate { + log.Info("update circuit breaker data no change, no need update", + ZapRequestID(requestID), zap.String("circuit breaker", req.String())) + return api.NewCircuitBreakerResponse(api.NoNeedUpdate, req) + } + + // 执行存储层操作 + if err := s.storage.UpdateCircuitBreaker(circuitBreaker); err != nil { + log.Error(err.Error(), ZapRequestID(requestID)) + return wrapperCircuitBreakerStoreResponse(req, err) + } + + msg := fmt.Sprintf("update circuit breaker: id=%v, version=%v, name=%v, namespace=%v", + circuitBreaker.ID, circuitBreaker.Version, circuitBreaker.Name, circuitBreaker.Namespace) + log.Info(msg, ZapRequestID(requestID)) + + // todo 记录操作记录 + + return api.NewCircuitBreakerResponse(api.ExecuteSuccess, req) +} + +/** + * @brief 修改规则属性 + */ +func (s *Server) updateCircuitBreakerAttribute(req *api.CircuitBreaker, circuitBreaker *model.CircuitBreaker) ( + *api.Response, bool) { + needUpdate := false + if req.GetOwners() != nil { + if req.GetOwners().GetValue() == "" { + return api.NewCircuitBreakerResponse(api.InvalidCircuitBreakerOwners, req), needUpdate + } + if req.GetOwners().GetValue() != circuitBreaker.Owner { + circuitBreaker.Owner = req.GetOwners().GetValue() + needUpdate = true + } + } + + if req.GetBusiness() != nil && req.GetBusiness().GetValue() != circuitBreaker.Business { + circuitBreaker.Business = req.GetBusiness().GetValue() + needUpdate = true + } + + if req.GetDepartment() != nil && req.GetDepartment().GetValue() != circuitBreaker.Department { + circuitBreaker.Department = req.GetDepartment().GetValue() + needUpdate = true + } + + if req.GetComment() != nil && req.GetComment().GetValue() != circuitBreaker.Comment { + circuitBreaker.Comment = req.GetComment().GetValue() + needUpdate = true + } + + inbounds, outbounds, err := marshalCircuitBreakerRule(req.GetInbounds(), req.GetOutbounds()) + if err != nil { + return api.NewCircuitBreakerResponse(api.ParseCircuitBreakerException, req), needUpdate + } + + if req.GetInbounds() != nil && inbounds != circuitBreaker.Inbounds { + circuitBreaker.Inbounds = inbounds + needUpdate = true + } + + if req.GetOutbounds() != nil && outbounds != circuitBreaker.Outbounds { + circuitBreaker.Outbounds = outbounds + needUpdate = true + } + + if needUpdate { + circuitBreaker.Revision = NewUUID() + } + + return nil, needUpdate +} + +/** + * @brief 批量发布熔断规则 + */ +func (s *Server) ReleaseCircuitBreakers(ctx context.Context, req []*api.ConfigRelease) *api.BatchWriteResponse { + if checkErr := checkBatchConfigRelease(req); checkErr != nil { + return checkErr + } + + resps := api.NewBatchWriteResponse(api.ExecuteSuccess) + for _, configRelease := range req { + resp := s.ReleaseCircuitBreaker(ctx, configRelease) + resps.Collect(resp) + } + return api.FormatBatchWriteResponse(resps) +} + +/** + * @brief 发布单个熔断规则 + */ +func (s *Server) ReleaseCircuitBreaker(ctx context.Context, req *api.ConfigRelease) *api.Response { + requestID := ParseRequestID(ctx) + + // 参数校验 + ruleID, resp := checkReleaseCircuitBreaker(req) + if resp != nil { + return resp + } + + //检查规则所属命名空间和服务所属命名空间是否一致 + if req.GetService().GetNamespace().GetValue() != req.GetCircuitBreaker().GetNamespace().GetValue() { + return api.NewConfigResponse(api.NotAllowDifferentNamespaceBindRule, req) + } + + // 检查服务是否可用并鉴权 + service, resp := s.checkService(ctx, req) + if resp != nil { + return resp + } + + // 检查此版本规则是否存在 + ruleVersion := req.GetCircuitBreaker().GetVersion().GetValue() + tagCircuitBreaker, err := s.storage.GetCircuitBreaker(ruleID, ruleVersion) + if err != nil { + log.Error(err.Error(), ZapRequestID(requestID)) + return api.NewConfigResponse(api.StoreLayerException, req) + } + if tagCircuitBreaker == nil { + return api.NewConfigResponse(api.NotFoundTagConfig, req) + } + + // 检查服务绑定的熔断规则是否存在以及是否为此规则 + serviceName := req.GetService().GetName().GetValue() + namespaceName := req.GetService().GetNamespace().GetValue() + rule, err := s.storage.GetCircuitBreakersByService(serviceName, namespaceName) + if err != nil { + log.Error(err.Error(), ZapRequestID(requestID)) + return api.NewConfigResponse(api.StoreLayerException, req) + } + if rule != nil && rule.ID == ruleID && rule.Version == ruleVersion { + return api.NewConfigResponse(api.ExistedResource, req) + } + + // 构造底层数据结构 + data := api2CircuitBreakerRelation(service.ID, ruleID, ruleVersion) + + // 执行存储层操作 + if err := s.storage.ReleaseCircuitBreaker(data); err != nil { + log.Error(err.Error(), zap.String("request-id", requestID)) + return wrapperConfigStoreResponse(req, err) + } + + msg := fmt.Sprintf("release circuit breaker: ruleID=%s, ruleVersion=%s, namespace=%s, service=%s", + ruleID, ruleVersion, service.Namespace, service.Name) + log.Info(msg, zap.String("request-id", requestID)) + + // todo 操作记录 + + return api.NewConfigResponse(api.ExecuteSuccess, req) +} + +/** + * @brief 批量解绑熔断规则 + */ +func (s *Server) UnBindCircuitBreakers(ctx context.Context, req []*api.ConfigRelease) *api.BatchWriteResponse { + if checkErr := checkBatchConfigRelease(req); checkErr != nil { + return checkErr + } + resps := api.NewBatchWriteResponse(api.ExecuteSuccess) + for _, configRelease := range req { + resp := s.UnBindCircuitBreaker(ctx, configRelease) + resps.Collect(resp) + } + return api.FormatBatchWriteResponse(resps) +} + +/** + * @brief 解绑单个熔断规则 + */ +func (s *Server) UnBindCircuitBreaker(ctx context.Context, req *api.ConfigRelease) *api.Response { + requestID := ParseRequestID(ctx) + + // 参数校验 + ruleID, resp := checkReleaseCircuitBreaker(req) + if resp != nil { + return resp + } + + // 检查服务是否可用并鉴权 + service, resp := s.checkService(ctx, req) + if resp != nil { + return resp + } + + // 检查此版本的规则是否存在 + ruleVersion := req.GetCircuitBreaker().GetVersion().GetValue() + tagCircuitBreaker, err := s.storage.GetCircuitBreaker(ruleID, ruleVersion) + if err != nil { + log.Error(err.Error(), ZapRequestID(requestID)) + return api.NewConfigResponse(api.StoreLayerException, req) + } + if tagCircuitBreaker == nil { + return api.NewConfigResponse(api.NotFoundTagConfig, req) + } + + // 检查服务绑定的熔断规则是否存在以及是否为此规则 + serviceName := req.GetService().GetName().GetValue() + namespaceName := req.GetService().GetNamespace().GetValue() + rule, err := s.storage.GetCircuitBreakersByService(serviceName, namespaceName) + if err != nil { + log.Error(err.Error(), ZapRequestID(requestID)) + return api.NewConfigResponse(api.StoreLayerException, req) + } + if rule == nil || rule.ID != ruleID || rule.Version != ruleVersion { + return api.NewConfigResponse(api.ExecuteSuccess, req) + } + + // 执行存储层操作 + if err := s.storage.UnbindCircuitBreaker(service.ID, ruleID, ruleVersion); err != nil { + log.Error(err.Error(), zap.String("request-id", requestID)) + return wrapperConfigStoreResponse(req, err) + } + + msg := fmt.Sprintf("unbind circuit breaker: ruleID=%s, ruleVersion=%s, namespace=%s, service=%s", + ruleID, ruleVersion, service.Namespace, service.Name) + log.Info(msg, zap.String("request-id", requestID)) + + // todo 操作记录 + + return api.NewConfigResponse(api.ExecuteSuccess, req) +} + +/** + * @brief 根据id和version查询熔断规则 + */ +func (s *Server) GetCircuitBreaker(query map[string]string) *api.BatchQueryResponse { + // 必填参数:id和version + if _, ok := query[ID]; !ok { + log.Errorf("params %s is not in querying circuit breaker", ID) + return api.NewBatchQueryResponse(api.InvalidParameter) + } + if _, ok := query[Version]; !ok { + log.Errorf("params %s is not in querying circuit breaker", Version) + return api.NewBatchQueryResponse(api.InvalidParameter) + } + + circuitBreaker, err := s.storage.GetCircuitBreaker(query[ID], query[Version]) + if err != nil { + log.Errorf("get circuit breaker err: %s", err.Error()) + return api.NewBatchQueryResponse(api.StoreLayerException) + } + + breaker, err := circuitBreaker2API(circuitBreaker) + if err != nil { + log.Errorf("get circuit breaker err: %s", err.Error()) + return api.NewBatchQueryResponse(api.ParseCircuitBreakerException) + } + + resp := api.NewBatchQueryResponse(api.ExecuteSuccess) + + if breaker == nil { + resp.Amount = utils.NewUInt32Value(0) + resp.Size = utils.NewUInt32Value(0) + resp.ConfigWithServices = []*api.ConfigWithService{} + return resp + } + + configWithService := &api.ConfigWithService{ + CircuitBreaker: breaker, + } + + resp.Amount = utils.NewUInt32Value(1) + resp.Size = utils.NewUInt32Value(1) + resp.ConfigWithServices = make([]*api.ConfigWithService, 0, 1) + resp.ConfigWithServices = append(resp.ConfigWithServices, configWithService) + return resp +} + +/** + * @brief 根据id查询熔断规则所有版本 + */ +func (s *Server) GetCircuitBreakerVersions(query map[string]string) *api.BatchQueryResponse { + // 必填参数:id + if _, ok := query[ID]; !ok { + log.Errorf("params %s is not in querying circuit breaker", ID) + return api.NewBatchQueryResponse(api.InvalidParameter) + } + + versions, err := s.storage.GetCircuitBreakerVersions(query[ID]) + if err != nil { + log.Errorf("get circuit breaker versions err: %s", err.Error()) + return api.NewBatchQueryResponse(api.StoreLayerException) + } + + nums := len(versions) + + resp := api.NewBatchQueryResponse(api.ExecuteSuccess) + resp.Amount = utils.NewUInt32Value(uint32(nums)) + resp.Size = utils.NewUInt32Value(uint32(nums)) + resp.ConfigWithServices = make([]*api.ConfigWithService, 0, nums) + for _, version := range versions { + config := ruleIDAndVersion2API(query[ID], version) + resp.ConfigWithServices = append(resp.ConfigWithServices, config) + } + return resp +} + +/** + * @brief 查询master熔断规则 + */ +func (s *Server) GetMasterCircuitBreakers(query map[string]string) *api.BatchQueryResponse { + for key := range query { + if _, ok := MasterCircuitBreakers[key]; !ok { + log.Errorf("params %s is not allowed in querying master circuit breakers", key) + return api.NewBatchQueryResponse(api.InvalidParameter) + } + } + + // 处理offset和limit + offset, limit, err := ParseOffsetAndLimit(query) + if err != nil { + return api.NewBatchQueryResponse(api.InvalidParameter) + } + + c, err := s.storage.ListMasterCircuitBreakers(query, offset, limit) + if err != nil { + log.Errorf("get master circuit breakers err: %s", err.Error()) + return api.NewBatchQueryResponse(api.StoreLayerException) + } + + return genCircuitBreakersResult(c) +} + +/** + * @brief 根据规则id查询已发布规则 + */ +func (s *Server) GetReleaseCircuitBreakers(query map[string]string) *api.BatchQueryResponse { + // 必须参数:id + if _, ok := query[ID]; !ok { + log.Errorf("params %s is not in querying release circuit breakers", ID) + return api.NewBatchQueryResponse(api.InvalidParameter) + } + + for key := range query { + if _, ok := ReleaseCircuitBreakers[key]; !ok { + log.Errorf("params %s is not allowed in querying release circuit breakers", key) + return api.NewBatchQueryResponse(api.InvalidParameter) + } + } + + //id转化为rule_id + if ruleID, ok := query[ID]; ok { + query["rule_id"] = ruleID + delete(query, ID) + } + + if ruleVersion, ok := query[Version]; ok { + query["rule_version"] = ruleVersion + delete(query, Version) + } + + // 处理offset和limit + offset, limit, err := ParseOffsetAndLimit(query) + if err != nil { + return api.NewBatchQueryResponse(api.InvalidParameter) + } + + c, err := s.storage.ListReleaseCircuitBreakers(query, offset, limit) + if err != nil { + log.Errorf("get release circuit breakers err: %s", err.Error()) + return api.NewBatchQueryResponse(api.StoreLayerException) + } + + return genCircuitBreakersResult(c) +} + +/** + * @brief 生成返回查询熔断规则的数据 + */ +func genCircuitBreakersResult(c *model.CircuitBreakerDetail) *api.BatchQueryResponse { + resp := api.NewBatchQueryResponse(api.ExecuteSuccess) + resp.Amount = utils.NewUInt32Value(c.Total) + resp.Size = utils.NewUInt32Value(uint32(len(c.CircuitBreakerInfos))) + resp.ConfigWithServices = make([]*api.ConfigWithService, 0, len(c.CircuitBreakerInfos)) + for _, item := range c.CircuitBreakerInfos { + info, err := circuitBreaker2ConsoleAPI(item) + if err != nil { + log.Errorf("get circuit breakers err: %s", err.Error()) + return api.NewBatchQueryResponse(api.ParseCircuitBreakerException) + } + resp.ConfigWithServices = append(resp.ConfigWithServices, info) + } + return resp +} + +/** + * @brief 根据服务查询绑定熔断规则 + */ +func (s *Server) GetCircuitBreakerByService(query map[string]string) *api.BatchQueryResponse { + // 必须参数:service和namespace + for key := range ServiceParams { + if _, ok := query[key]; !ok { + log.Errorf("params %s is not in querying circuit breakers by service", key) + return api.NewBatchQueryResponse(api.InvalidParameter) + } + } + + circuitBreaker, err := s.storage.GetCircuitBreakersByService(query[Service], query[Namespace]) + if err != nil { + log.Errorf("get circuit breaker by service err: %s", err.Error()) + return api.NewBatchQueryResponse(api.StoreLayerException) + } + + breaker, err := circuitBreaker2API(circuitBreaker) + if err != nil { + log.Errorf("get circuit breaker by service err: %s", err.Error()) + return api.NewBatchQueryResponse(api.ParseCircuitBreakerException) + } + + resp := api.NewBatchQueryResponse(api.ExecuteSuccess) + + if breaker == nil { + resp.Amount = utils.NewUInt32Value(0) + resp.Size = utils.NewUInt32Value(0) + resp.ConfigWithServices = []*api.ConfigWithService{} + return resp + } + + configWithService := &api.ConfigWithService{ + CircuitBreaker: breaker, + } + + resp.Amount = utils.NewUInt32Value(1) + resp.Size = utils.NewUInt32Value(1) + resp.ConfigWithServices = make([]*api.ConfigWithService, 0, 1) + resp.ConfigWithServices = append(resp.ConfigWithServices, configWithService) + + return resp +} + +/** +* @brief 查询熔断规则的token + */ +func (s *Server) GetCircuitBreakerToken(ctx context.Context, req *api.CircuitBreaker) *api.Response { + id, resp := checkReviseCircuitBreaker(ctx, req) + if resp != nil { + return resp + } + + circuitBreaker, resp := s.checkCircuitBreakerValid(ctx, req, id, Master) + if resp != nil { + return resp + } + + out := api.NewResponse(api.ExecuteSuccess) + out.CircuitBreaker = &api.CircuitBreaker{ + Id: utils.NewStringValue(id), + Name: utils.NewStringValue(circuitBreaker.Name), + Namespace: utils.NewStringValue(circuitBreaker.Namespace), + Token: utils.NewStringValue(circuitBreaker.Token), + } + return out +} + +/* + * @brief 检查熔断规则批量请求 + */ +func checkBatchCircuitBreakers(req []*api.CircuitBreaker) *api.BatchWriteResponse { + if len(req) == 0 { + return api.NewBatchWriteResponse(api.EmptyRequest) + } + + if len(req) > MaxBatchSize { + return api.NewBatchWriteResponse(api.BatchSizeOverLimit) + } + + return nil +} + +/** + * @brief 检查规则发布批量请求 + */ +func checkBatchConfigRelease(req []*api.ConfigRelease) *api.BatchWriteResponse { + if len(req) == 0 { + return api.NewBatchWriteResponse(api.EmptyRequest) + } + + if len(req) > MaxBatchSize { + return api.NewBatchWriteResponse(api.BatchSizeOverLimit) + } + + return nil +} + +/** + * @brief 检查创建熔断规则参数 + */ +func checkCreateCircuitBreaker(req *api.CircuitBreaker) (string, *api.Response) { + if req == nil { + return "", api.NewCircuitBreakerResponse(api.EmptyRequest, req) + } + // 检查负责人 + if err := checkResourceOwners(req.GetOwners()); err != nil { + return "", api.NewCircuitBreakerResponse(api.InvalidCircuitBreakerOwners, req) + } + // 检查字段长度是否大于DB中对应字段长 + err, notOk := CheckDbCircuitbreakerFieldLen(req) + if notOk { + return "", err + } + return checkRuleTwoTuple(req) +} + +/** + * @brief 检查修改/删除/创建版本参数 + */ +func checkReviseCircuitBreaker(ctx context.Context, req *api.CircuitBreaker) (string, *api.Response) { + if req == nil { + return "", api.NewCircuitBreakerResponse(api.EmptyRequest, req) + } + // 检查规则version + if err := checkResourceName(req.GetVersion()); err != nil { + return "", api.NewCircuitBreakerResponse(api.InvalidCircuitBreakerVersion, req) + } + // 检查规则token + if token := parseCircuitBreakerToken(ctx, req); token == "" { + return "", api.NewCircuitBreakerResponse(api.InvalidCircuitBreakerToken, req) + } + // 检查规则id + if req.GetId() != nil { + if req.GetId().GetValue() == "" { + return "", api.NewCircuitBreakerResponse(api.InvalidCircuitBreakerID, req) + } + return req.GetId().GetValue(), nil + } + // 检查字段长度是否大于DB中对应字段长 + err, notOk := CheckDbCircuitbreakerFieldLen(req) + if notOk { + return "", err + } + return checkRuleTwoTuple(req) +} + +/** + * @brief 检查发布、解绑熔断规则参数 + */ +func checkReleaseCircuitBreaker(req *api.ConfigRelease) (string, *api.Response) { + if req == nil { + return "", api.NewConfigResponse(api.EmptyRequest, req) + } + // 检查命名空间 + if err := checkResourceName(req.GetService().GetNamespace()); err != nil { + return "", api.NewConfigResponse(api.InvalidNamespaceName, req) + } + if err := CheckDbStrFieldLen(req.GetService().GetNamespace(), MaxDbServiceNamespaceLength); err != nil { + return "", api.NewConfigResponse(api.InvalidNamespaceName, req) + } + // 检查服务名 + if err := checkResourceName(req.GetService().GetName()); err != nil { + return "", api.NewConfigResponse(api.InvalidServiceName, req) + } + if err := CheckDbStrFieldLen(req.GetService().GetName(), MaxDbServiceNameLength); err != nil { + return "", api.NewConfigResponse(api.InvalidServiceName, req) + } + if err := CheckDbStrFieldLen(req.GetService().GetToken(), MaxDbServiceToken); err != nil { + return "", api.NewConfigResponse(api.InvalidServiceToken, req) + } + // 检查规则version + if err := checkResourceName(req.GetCircuitBreaker().GetVersion()); err != nil { + return "", api.NewConfigResponse(api.InvalidCircuitBreakerVersion, req) + } + if err := CheckDbStrFieldLen(req.GetCircuitBreaker().GetVersion(), MaxDbCircuitbreakerVersion); err != nil { + return "", api.NewConfigResponse(api.InvalidCircuitBreakerVersion, req) + } + // 判断version是否为master + if req.GetCircuitBreaker().GetVersion().GetValue() == Master { + return "", api.NewConfigResponse(api.InvalidCircuitBreakerVersion, req) + } + // 规则name和规则namespace必填 + return checkRuleTwoTuple(req.GetCircuitBreaker()) +} + +/** + * @brief 根据规则name和规则namespace计算ID + */ +func checkRuleTwoTuple(req *api.CircuitBreaker) (string, *api.Response) { + // 检查规则name + if err := checkResourceName(req.GetName()); err != nil { + return "", api.NewCircuitBreakerResponse(api.InvalidCircuitBreakerName, req) + } + if err := CheckDbStrFieldLen(req.GetName(), MaxDbCircuitbreakerName); err != nil { + return "", api.NewCircuitBreakerResponse(api.InvalidCircuitBreakerName, req) + } + // 检查规则namespace + if err := checkResourceName(req.GetNamespace()); err != nil { + return "", api.NewCircuitBreakerResponse(api.InvalidCircuitBreakerNamespace, req) + } + if err := CheckDbStrFieldLen(req.GetNamespace(), MaxDbCircuitbreakerNamespace); err != nil { + return "", api.NewCircuitBreakerResponse(api.InvalidCircuitBreakerNamespace, req) + } + return CalculateRuleID(req.GetName().GetValue(), req.GetNamespace().GetValue()), nil +} + +/** + * @brief 修改/删除/发布熔断规则的公共检查 + */ +func (s *Server) checkCircuitBreakerValid(ctx context.Context, req *api.CircuitBreaker, id, version string) ( + *model.CircuitBreaker, *api.Response) { + requestID := ParseRequestID(ctx) + + // 检查熔断规则是否存在 + circuitBreaker, err := s.storage.GetCircuitBreaker(id, version) + if err != nil { + log.Error(err.Error(), ZapRequestID(requestID)) + return nil, api.NewCircuitBreakerResponse(api.StoreLayerException, req) + } + if circuitBreaker == nil { + return nil, api.NewCircuitBreakerResponse(api.NotFoundCircuitBreaker, req) + } + + // 鉴权 + if ok := s.authority.VerifyRule(circuitBreaker.Token, parseCircuitBreakerToken(ctx, req)); !ok { + return nil, api.NewCircuitBreakerResponse(api.Unauthorized, req) + } + return circuitBreaker, nil +} + +/** + * @brief 判断服务是否可用并鉴权 + */ +func (s *Server) checkService(ctx context.Context, req *api.ConfigRelease) (*model.Service, *api.Response) { + requestID := ParseRequestID(ctx) + serviceName := req.GetService().GetName().GetValue() + namespaceName := req.GetService().GetNamespace().GetValue() + + service, err := s.storage.GetService(serviceName, namespaceName) + if err != nil { + log.Error(err.Error(), zap.String("request-id", requestID)) + return nil, api.NewConfigResponse(api.StoreLayerException, req) + } + if service == nil { + return nil, api.NewConfigResponse(api.NotFoundService, req) + } + if service.IsAlias() { + return nil, api.NewConfigResponse(api.NotAllowAliasBindRule, req) + } + + // 使用平台id以及token鉴权 + if ok := s.verifyAuthByPlatform(ctx, service.PlatformID); !ok { + // 检查token是否存在 + serviceToken := parseRequestToken(ctx, req.GetService().GetToken().GetValue()) + if !s.authority.VerifyToken(serviceToken) { + return nil, api.NewConfigResponse(api.InvalidServiceToken, req) + } + + // 检查token是否ok + if ok := s.authority.VerifyService(service.Token, serviceToken); !ok { + return nil, api.NewConfigResponse(api.Unauthorized, req) + } + } + + return service, nil +} + +/** + * @brief 获取熔断规则的token信息 + */ +func parseCircuitBreakerToken(ctx context.Context, req *api.CircuitBreaker) string { + if token := req.GetToken().GetValue(); token != "" { + return token + } + + return ParseToken(ctx) +} + +/** + * @brief 创建存储层熔断规则模型 + */ +func api2CircuitBreaker(req *api.CircuitBreaker, id, token, version string) (*model.CircuitBreaker, error) { + inbounds, outbounds, err := marshalCircuitBreakerRule(req.GetInbounds(), req.GetOutbounds()) + if err != nil { + return nil, err + } + + circuitBreaker := &model.CircuitBreaker{ + ID: id, + Version: version, + Name: req.GetName().GetValue(), + Namespace: req.GetNamespace().GetValue(), + Business: req.GetBusiness().GetValue(), + Department: req.GetDepartment().GetValue(), + Comment: req.GetComment().GetValue(), + Inbounds: inbounds, + Outbounds: outbounds, + Token: token, + Owner: req.GetOwners().GetValue(), + Revision: NewUUID(), + } + + return circuitBreaker, nil +} + +/** + * @brief 创建存储层熔断规则关系模型 + */ +func api2CircuitBreakerRelation(serviceID, ruleID, ruleVersion string) *model.CircuitBreakerRelation { + circuitBreakerRelation := &model.CircuitBreakerRelation{ + ServiceID: serviceID, + RuleID: ruleID, + RuleVersion: ruleVersion, + } + + return circuitBreakerRelation +} + +/** + * @brief 返回规则id和version + */ +func ruleIDAndVersion2API(id, version string) *api.ConfigWithService { + out := &api.ConfigWithService{} + + rule := &api.CircuitBreaker{ + Id: utils.NewStringValue(id), + Version: utils.NewStringValue(version), + } + + out.CircuitBreaker = rule + return out +} + +/** + * @brief 把内部数据结构转化为熔断规则API参数 + */ +func circuitBreaker2API(req *model.CircuitBreaker) (*api.CircuitBreaker, error) { + if req == nil { + return nil, nil + } + + // token不返回 + out := &api.CircuitBreaker{ + Id: utils.NewStringValue(req.ID), + Version: utils.NewStringValue(req.Version), + Name: utils.NewStringValue(req.Name), + Namespace: utils.NewStringValue(req.Namespace), + Owners: utils.NewStringValue(req.Owner), + Comment: utils.NewStringValue(req.Comment), + Ctime: utils.NewStringValue(time2String(req.CreateTime)), + Mtime: utils.NewStringValue(time2String(req.ModifyTime)), + Revision: utils.NewStringValue(req.Revision), + Business: utils.NewStringValue(req.Business), + Department: utils.NewStringValue(req.Department), + } + + if req.Inbounds != "" { + var inBounds []*api.CbRule + if err := json.Unmarshal([]byte(req.Inbounds), &inBounds); err != nil { + return nil, err + } + out.Inbounds = inBounds + } + if req.Outbounds != "" { + var outBounds []*api.CbRule + if err := json.Unmarshal([]byte(req.Outbounds), &outBounds); err != nil { + return nil, err + } + out.Outbounds = outBounds + } + return out, nil +} + +/** + * @brief 把内部数据结构转化为客户端API参数 + */ +func circuitBreaker2ClientAPI(req *model.ServiceWithCircuitBreaker, service string, namespace string) ( + *api.CircuitBreaker, error) { + if req == nil { + return nil, nil + } + + out, err := circuitBreaker2API(req.CircuitBreaker) + if err != nil { + return nil, err + } + + if out == nil { + return nil, nil + } + + out.Service = utils.NewStringValue(service) + out.ServiceNamespace = utils.NewStringValue(namespace) + + return out, nil +} + +/** + * @brief 把内部数据结构转化为控制台API参数 + */ +func circuitBreaker2ConsoleAPI(req *model.CircuitBreakerInfo) (*api.ConfigWithService, error) { + if req == nil { + return nil, nil + } + + out := &api.ConfigWithService{} + circuitBreaker, err := circuitBreaker2API(req.CircuitBreaker) + if err != nil { + return nil, err + } + out.CircuitBreaker = circuitBreaker + + if len(req.Services) == 0 { + return out, nil + } + + services := make([]*api.Service, 0, len(req.Services)) + for _, item := range req.Services { + service := serviceRelatedRules2API(item) + services = append(services, service) + } + + out.Services = services + return out, nil +} + +/** + * @brief 转化服务名和命名空间 + */ +func serviceRelatedRules2API(service *model.Service) *api.Service { + if service == nil { + return nil + } + + out := &api.Service{ + Name: utils.NewStringValue(service.Name), + Namespace: utils.NewStringValue(service.Namespace), + Owners: utils.NewStringValue(service.Owner), + Ctime: utils.NewStringValue(time2String(service.CreateTime)), + Mtime: utils.NewStringValue(time2String(service.ModifyTime)), + } + + return out +} + +/** + * @brief 序列化inbounds和outbounds + */ +func marshalCircuitBreakerRule(in []*api.CbRule, out []*api.CbRule) (string, string, error) { + inbounds, err := json.Marshal(in) + if err != nil { + return "", "", err + } + + outbounds, err := json.Marshal(out) + if err != nil { + return "", "", err + } + + return string(inbounds), string(outbounds), nil +} + +/** + * @brief 封装熔断规则存储层错误 + */ +func wrapperCircuitBreakerStoreResponse(circuitBreaker *api.CircuitBreaker, err error) *api.Response { + resp := storeError2Response(err) + if resp == nil { + return nil + } + resp.CircuitBreaker = circuitBreaker + return resp +} + +/** + * @brief 封装熔断规则发布存储层错误 + */ +func wrapperConfigStoreResponse(configRelease *api.ConfigRelease, err error) *api.Response { + resp := storeError2Response(err) + if resp == nil { + return nil + } + resp.ConfigRelease = configRelease + return resp +} + +// 检查DB中circuitbreaker表对应的入参字段合法性 +func CheckDbCircuitbreakerFieldLen(req *api.CircuitBreaker) (*api.Response, bool) { + if err := CheckDbStrFieldLen(req.GetName(), MaxDbCircuitbreakerName); err != nil { + return api.NewCircuitBreakerResponse(api.InvalidCircuitBreakerName, req), true + } + if err := CheckDbStrFieldLen(req.GetNamespace(), MaxDbCircuitbreakerNamespace); err != nil { + return api.NewCircuitBreakerResponse(api.InvalidCircuitBreakerNamespace, req), true + } + if err := CheckDbStrFieldLen(req.GetBusiness(), MaxDbCircuitbreakerBusiness); err != nil { + return api.NewCircuitBreakerResponse(api.InvalidCircuitBreakerBusiness, req), true + } + if err := CheckDbStrFieldLen(req.GetDepartment(), MaxDbCircuitbreakerDepartment); err != nil { + return api.NewCircuitBreakerResponse(api.InvalidCircuitBreakerDepartment, req), true + } + if err := CheckDbStrFieldLen(req.GetComment(), MaxDbCircuitbreakerComment); err != nil { + return api.NewCircuitBreakerResponse(api.InvalidCircuitBreakerComment, req), true + } + if err := CheckDbStrFieldLen(req.GetOwners(), MaxDbCircuitbreakerOwner); err != nil { + return api.NewCircuitBreakerResponse(api.InvalidCircuitBreakerOwners, req), true + } + + return nil, false +} diff --git a/naming/circuitbreaker_config_test.go b/naming/circuitbreaker_config_test.go new file mode 100644 index 000000000..fbb05b1cc --- /dev/null +++ b/naming/circuitbreaker_config_test.go @@ -0,0 +1,144 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package naming + +import ( + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/gogo/protobuf/jsonpb" + "github.com/golang/protobuf/ptypes/duration" + "github.com/golang/protobuf/ptypes/wrappers" + "testing" +) + +func TestServer_CreateCircuitBreakerJson(t *testing.T) { + rule := &api.CircuitBreaker{} + rule.Id = &wrappers.StringValue{Value: "12345678"} + rule.Version = &wrappers.StringValue{Value: "1.0.0"} + rule.Name = &wrappers.StringValue{Value: "testCbRule"} + rule.Namespace = &wrappers.StringValue{Value: "Test"} + rule.Service = &wrappers.StringValue{Value: "TestService1"} + rule.ServiceNamespace = &wrappers.StringValue{Value: "Test"} + rule.Inbounds = []*api.CbRule{ + { + Sources: []*api.SourceMatcher{ + { + Service: &wrappers.StringValue{Value: "*"}, + Namespace: &wrappers.StringValue{Value: "*"}, + Labels: map[string]*api.MatchString{ + "user": &api.MatchString{ + Type: 0, + Value: &wrappers.StringValue{Value: "vip"}, + }, + }, + }, + }, + Destinations: []*api.DestinationSet{ + { + Method: &api.MatchString{ + Type: 0, + Value: &wrappers.StringValue{Value: "/info"}, + }, + Resource: api.DestinationSet_INSTANCE, + Type: api.DestinationSet_LOCAL, + Scope: api.DestinationSet_CURRENT, + Policy: &api.CbPolicy{ + ErrorRate: &api.CbPolicy_ErrRateConfig{ + Enable: &wrappers.BoolValue{Value: true}, + RequestVolumeThreshold: &wrappers.UInt32Value{Value: 10}, + ErrorRateToOpen: &wrappers.UInt32Value{Value: 50}, + }, + Consecutive: &api.CbPolicy_ConsecutiveErrConfig{ + Enable: &wrappers.BoolValue{Value: true}, + ConsecutiveErrorToOpen: &wrappers.UInt32Value{Value: 10}, + }, + SlowRate: &api.CbPolicy_SlowRateConfig{ + Enable: &wrappers.BoolValue{Value: true}, + MaxRt: &duration.Duration{Seconds: 1}, + SlowRateToOpen: &wrappers.UInt32Value{Value: 80}, + }, + }, + Recover: &api.RecoverConfig{ + SleepWindow: &duration.Duration{ + Seconds: 1, + }, + OutlierDetectWhen: api.RecoverConfig_ON_RECOVER, + }, + }, + }, + }, + } + rule.Outbounds = []*api.CbRule{ + { + Sources: []*api.SourceMatcher{ + { + Labels: map[string]*api.MatchString{ + "callerName": &api.MatchString{ + Type: 0, + Value: &wrappers.StringValue{Value: "xyz"}, + }, + }, + }, + }, + Destinations: []*api.DestinationSet{ + { + Namespace: &wrappers.StringValue{Value: "Test"}, + Service: &wrappers.StringValue{Value: "TestService1"}, + Method: &api.MatchString{ + Type: 0, + Value: &wrappers.StringValue{Value: "/info"}, + }, + Resource: api.DestinationSet_INSTANCE, + Type: api.DestinationSet_LOCAL, + Scope: api.DestinationSet_CURRENT, + Policy: &api.CbPolicy{ + ErrorRate: &api.CbPolicy_ErrRateConfig{ + Enable: &wrappers.BoolValue{Value: true}, + RequestVolumeThreshold: &wrappers.UInt32Value{Value: 10}, + ErrorRateToOpen: &wrappers.UInt32Value{Value: 50}, + }, + Consecutive: &api.CbPolicy_ConsecutiveErrConfig{ + Enable: &wrappers.BoolValue{Value: true}, + ConsecutiveErrorToOpen: &wrappers.UInt32Value{Value: 10}, + }, + SlowRate: &api.CbPolicy_SlowRateConfig{ + Enable: &wrappers.BoolValue{Value: true}, + MaxRt: &duration.Duration{Seconds: 1}, + SlowRateToOpen: &wrappers.UInt32Value{Value: 80}, + }, + }, + Recover: &api.RecoverConfig{ + SleepWindow: &duration.Duration{ + Seconds: 1, + }, + OutlierDetectWhen: api.RecoverConfig_ON_RECOVER, + }, + }, + }, + }, + } + rule.Business = &wrappers.StringValue{Value: "polaris"} + rule.Owners = &wrappers.StringValue{Value: "polaris"} + + marshaler := &jsonpb.Marshaler{} + ruleStr, err := marshaler.MarshalToString(rule) + if nil != err { + t.Fatal(err) + } + fmt.Printf(ruleStr) +} \ No newline at end of file diff --git a/naming/client.go b/naming/client.go new file mode 100644 index 000000000..0aae3264d --- /dev/null +++ b/naming/client.go @@ -0,0 +1,417 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package naming + +import ( + "context" + + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/common/utils" + "go.uber.org/zap" +) + +/** + * @brief 客户端上报信息 + */ +func (s *Server) ReportClient(ctx context.Context, req *api.Client) *api.Response { + requestID, _ := ctx.Value(utils.StringContext("request-id")).(string) + if s.caches == nil { + return api.NewResponse(api.ClientAPINotOpen) + } + + // 客户端信息不写入到DB中 + host := req.GetHost().GetValue() + out := &api.Client{ + Host: req.GetHost(), + } + + // 从CMDB查询地理位置信息 + if s.cmdb != nil { + location, err := s.cmdb.GetLocation(host) + if err != nil { + log.Error(err.Error(), zap.String("request-id", requestID)) + return api.NewClientResponse(api.CMDBPluginException, req) + } + + if location == nil { + return api.NewClientResponse(api.CMDBNotFindHost, req) + } + out.Location = location.Proto + } + + return api.NewClientResponse(api.ExecuteSuccess, out) +} + +/** + * @brief 服务实例上报心跳 + */ +func (s *Server) Heartbeat(ctx context.Context, req *api.Instance) *api.Response { + if s.caches == nil { + return api.NewResponse(api.ClientAPINotOpen) + } + + if s.hbMgr == nil { + return api.NewInstanceResponse(api.HealthCheckNotOpen, req) + } + return s.hbMgr.healthCheck(ctx, req) +} + +/** + * @brief 根据元数据查询服务 + */ +func (s *Server) GetServiceWithCache(ctx context.Context, req *api.Service) *api.DiscoverResponse { + if s.caches == nil { + return api.NewDiscoverServiceResponse(api.ClientAPINotOpen, req) + } + + if req == nil { + return api.NewDiscoverServiceResponse(api.EmptyRequest, req) + } + //可根据business查询服务 + if len(req.GetMetadata()) == 0 && len(req.Business.GetValue()) == 0 { + return api.NewDiscoverServiceResponse(api.InvalidServiceMetadata, req) + } + + requestID := ParseRequestID(ctx) + + resp := api.NewDiscoverServiceResponse(api.ExecuteSuccess, req) + + resp.Services = []*api.Service{} + + serviceIterProc := func(key string, value *model.Service) (bool, error) { + if checkServiceMetadata(req.GetMetadata(), value, req.Business.GetValue()) { + service := &api.Service{ + Name: utils.NewStringValue(value.Name), + Namespace: utils.NewStringValue(value.Namespace), + } + resp.Services = append(resp.Services, service) + } + return true, nil + } + + if err := s.caches.Service().IteratorServices(serviceIterProc); err != nil { + log.Error(err.Error(), ZapRequestID(requestID)) + return api.NewDiscoverServiceResponse(api.ExecuteException, req) + } + + return resp +} + +/** + * @brief 判断请求元数据是否属于服务的元数据 + */ +func checkServiceMetadata(requestMeta map[string]string, service *model.Service, business string) bool { + if len(service.Meta) == 0 && len(business) == 0 { + return false + } + if len(business) > 0 && business != service.Business { + return false + } + for key, requestValue := range requestMeta { + value, ok := service.Meta[key] + if !ok || requestValue != value { + return false + } + } + return true +} + +/** + * @brief 根据服务名查询服务实例列表 + */ +func (s *Server) ServiceInstancesCache(ctx context.Context, req *api.Service) *api.DiscoverResponse { + if req == nil { + return api.NewDiscoverInstanceResponse(api.EmptyRequest, req) + } + if s.caches == nil { + return api.NewDiscoverInstanceResponse(api.ClientAPINotOpen, req) + } + + serviceName := req.GetName().GetValue() + namespaceName := req.GetNamespace().GetValue() + + if serviceName == "" { + return api.NewDiscoverInstanceResponse(api.InvalidServiceName, req) + } + + // 消费服务为了兼容,可以不带namespace,server端使用默认的namespace + if namespaceName == "" { + namespaceName = DefaultNamespace + } + + // 数据源都来自Cache,这里拿到的service,已经是源服务 + service := s.getServiceCache(serviceName, namespaceName) + if service == nil { + log.Errorf("[Server][Service][Instance] not found name(%s) namespace(%s) service", serviceName, namespaceName) + return api.NewDiscoverInstanceResponse(api.NotFoundResource, req) + } + s.RecordDiscoverStatis(service.Name, service.Namespace) + // 获取revision,如果revision一致,则不返回内容,直接返回一个状态码 + revision := s.caches.GetServiceInstanceRevision(service.ID) + if revision == "" { + // 不能直接获取,则需要重新计算,大部分情况都可以直接获取的 + // 获取instance数据,service已经是源服务,可以直接查找cache + instances := s.caches.Instance().GetInstancesByServiceID(service.ID) + var revisionErr error + revision, revisionErr = s.GetServiceInstanceRevision(service.ID, instances) + if revisionErr != nil { + log.Errorf("[Server][Service][Instance] compute revision service(%s) err: %s", + service.ID, revisionErr.Error()) + return api.NewDiscoverInstanceResponse(api.ExecuteException, req) + } + } + if revision == req.GetRevision().GetValue() { + return api.NewDiscoverInstanceResponse(api.DataNoChange, req) + } + + // revision不一致,重新获取数据 + // 填充service数据 + resp := api.NewDiscoverInstanceResponse(api.ExecuteSuccess, service2Api(service)) + // 填充新的revision TODO + resp.Service.Revision.Value = revision + resp.Service.Name = req.GetName() // 别名场景,response需要保持和request的服务名一致 + // 填充instance数据 + resp.Instances = make([]*api.Instance, 0) // TODO + _ = s.caches.Instance(). + IteratorInstancesWithService(service.ID, // service已经是源服务 + func(key string, value *model.Instance) (b bool, e error) { + // 注意:这里的value是cache的,不修改cache的数据,通过getInstance,浅拷贝一份数据 + resp.Instances = append(resp.Instances, s.getInstance(req, value.Proto)) + return true, nil + }) + + return resp +} + +// 获取缓存中的路由配置信息 +func (s *Server) GetRoutingConfigWithCache(ctx context.Context, req *api.Service) *api.DiscoverResponse { + if s.caches == nil { + return api.NewDiscoverRoutingResponse(api.ClientAPINotOpen, req) + } + rid := ParseRequestID(ctx) + if req == nil { + return api.NewDiscoverRoutingResponse(api.EmptyRequest, req) + } + + if req.GetName().GetValue() == "" { + return api.NewDiscoverRoutingResponse(api.InvalidServiceName, req) + } + if req.GetNamespace().GetValue() == "" { + return api.NewDiscoverRoutingResponse(api.InvalidNamespaceName, req) + } + + resp := api.NewDiscoverRoutingResponse(api.ExecuteSuccess, nil) + resp.Service = &api.Service{ + Name: req.GetName(), + Namespace: req.GetNamespace(), + } + + // 先从缓存获取ServiceID,这里返回的是源服务 + service := s.getServiceCache(req.GetName().GetValue(), req.GetNamespace().GetValue()) + if service == nil { + return api.NewDiscoverRoutingResponse(api.NotFoundService, req) + } + out := s.caches.RoutingConfig().GetRoutingConfig(service.ID) + if out == nil { + return resp + } + + // 获取路由数据,并对比revision + if out.Revision == req.GetRevision().GetValue() { + return api.NewDiscoverRoutingResponse(api.DataNoChange, req) + } + + // 数据不一致,发生了改变 + // 数据格式转换,service只需要返回二元组与routing的revision + var err error + resp.Service.Revision = utils.NewStringValue(out.Revision) + resp.Routing, err = routingConfig2API(out, req.GetName().GetValue(), req.GetNamespace().GetValue()) + if err != nil { + log.Error(err.Error(), ZapRequestID(rid)) + return api.NewDiscoverRoutingResponse(api.ExecuteException, req) + } + + return resp +} + +// 获取缓存中的限流规则信息 +func (s *Server) GetRateLimitWithCache(ctx context.Context, req *api.Service) *api.DiscoverResponse { + if s.caches == nil { + return api.NewDiscoverRoutingResponse(api.ClientAPINotOpen, req) + } + + requestID := ParseRequestID(ctx) + + if req == nil { + return api.NewDiscoverRateLimitResponse(api.EmptyRequest, req) + } + + if req.GetName().GetValue() == "" { + return api.NewDiscoverRateLimitResponse(api.InvalidServiceName, req) + } + if req.GetNamespace().GetValue() == "" { + return api.NewDiscoverRateLimitResponse(api.InvalidNamespaceName, req) + } + + // 获取源服务 + service := s.getServiceCache(req.GetName().GetValue(), req.GetNamespace().GetValue()) + if service == nil { + return api.NewDiscoverRateLimitResponse(api.NotFoundService, req) + } + + resp := api.NewDiscoverRateLimitResponse(api.ExecuteSuccess, nil) + // 服务名和request保持一致 + resp.Service = &api.Service{ + Name: req.GetName(), + Namespace: req.GetNamespace(), + } + + // 获取最新的revision + lastRevision := s.caches.RateLimit().GetLastRevision(service.ID) + + // 缓存中无此服务的限流规则 + if lastRevision == "" { + return resp + } + + if req.GetRevision().GetValue() == lastRevision { + return api.NewDiscoverRateLimitResponse(api.DataNoChange, req) + } + + // 获取限流规则数据 + resp.Service.Revision = utils.NewStringValue(lastRevision) // 更新revision + + resp.RateLimit = &api.RateLimit{ + Revision: utils.NewStringValue(lastRevision), + Rules: []*api.Rule{}, + } + + rateLimitIterProc := func(key string, value *model.RateLimit) (bool, error) { + rateLimit, err := rateLimit2api(req.GetName().GetValue(), req.GetNamespace().GetValue(), value) + if err != nil { + return false, err + } + resp.RateLimit.Rules = append(resp.RateLimit.Rules, rateLimit) + return true, nil + } + + err := s.caches.RateLimit().GetRateLimit(service.ID, rateLimitIterProc) + if err != nil { + log.Error(err.Error(), ZapRequestID(requestID)) + return api.NewDiscoverRateLimitResponse(api.ExecuteException, req) + } + + return resp +} + +// 获取缓存中的熔断规则信息 +func (s *Server) GetCircuitBreakerWithCache(ctx context.Context, req *api.Service) *api.DiscoverResponse { + if s.caches == nil { + return api.NewDiscoverCircuitBreakerResponse(api.ClientAPINotOpen, req) + } + requestID := ParseRequestID(ctx) + if req == nil { + return api.NewDiscoverCircuitBreakerResponse(api.EmptyRequest, req) + } + + if req.GetName().GetValue() == "" { + return api.NewDiscoverCircuitBreakerResponse(api.InvalidServiceName, req) + } + if req.GetNamespace().GetValue() == "" { + return api.NewDiscoverCircuitBreakerResponse(api.InvalidNamespaceName, req) + } + + // 获取源服务 + service := s.getServiceCache(req.GetName().GetValue(), req.GetNamespace().GetValue()) + if service == nil { + return api.NewDiscoverCircuitBreakerResponse(api.NotFoundService, req) + } + + resp := api.NewDiscoverCircuitBreakerResponse(api.ExecuteSuccess, nil) + // 服务名和request保持一致 + resp.Service = &api.Service{ + Name: req.GetName(), + Namespace: req.GetNamespace(), + } + + out := s.caches.CircuitBreaker().GetCircuitBreakerConfig(service.ID) + if out == nil { + return resp + } + + // 获取熔断规则数据,并对比revision + if req.GetRevision().GetValue() == out.CircuitBreaker.Revision { + return api.NewDiscoverCircuitBreakerResponse(api.DataNoChange, req) + } + + // 数据不一致,发生了改变 + var err error + resp.Service.Revision = utils.NewStringValue(out.CircuitBreaker.Revision) + resp.CircuitBreaker, err = circuitBreaker2ClientAPI(out, req.GetName().GetValue(), req.GetNamespace().GetValue()) + if err != nil { + log.Error(err.Error(), ZapRequestID(requestID)) + return api.NewDiscoverCircuitBreakerResponse(api.ExecuteException, req) + } + return resp +} + +// 根据ServiceID获取instances +func (s *Server) getInstancesCache(service *model.Service) []*model.Instance { + id := s.getSourceServiceID(service) + // TODO refer_filter还要处理一下 + return s.caches.Instance().GetInstancesByServiceID(id) +} + +// 获取顶级服务ID +// 没有顶级ID,则返回自身 +func (s *Server) getSourceServiceID(service *model.Service) string { + if service == nil || service.ID == "" { + return "" + } + // 找到parent服务,最多两级,因此不用递归查找 + if service.IsAlias() { + return service.Reference + } + + return service.ID + +} + +// 根据服务名获取服务缓存数据 +// 注意,如果是服务别名查询,这里会返回别名的源服务,不会返回别名 +func (s *Server) getServiceCache(name string, namespace string) *model.Service { + sc := s.caches.Service() + service := sc.GetServiceByName(name, namespace) + if service == nil { + return nil + } + // 如果是服务别名,继续查找一下 + if service.IsAlias() { + service = sc.GetServiceByID(service.Reference) + if service == nil { + return nil + } + } + + if service.Meta == nil { + service.Meta = make(map[string]string) + } + return service +} \ No newline at end of file diff --git a/naming/healthcheck.go b/naming/healthcheck.go new file mode 100644 index 000000000..36657f4b6 --- /dev/null +++ b/naming/healthcheck.go @@ -0,0 +1,478 @@ +/* + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package naming + +import ( + "context" + "errors" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/common/redispool" + "go.uber.org/zap" + "strconv" + "strings" + "sync" + "time" + + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/timewheel" + "github.com/polarismesh/polaris-server/common/utils" +) + +/** + * @brief 健康检查配置 + */ +type HealthCheckConfig struct { + Open bool `yaml:"open"` + KvConnNum int `yaml:"kvConnNum"` + KvServiceName string `yaml:"kvServiceName"` + KvNamespace string `yaml:"kvNamespace"` + KvPasswd string `yaml:"kvPasswd"` + SlotNum int `yaml:"slotNum"` + LocalHost string `yaml:"localHost"` + MaxIdle int `yaml:"maxIdle"` + IdleTimeout int `yaml:"idleTimeout"` +} + +/** + * @brief 记录实例心跳信息 + */ +type HbInfo struct { + id string + addr string + beatTime int64 + ttl uint32 +} + +/** + * @brief 心跳管理器结构体 + * 包括时间轮、ckv连接池、存储实例心跳信息的map + */ +type HeartBeatMgr struct { + ctx context.Context + mu sync.Mutex + hbMap map[string]*HbInfo + ckvTw *timewheel.TimeWheel + dbTw *timewheel.TimeWheel + //ckvPool *ckv.Pool + redisPool *redispool.Pool +} + +/** + * @brief 时间轮任务结构体 + */ +type TimeWheelTask struct { + lastBeatTime int64 + hbInfo *HbInfo +} + +const ( + NotHealthy = 0 + Healthy = 1 + RedisNoKeyErr = "redigo: nil returned" +) + +var ( + healthCheckConf *HealthCheckConfig + hbMgr *HeartBeatMgr + + /** + * @brief 时间轮回调函数1:实例上报心跳1 ttl后执行,发现这 + * 段时间内该实例没有再次上报心跳则改写ckv中的实例状态为不健康 + */ + ckvCallback timewheel.Callback = func(data interface{}) { + task := data.(*TimeWheelTask) + lastBeatTime := task.lastBeatTime + // 说明本机收到了心跳,实例状态正常,不做任何处理 + if lastBeatTime < task.hbInfo.beatTime { + return + } + + // 更新上报心跳时间 + now := time.Now().Unix() + task.lastBeatTime = now + + // 从ckv获取实例状态,看其他server这段时间有没有收到心跳 + respCh := make(chan *redispool.Resp) + hbMgr.redisPool.Get(task.hbInfo.id, respCh) + resp := <-respCh + if !resp.Local { + if resp.Err != nil { + // 获取ckv失败,不能退出,直接进入set ckv unhealthy & dbCallback流程 + log.Errorf("[health check] addr:%s id:%s 1ttl get redis err:%s", + task.hbInfo.addr, task.hbInfo.id, resp.Err) + } else { + // ckv中value格式 健康状态(1健康 0不健康):心跳时间戳:写者ip + // 如: 1:timestamp:10.60.31.22 + // 基于解析性能考虑,没有使用json + res := strings.Split(resp.Value, ":") + kvBeatTime, err := strconv.ParseInt(res[1], 10, 64) + if err != nil { + log.Errorf("[health check] addr:%s id:%s redis beat time parse err:%s", + task.hbInfo.addr, task.hbInfo.id, err) + } + + // ckv中的实例状态为已经被改为不健康 + // 或ckv心跳时间 > 本地上次心跳时间,说明其他server收到了心跳,不做任何处理 + if res[0] == "0" || kvBeatTime > lastBeatTime { + return + } + } + + // 将ckv中状态改为不健康 + log.Infof("[health check] addr:%s id:%s 1ttl overtime, set redis not healthy", task.hbInfo.addr, task.hbInfo.id) + hbMgr.redisPool.Set(task.hbInfo.id, NotHealthy, now, respCh) + resp = <-respCh + if resp.Err != nil { + log.Errorf("[health check] addr:%s id:%s set redis err:%s", task.hbInfo.addr, task.hbInfo.id, resp.Err) + } + } + // 添加时间轮任务:再过2ttl后仍未收到心跳,改写db实例状态为不健康 + _ = hbMgr.dbTw.AddTask(time.Duration(2*task.hbInfo.ttl-1)*time.Second, task, dbCallback) + } + + /** + * @brief 时间轮回调函数2:实例上报心跳1 ttl后若 + * 未上报心跳,则再过2ttl后执行此函数,负责判定实例死亡、改写db状态 + */ + dbCallback timewheel.Callback = func(data interface{}) { + task := data.(*TimeWheelTask) + // 说明本机收到了心跳,实例状态正常,不做任何处理 + if task.lastBeatTime < task.hbInfo.beatTime { + return + } + + // 从ckv获取下key状态,看其他server有没有收到心跳 + respCh := make(chan *redispool.Resp) + hbMgr.redisPool.Get(task.hbInfo.id, respCh) + resp := <-respCh + if !resp.Local { + if resp.Err != nil { + log.Errorf("[healthCheck] dbCallback get addr(%s) id(%s) from redis err: %s", + task.hbInfo.addr, task.hbInfo.id, resp.Err) + } else { + res := strings.Split(resp.Value, ":") + if res[0] == "1" { + log.Infof("[health check] addr: %s id: %s redis status is healthy, ignore set db unhealthy", + task.hbInfo.addr, task.hbInfo.id) + return + } + } + + // 删除kv + log.Infof("[health check] del redis id:%s", task.hbInfo.id) + hbMgr.redisPool.Del(task.hbInfo.id, respCh) + resp = <-respCh + if resp.Err != nil { + log.Errorf("[health check] addr:%s id:%s del redis err:%s", task.hbInfo.addr, task.hbInfo.id, resp.Err) + } + } + + insCache := server.caches.Instance().GetInstance(task.hbInfo.id) + if insCache == nil { + log.Errorf(`[health check] addr:%s id:%s ready to set db status + not health, but not found instance`, task.hbInfo.addr, task.hbInfo.id) + return + } + + // 修改db状态为不健康 + // 如果用户关闭了健康检查功能,则不做任何处理 + if insCache.EnableHealthCheck() && insCache.Healthy() != false { + setInsDbStatus(task.hbInfo.id, task.hbInfo.addr, NotHealthy) + } + + // 从本机map中删除 + hbMgr.mu.Lock() + delete(hbMgr.hbMap, task.hbInfo.id) + hbMgr.mu.Unlock() + } +) + +/** +* @brief 设置健康检查配置 + */ +func SetHealthCheckConfig(conf *HealthCheckConfig) { + healthCheckConf = conf +} + +/** + * @brief 初始化心跳管理器 + */ +func NewHeartBeatMgr(ctx context.Context) (*HeartBeatMgr, error) { + kvService := server.caches.Service(). + GetServiceByName(healthCheckConf.KvServiceName, healthCheckConf.KvNamespace) + var kvInstances []*model.Instance + + if kvService != nil { + kvInstances = server.caches.Instance().GetInstancesByServiceID(kvService.ID) + } + //if len(kvInstances) == 0 { + // return nil, fmt.Errorf("no available ckv instance, serviceId:%s", kvService.ID) + //} + + redisPool, err := redispool.NewPool(healthCheckConf.KvConnNum, healthCheckConf.KvPasswd, + healthCheckConf.LocalHost, kvInstances, healthCheckConf.MaxIdle, healthCheckConf.IdleTimeout) + if err != nil { + return nil, err + } + + mgr := &HeartBeatMgr{ + ctx: ctx, + hbMap: make(map[string]*HbInfo), + ckvTw: timewheel.New(time.Second, healthCheckConf.SlotNum, "ckv task timewheel"), + dbTw: timewheel.New(time.Second, healthCheckConf.SlotNum, "db task timewheel"), + redisPool: redisPool, + } + if kvService != nil { + go mgr.watchCkvService(kvService.ID) + } + return mgr, nil +} + +/** + * @brief 启动心跳管理器,启动健康检查功能 + */ +func (hb *HeartBeatMgr) Start() { + hb.redisPool.Start() + hb.ckvTw.Start() + hb.dbTw.Start() +} + +/** + * @brief 监控ckv实例有没有变化 + */ +func (hb *HeartBeatMgr) watchCkvService(id string) { + kvInstances := server.caches.Instance().GetInstancesByServiceID(id) + lastRevision, err := server.GetServiceInstanceRevision(id, kvInstances) + if err != nil { + log.Errorf("[health check] get redis revision err:%s", err) + } + + for range time.NewTicker(10 * time.Second).C { + kvInstances = server.caches.Instance().GetInstancesByServiceID(id) + if len(kvInstances) == 0 { + // need alert + log.Errorf("[health check] get redis ins nil") + continue + } + newRevision, err := server.GetServiceInstanceRevision(id, kvInstances) + if err != nil { + log.Errorf("[health check] get redis revision err:%s", err) + continue + } + if lastRevision != newRevision { + err := hb.redisPool.Update(kvInstances) + if err != nil { + // need alert + log.Errorf("[health check] update redis pool err:%s", err) + continue + } + lastRevision = newRevision + } + } +} + +/** +* @brief 心跳处理函数 + */ +func (hb *HeartBeatMgr) healthCheck(ctx context.Context, instance *api.Instance) *api.Response { + id, errRsp := checkHeartbeatInstance(instance) + if errRsp != nil { + return errRsp + } + instance.Id = utils.NewStringValue(id) + insCache := server.caches.Instance().GetInstance(id) + if insCache == nil { + return api.NewInstanceResponse(api.NotFoundResource, instance) + } + + service := server.caches.Service().GetServiceByID(insCache.ServiceID) + if service == nil { + return api.NewInstanceResponse(api.NotFoundResource, instance) + } + // 鉴权 + token := instance.GetServiceToken().GetValue() + if !server.authority.VerifyToken(token) { + return api.NewInstanceResponse(api.InvalidServiceToken, instance) + } + ok := server.authority.VerifyInstance(service.Token, token) + if !ok { + return api.NewInstanceResponse(api.Unauthorized, instance) + } + + // 如果实例未开启健康检查,返回 + if !insCache.EnableHealthCheck() || insCache.HealthCheck() == nil { + return api.NewInstanceResponse(api.HeartbeatOnDisabledIns, instance) + } + + // 记录收到心跳的instance日志,方便定位实例是否上报心跳 + log.Info("receive heartbeat", ZapRequestID(ParseRequestID(ctx)), zap.String("id", id), + zap.String("service", service.Namespace+":"+service.Name), + zap.String("host", insCache.Host()), zap.Uint32("port", insCache.Port())) + addr := insCache.Host() + ":" + strconv.Itoa(int(insCache.Port())) + ttl := insCache.HealthCheck().GetHeartbeat().GetTtl().GetValue() + now := time.Now().Unix() + var hbInfo *HbInfo + respCh := make(chan *redispool.Resp) + + hb.mu.Lock() + hbInfo, ok = hb.hbMap[id] + if !ok { + hbInfo = &HbInfo{id, addr, now, ttl} + hb.hbMap[id] = hbInfo + hb.mu.Unlock() + + // hbMap中没有找到该实例,说明这是实例近期第一次上报心跳,set ckv中实例状态为健康 + log.Infof("[health check] addr:%s id:%s ttl:%d heartbeat first time, set redis", addr, id, ttl) + hbMgr.redisPool.Set(id, Healthy, now, respCh) + resp := <-respCh + if resp.Err != nil { + log.Errorf("[health check] addr:%s id:%s set redis err:%s", addr, id, resp.Err) + return api.NewInstanceResponse(api.HeartbeatException, instance) + } + } else { + hb.mu.Unlock() + lastBeatTime := hbInfo.beatTime + if now == lastBeatTime { + log.Debugf("[health check] addr:%s id:%s ins heartbeat exceed 1 time/s", addr, id) + return api.NewInstanceResponse(api.HeartbeatExceedLimit, instance) + } + + // 修改实例心跳上报时间 + hbInfo.beatTime = now + hbInfo.ttl = ttl + + // 本机超过1 ttl + 1s未收到心跳,set一次ckv状态 + if now-lastBeatTime >= int64(ttl+1) { + log.Infof("[health check] addr:%s, id:%s receive heart beat after ttl + 1s, set redis healthy", addr, id) + hbMgr.redisPool.Set(id, Healthy, now, respCh) + resp := <-respCh + + if resp.Err != nil { + log.Errorf("[health check] addr:%s id:%s set redis err:%s", addr, id, resp.Err) + return api.NewInstanceResponse(api.HeartbeatException, instance) + } + } + } + + // db中实例状态若为不健康,设为健康 + if insCache.Healthy() != true { + setInsDbStatus(id, addr, Healthy) + } + + // 将超时检查任务放入时间轮 + task := &TimeWheelTask{now, hbInfo} + _ = hb.ckvTw.AddTask(time.Duration(ttl+1)*time.Second, task, ckvCallback) + + return api.NewInstanceResponse(api.ExecuteSuccess, instance) +} + +// 获取上一次的心跳时间 +func (hb *HeartBeatMgr) acquireLastHeartbeat(instance *api.Instance) error { + id := instance.GetId().GetValue() + if instance.Metadata == nil { + instance.Metadata = make(map[string]string) + } + + // 先获取本地记录的时间,这里可能为空的 + // (该实例不是上报到这台server,可以根据ckv信息获取其上报的server) + hb.mu.Lock() + info, ok := hb.hbMap[id] + hb.mu.Unlock() + if ok { + instance.Metadata["last-heartbeat-time"] = time2String(time.Unix(info.beatTime, 0)) + instance.Metadata["system-time"] = time2String(time.Now()) + } + + // 获取ckv记录的时间 + respCh := make(chan *redispool.Resp) + hbMgr.redisPool.Get(id, respCh) + resp := <-respCh + if resp.Err != nil { + if resp.Err.Error() == RedisNoKeyErr { + return nil + } + + log.Errorf("[health check] get id(%s) from redis err: %s", id, resp.Err.Error()) + return resp.Err + } + if resp.Local { + return nil + } + + res := strings.Split(resp.Value, ":") + if len(res) != 3 { + log.Errorf("[health check] id(%s) redis record invalid(%s)", id, resp.Value) + return errors.New("invalid ckv record") + } + tm, err := strconv.ParseInt(res[1], 10, 64) + if err != nil { + log.Errorf("[health check] id(%s) redis record heartbeat time(%s) is invalid", id, res[1]) + return err + } + + // ckv记录的心跳时间与心跳server, + // 根据这个心跳server可以获取到实例上报到哪台心跳server + instance.Metadata["ckv-record-healthy"] = res[0] + instance.Metadata["ckv-record-heartbeat-time"] = time2String(time.Unix(tm, 0)) + instance.Metadata["ckv-record-heartbeat-server"] = res[2] + return nil +} + +/** +* @brief 修改实例状态 + 需要打印操作记录 + server 是当前package的全局变量 +*/ +func setInsDbStatus(id, addr string, status int) { + log.Infof("[health check] addr:%s id:%s set db status %d", addr, id, status) + err := server.storage.SetInstanceHealthStatus(id, status, NewUUID()) + if err != nil { + log.Errorf("[health check] id: %s set db status err:%s", id, err) + return + } + + instance := server.caches.Instance().GetInstance(id) + if instance == nil { + log.Errorf("[HealthCheck] not found instance(%s)", id) + return + } + service := server.caches.Service().GetServiceByID(instance.ServiceID) + if service == nil { + log.Errorf("[HealthCheck] not found serviceID(%s) for instance(%s)", + instance.ServiceID, id) + return + } + + healthStatus := true + if status == 0 { + healthStatus = false + } + recordInstance := &model.Instance{ + Proto: &api.Instance{ + Host: instance.Proto.GetHost(), + Port: instance.Proto.GetPort(), + Priority: instance.Proto.GetPriority(), + Weight: instance.Proto.GetWeight(), + Healthy: utils.NewBoolValue(healthStatus), + Isolate: instance.Proto.GetIsolate(), + }, + } + + server.RecordHistory(instanceRecordEntry(nil, service, recordInstance, model.OUpdate)) +} diff --git a/naming/instance.go b/naming/instance.go new file mode 100644 index 000000000..a61841342 --- /dev/null +++ b/naming/instance.go @@ -0,0 +1,1206 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package naming + +import ( + "context" + "fmt" + "time" + + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/common/utils" + "go.uber.org/zap" +) + +var ( + // 查询实例支持的过滤字段 + InstanceFilterAttributes = map[string]bool{ + "service": true, // 服务name + "namespace": true, // 服务namespace + "host": true, + "port": true, + "keys": true, + "values": true, + "protocol": true, + "version": true, + "health_status": true, + "healthy": true, // health_status, healthy都有,以healthy为准 + "isolate": true, + "weight": true, + "logic_set": true, + "cmdb_region": true, + "cmdb_zone": true, + "cmdb_idc": true, + "priority": true, + "offset": true, + "limit": true, + } + // 查询字段转为存储层的属性值,映射表 + InsFilter2toreAttr = map[string]string{ + "service": "name", + "healthy": "health_status", + } + // 不属于 instance 表属性的字段 + NotInsFilterAttr = map[string]bool { + "keys": true, + "values": true, + } +) + +/** + * @brief 批量创建服务实例 + */ +func (s *Server) CreateInstances(ctx context.Context, reqs []*api.Instance) *api.BatchWriteResponse { + if checkError := checkBatchInstance(reqs); checkError != nil { + return checkError + } + + return batchOperateInstances(ctx, reqs, s.CreateInstance) +} + +/** + * @brief 创建单个服务实例 + * 注意:创建实例需要对服务进行加锁保护服务不被删除 + */ +func (s *Server) CreateInstance(ctx context.Context, req *api.Instance) *api.Response { + rid := ParseRequestID(ctx) + pid := ParsePlatformID(ctx) + start := time.Now() + // 参数检查 + instanceID, checkError := checkCreateInstance(req) + if checkError != nil { + return checkError + } + // 限制instance频繁注册 + if ok := s.allowInstanceAccess(instanceID); !ok { + log.Error("create instance is not allow access", ZapRequestID(rid), ZapPlatformID(pid)) + return api.NewInstanceResponse(api.InstanceTooManyRequests, req) + } + + // 防止污染req,拷贝一份出来,并且填充一下token ID + ins := *req + ins.ServiceToken = utils.NewStringValue(parseInstanceReqToken(ctx, req)) + ins.Id = utils.NewStringValue(instanceID) + data, resp := s.createInstance(ctx, req, &ins) + if resp != nil { + return resp + } + + // 处理create成功的消息 + msg := fmt.Sprintf("create instance: id=%v, namespace=%v, service=%v, host=%v, port=%v", + ins.GetId().GetValue(), req.GetNamespace().GetValue(), req.GetService().GetValue(), + req.GetHost().GetValue(), req.GetPort().GetValue()) + log.Info(msg, ZapRequestID(rid), ZapPlatformID(pid), zap.Duration("cost", time.Now().Sub(start))) + service := &model.Service{ + Name: req.GetService().GetValue(), + Namespace: req.GetNamespace().GetValue(), + } + s.RecordHistory(instanceRecordEntry(ctx, service, data, model.OCreate)) + out := &api.Instance{ + Id: ins.GetId(), + Service: req.GetService(), + Namespace: req.GetNamespace(), + VpcId: req.GetVpcId(), + Host: req.GetHost(), + Port: req.GetPort(), + } + return api.NewInstanceResponse(api.ExecuteSuccess, out) +} + +// store operate +func (s *Server) createInstance(ctx context.Context, req *api.Instance, ins *api.Instance) ( + *model.Instance, *api.Response) { + if server.bc == nil || !server.bc.CreateInstanceOpen() { + return s.serialCreateInstance(ctx, req, ins) // 单个同步 + } + + return s.asyncCreateInstance(ctx, req, ins) // 批量异步 +} + +// 异步新建实例 +// 底层函数会合并create请求,增加并发创建的吞吐 +// req 原始请求 +// ins 包含了req数据与instanceID,serviceToken +func (s *Server) asyncCreateInstance(ctx context.Context, req *api.Instance, ins *api.Instance) ( + *model.Instance, *api.Response) { + rid := ParseRequestID(ctx) + pid := ParsePlatformID(ctx) + future := s.bc.AsyncCreateInstance(ins, ParsePlatformID(ctx), ParsePlatformToken(ctx)) + if err := future.Wait(); err != nil { + log.Error(err.Error(), ZapRequestID(rid), ZapPlatformID(pid)) + if future.Code() == api.ExistedResource { + req.Id = utils.NewStringValue(ins.GetId().GetValue()) + } + return nil, api.NewInstanceResponse(future.Code(), req) + } + + return future.Instance(), nil +} + +// 同步串行创建实例 +// req为原始的请求体 +// ins包括了req的内容,并且填充了instanceID与serviceToken +func (s *Server) serialCreateInstance(ctx context.Context, req *api.Instance, ins *api.Instance) ( + *model.Instance, *api.Response) { + // 检查服务实例是否存在 + rid := ParseRequestID(ctx) + pid := ParsePlatformID(ctx) + instance, err := s.storage.GetInstance(ins.GetId().GetValue()) + if err != nil { + log.Error(err.Error(), ZapRequestID(rid), ZapPlatformID(pid)) + return nil, api.NewInstanceResponse(api.StoreLayerException, req) + } + if instance != nil { + req.Id = utils.NewStringValue(ins.GetId().GetValue()) + return nil, api.NewInstanceResponse(api.ExistedResource, req) + } + + // 鉴权,这里拿的源服务的,如果是别名,service=nil + service, err := s.storage.GetSourceServiceToken(req.GetService().GetValue(), req.GetNamespace().GetValue()) + if err != nil { + log.Error(err.Error(), ZapRequestID(rid), ZapPlatformID(pid)) + return nil, api.NewInstanceResponse(api.StoreLayerException, req) + } + if service == nil { + return nil, api.NewInstanceResponse(api.NotFoundResource, req) + } + if err := s.verifyInstanceAuth(ctx, service, req); err != nil { + return nil, err + } + + // 直接同步创建服务实例 + data := utils.CreateInstanceModel(service.ID, ins) + if err := s.storage.AddInstance(data); err != nil { + log.Error(err.Error(), ZapRequestID(rid), ZapPlatformID(pid)) + return nil, wrapperInstanceStoreResponse(req, err) + } + + return data, nil +} + +/** + * @brief 批量删除服务实例 + */ +func (s *Server) DeleteInstances(ctx context.Context, req []*api.Instance) *api.BatchWriteResponse { + if checkError := checkBatchInstance(req); checkError != nil { + return checkError + } + + return batchOperateInstances(ctx, req, s.DeleteInstance) +} + +/** + * @brief 删除单个服务实例 + */ +func (s *Server) DeleteInstance(ctx context.Context, req *api.Instance) *api.Response { + rid := ParseRequestID(ctx) + pid := ParsePlatformID(ctx) + + // 参数检查 + instanceID, checkError := checkReviseInstance(req) + if checkError != nil { + return checkError + } + // 限制instance频繁反注册 + if ok := s.allowInstanceAccess(instanceID); !ok { + log.Error("delete instance is not allow access", ZapRequestID(rid), ZapPlatformID(pid)) + return api.NewInstanceResponse(api.InstanceTooManyRequests, req) + } + + ins := *req // 防止污染外部的req + ins.Id = utils.NewStringValue(instanceID) + ins.ServiceToken = utils.NewStringValue(parseInstanceReqToken(ctx, req)) + return s.deleteInstance(ctx, req, &ins) +} + +// 删除实例的store操作 +// req 原始请求 +// ins 填充了instanceID与serviceToken +func (s *Server) deleteInstance(ctx context.Context, req *api.Instance, ins *api.Instance) *api.Response { + if s.bc == nil || !s.bc.DeleteInstanceOpen() { + return s.serialDeleteInstance(ctx, req, ins) + } + + return s.asyncDeleteInstance(ctx, req, ins) +} + +// 串行删除实例 +// 返回实例所属的服务和resp +func (s *Server) serialDeleteInstance(ctx context.Context, req *api.Instance, ins *api.Instance) *api.Response { + start := time.Now() + // 检查服务实例是否存在 + rid := ParseRequestID(ctx) + pid := ParsePlatformID(ctx) + instance, err := s.storage.GetInstance(ins.GetId().GetValue()) + if err != nil { + log.Error(err.Error(), ZapRequestID(rid)) + return api.NewInstanceResponse(api.StoreLayerException, req) + } + if instance == nil { + // 实例不存在,则返回成功 + return api.NewInstanceResponse(api.ExecuteSuccess, req) + } + // 鉴权 + service, resp := s.instanceAuth(ctx, req, instance.ServiceID) + if resp != nil { + return resp + } + + // 存储层操作 + if err := s.storage.DeleteInstance(instance.ID()); err != nil { + log.Error(err.Error(), ZapRequestID(rid), ZapPlatformID(pid)) + return wrapperInstanceStoreResponse(req, err) + } + + msg := fmt.Sprintf("delete instance: id=%v, namespace=%v, service=%v, host=%v, port=%v", + instance.ID(), service.Namespace, service.Name, instance.Host(), instance.Port()) + log.Info(msg, ZapRequestID(rid), ZapPlatformID(pid), zap.Duration("cost", time.Now().Sub(start))) + s.RecordHistory(instanceRecordEntry(ctx, service, instance, model.ODelete)) + + return api.NewInstanceResponse(api.ExecuteSuccess, req) +} + +// 异步删除实例 +// 返回实例所属的服务和resp +func (s *Server) asyncDeleteInstance(ctx context.Context, req *api.Instance, ins *api.Instance) *api.Response { + start := time.Now() + rid := ParseRequestID(ctx) + pid := ParsePlatformID(ctx) + future := s.bc.AsyncDeleteInstance(ins, ParsePlatformID(ctx), ParsePlatformToken(ctx)) + if err := future.Wait(); err != nil { + // 如果发现不存在资源,意味着实例已经被删除,直接返回成功 + if future.Code() == api.NotFoundResource { + return api.NewInstanceResponse(api.ExecuteSuccess, req) + } + log.Error(err.Error(), ZapRequestID(rid), ZapPlatformID(pid)) + return api.NewInstanceResponse(future.Code(), req) + } + instance := future.Instance() + + // 打印本地日志与操作记录 + msg := fmt.Sprintf("delete instance: id=%v, namespace=%v, service=%v, host=%v, port=%v", + instance.ID(), instance.Namespace(), instance.Service(), instance.Host(), instance.Port()) + log.Info(msg, ZapRequestID(rid), ZapPlatformID(pid), zap.Duration("cost", time.Now().Sub(start))) + service := &model.Service{Name: instance.Service(), Namespace: instance.Namespace()} + s.RecordHistory(instanceRecordEntry(ctx, service, instance, model.ODelete)) + + return api.NewInstanceResponse(api.ExecuteSuccess, req) +} + +/** + * @brief 根据host批量删除服务实例 + */ +func (s *Server) DeleteInstancesByHost(ctx context.Context, req []*api.Instance) *api.BatchWriteResponse { + if checkError := checkBatchInstance(req); checkError != nil { + return checkError + } + + return batchOperateInstances(ctx, req, s.DeleteInstanceByHost) +} + +/** + * @brief 根据host删除服务实例 + */ +func (s *Server) DeleteInstanceByHost(ctx context.Context, req *api.Instance) *api.Response { + requestID := ParseRequestID(ctx) + platformID := ParsePlatformID(ctx) + + // 参数校验 + if err := checkInstanceByHost(req); err != nil { + return err + } + + // 获取实例 + instances, service, err := s.getInstancesMainByService(ctx, req) + if err != nil { + return err + } + + if instances == nil { + return api.NewInstanceResponse(api.ExecuteSuccess, req) + } + + ids := make([]interface{}, 0, len(instances)) + for _, instance := range instances { + ids = append(ids, instance.ID()) + } + + if err := s.storage.BatchDeleteInstances(ids); err != nil { + log.Error(err.Error(), ZapRequestID(requestID), ZapPlatformID(platformID)) + return wrapperInstanceStoreResponse(req, err) + } + + for _, instance := range instances { + msg := fmt.Sprintf("delete instance: id=%v, namespace=%v, service=%v, host=%v, port=%v", + instance.ID(), service.Namespace, service.Name, instance.Host(), instance.Port()) + log.Info(msg, ZapRequestID(requestID), ZapPlatformID(platformID)) + s.RecordHistory(instanceRecordEntry(ctx, service, instance, model.ODelete)) + } + + return api.NewInstanceResponse(api.ExecuteSuccess, req) +} + +/** + * @brief 批量修改服务实例 + */ +func (s *Server) UpdateInstances(ctx context.Context, req []*api.Instance) *api.BatchWriteResponse { + if checkError := checkBatchInstance(req); checkError != nil { + return checkError + } + + return batchOperateInstances(ctx, req, s.UpdateInstance) +} + +/** + * @brief 修改单个服务实例 + */ +func (s *Server) UpdateInstance(ctx context.Context, req *api.Instance) *api.Response { + service, instance, preErr := s.execInstancePreStep(ctx, req) + if preErr != nil { + return preErr + } + if err := checkMetadata(req.GetMetadata()); err != nil { + return api.NewInstanceResponse(api.InvalidMetadata, req) + } + + // 修改 + requestID := ParseRequestID(ctx) + platformID := ParsePlatformID(ctx) + log.Info(fmt.Sprintf("old instance: %+v", instance), ZapRequestID(requestID), ZapPlatformID(platformID)) + + // 存储层操作 + if needUpdate := s.updateInstanceAttribute(req, instance); !needUpdate { + log.Info("update instance no data change, no need update", + ZapRequestID(requestID), ZapPlatformID(platformID), zap.String("instance", req.String())) + return api.NewInstanceResponse(api.NoNeedUpdate, req) + } + if err := s.storage.UpdateInstance(instance); err != nil { + log.Error(err.Error(), ZapRequestID(requestID), ZapPlatformID(platformID)) + return wrapperInstanceStoreResponse(req, err) + } + + msg := fmt.Sprintf("update instance: id=%v, namespace=%v, service=%v, host=%v, port=%v, healthy = %v", + instance.ID(), service.Namespace, service.Name, instance.Host(), + instance.Port(), instance.Healthy()) + log.Info(msg, ZapRequestID(requestID), ZapPlatformID(platformID)) + s.RecordHistory(instanceRecordEntry(ctx, service, instance, model.OUpdate)) + + return api.NewInstanceResponse(api.ExecuteSuccess, req) +} + +/** + * @brief 批量修改服务实例隔离状态 + * @note 必填参数为service+namespace+host + */ +func (s *Server) UpdateInstancesIsolate(ctx context.Context, req []*api.Instance) *api.BatchWriteResponse { + if checkError := checkBatchInstance(req); checkError != nil { + return checkError + } + + return batchOperateInstances(ctx, req, s.UpdateInstanceIsolate) +} + +/** + * @brief 修改服务实例隔离状态 + * @note 必填参数为service+namespace+ip + */ +func (s *Server) UpdateInstanceIsolate(ctx context.Context, req *api.Instance) *api.Response { + requestID := ParseRequestID(ctx) + platformID := ParsePlatformID(ctx) + + // 参数校验 + if err := checkInstanceByHost(req); err != nil { + return err + } + if req.GetIsolate() == nil { + return api.NewInstanceResponse(api.InvalidInstanceIsolate, req) + } + + // 获取实例 + instances, service, err := s.getInstancesMainByService(ctx, req) + if err != nil { + return err + } + if instances == nil { + return api.NewInstanceResponse(api.NotFoundInstance, req) + } + + // 判断是否需要更新 + needUpdate := false + for _, instance := range instances { + if instance.Isolate() != req.GetIsolate().GetValue() { + needUpdate = true + break + } + } + if !needUpdate { + return api.NewInstanceResponse(api.NoNeedUpdate, req) + } + + isolate := 0 + if req.GetIsolate().GetValue() { + isolate = 1 + } + + ids := make([]interface{}, 0, len(instances)) + for _, instance := range instances { + // 方便后续打印操作记录 + instance.Proto.Isolate = req.GetIsolate() + ids = append(ids, instance.ID()) + } + + if err := s.storage.BatchSetInstanceIsolate(ids, isolate, NewUUID()); err != nil { + log.Error(err.Error(), ZapRequestID(requestID), ZapPlatformID(platformID)) + return wrapperInstanceStoreResponse(req, err) + } + + for _, instance := range instances { + msg := fmt.Sprintf("update instance: id=%v, namespace=%v, service=%v, host=%v, port=%v, isolate=%v", + instance.ID(), service.Namespace, service.Name, instance.Host(), instance.Port(), instance.Isolate()) + log.Info(msg, ZapRequestID(requestID), ZapPlatformID(platformID)) + s.RecordHistory(instanceRecordEntry(ctx, service, instance, model.OUpdateIsolate)) + } + + return api.NewInstanceResponse(api.ExecuteSuccess, req) +} + +/** + * @brief 根据ip隔离和删除服务实例的参数检查 + */ +func checkInstanceByHost(req *api.Instance) *api.Response { + if req == nil { + return api.NewInstanceResponse(api.EmptyRequest, req) + } + if err := checkResourceName(req.GetService()); err != nil { + return api.NewInstanceResponse(api.InvalidServiceName, req) + } + if err := checkResourceName(req.GetNamespace()); err != nil { + return api.NewInstanceResponse(api.InvalidNamespaceName, req) + } + if err := checkInstanceHost(req.GetHost()); err != nil { + return api.NewInstanceResponse(api.InvalidInstanceHost, req) + } + return nil +} + +/** + * @brief 根据服务和host获取服务实例 + */ +func (s *Server) getInstancesMainByService(ctx context.Context, req *api.Instance) ( + []*model.Instance, *model.Service, *api.Response) { + requestID := ParseRequestID(ctx) + platformID := ParsePlatformID(ctx) + + // 检查服务 + // 这里获取的是源服务的token。如果是别名,service=nil + service, err := s.storage.GetSourceServiceToken(req.GetService().GetValue(), req.GetNamespace().GetValue()) + if err != nil { + log.Error(err.Error(), ZapRequestID(requestID), ZapPlatformID(platformID)) + return nil, nil, api.NewInstanceResponse(api.StoreLayerException, req) + } + if service == nil { + return nil, nil, api.NewInstanceResponse(api.NotFoundService, req) + } + + // 鉴权 + if err := s.verifyInstanceAuth(ctx, service, req); err != nil { + return nil, nil, err + } + + // 获取服务实例 + instances, err := s.storage.GetInstancesMainByService(service.ID, req.GetHost().GetValue()) + if err != nil { + log.Error(err.Error(), ZapRequestID(requestID), ZapPlatformID(platformID)) + return nil, nil, api.NewInstanceResponse(api.StoreLayerException, req) + } + return instances, service, nil +} + +/** + * @brief 修改服务属性 + */ +func (s *Server) updateInstanceAttribute(req *api.Instance, instance *model.Instance) bool { + // #lizard forgives + instance.MallocProto() + needUpdate := false + insProto := instance.Proto + if ok := instanceMetaNeedUpdate(req.GetMetadata(), instance.Metadata()); ok { + insProto.Metadata = req.GetMetadata() + needUpdate = true + } + if !needUpdate { + // 不需要更新metadata,则置空 + insProto.Metadata = nil + } + + if req.GetProtocol() != nil && req.GetProtocol().GetValue() != instance.Protocol() { + insProto.Protocol = req.GetProtocol() + needUpdate = true + } + + if req.GetVersion() != nil && req.GetVersion().GetValue() != instance.Version() { + insProto.Version = req.GetVersion() + needUpdate = true + } + + if req.GetPriority() != nil && req.GetPriority().GetValue() != instance.Priority() { + insProto.Priority = req.GetPriority() + needUpdate = true + } + + if req.GetWeight() != nil && req.GetWeight().GetValue() != instance.Weight() { + insProto.Weight = req.GetWeight() + needUpdate = true + } + + if req.GetHealthy() != nil && req.GetHealthy().GetValue() != instance.Healthy() { + insProto.Healthy = req.GetHealthy() + needUpdate = true + } + + if req.GetIsolate() != nil && req.GetIsolate().GetValue() != instance.Isolate() { + insProto.Isolate = req.GetIsolate() + needUpdate = true + } + + if req.GetLogicSet() != nil && req.GetLogicSet().GetValue() != instance.LogicSet() { + insProto.LogicSet = req.GetLogicSet() + needUpdate = true + } + + if ok := updateHealthCheck(req, instance); ok { + needUpdate = true + } + + // 每次更改,都要生成一个新的uuid + if needUpdate { + insProto.Revision = utils.NewStringValue(NewUUID()) + } + + return needUpdate +} + +// 健康检查的更新 +func updateHealthCheck(req *api.Instance, instance *model.Instance) bool { + needUpdate := false + insProto := instance.Proto + // health Check,healthCheck不能为空,且没有把enable_health_check置为false + if req.GetHealthCheck().GetHeartbeat() != nil && + (req.GetEnableHealthCheck() == nil || req.GetEnableHealthCheck().GetValue()) { + // 如果数据库中实例原有是不打开健康检查, + // 那么一旦打开,status需置为false,等待一次心跳成功才能变成true + if instance.EnableHealthCheck() == false { + // 需要重置healthy,则认为有变更 + insProto.Healthy = utils.NewBoolValue(false) + insProto.EnableHealthCheck = utils.NewBoolValue(true) + needUpdate = true + } + + ttl := req.GetHealthCheck().GetHeartbeat().GetTtl().GetValue() + if ttl == 0 || ttl > 60 { + ttl = DefaultTLL + } + if ttl != instance.HealthCheck().GetHeartbeat().GetTtl().GetValue() { + // ttl有变更 + needUpdate = true + } + if api.HealthCheck_HEARTBEAT != instance.HealthCheck().GetType() { + // health check type有变更 + needUpdate = true + } + insProto.HealthCheck = req.GetHealthCheck() + insProto.HealthCheck.Type = api.HealthCheck_HEARTBEAT + if insProto.HealthCheck.Heartbeat.Ttl == nil { + insProto.HealthCheck.Heartbeat.Ttl = utils.NewUInt32Value(0) + } + insProto.HealthCheck.Heartbeat.Ttl.Value = ttl + } + + // update的时候,修改了enableHealthCheck的值 + if req.GetEnableHealthCheck() != nil && req.GetEnableHealthCheck().GetValue() == false { + if req.GetEnableHealthCheck().GetValue() != instance.EnableHealthCheck() { + needUpdate = true + } + if insProto.GetHealthCheck() != nil { + needUpdate = true + } + + insProto.EnableHealthCheck = utils.NewBoolValue(false) + insProto.HealthCheck = nil + } + + return needUpdate +} + +/** + * @brief 查询服务实例 + */ +func (s *Server) GetInstances(query map[string]string) *api.BatchQueryResponse { + // 对数据先进行提前处理一下 + filters, metaFilter, batchErr := preGetInstances(query) + if batchErr != nil { + return batchErr + } + // 分页数据 + offset, limit, err := ParseOffsetAndLimit(filters) + if err != nil { + return api.NewBatchQueryResponse(api.InvalidParameter) + } + + total, instances, err := s.storage.GetExpandInstances(filters, metaFilter, offset, limit) + if err != nil { + log.Errorf("[Server][Instances][Query] instances store err: %s", err.Error()) + return api.NewBatchQueryResponse(api.StoreLayerException) + } + + out := api.NewBatchQueryResponse(api.ExecuteSuccess) + out.Amount = utils.NewUInt32Value(total) + out.Size = utils.NewUInt32Value(uint32(len(instances))) + + apiInstances := make([]*api.Instance, 0, len(instances)) + for _, instance := range instances { + // 数据来源于数据库,不需要拷贝一份,直接填充后返回 + s.packCmdb(instance.Proto) + apiInstances = append(apiInstances, instance.Proto) + } + out.Instances = apiInstances + + return out +} + +/** + * @brief 查询总的服务实例,不带过滤条件的 + */ +func (s *Server) GetInstancesCount() *api.BatchQueryResponse { + count, err := s.storage.GetInstancesCount() + if err != nil { + log.Errorf("[Server][Instance][Count] storage get err: %s", err.Error()) + return api.NewBatchQueryResponse(api.StoreLayerException) + } + + out := api.NewBatchQueryResponse(api.ExecuteSuccess) + out.Amount = utils.NewUInt32Value(count) + out.Instances = make([]*api.Instance, 0) + return out +} + +// 清理无效的实例(flag == 1) +func (s *Server) CleanInstance(ctx context.Context, req *api.Instance) *api.Response { + // 无效数据,不需要鉴权,直接删除 + getInstanceID := func() (string, *api.Response) { + if req.GetId() != nil { + if req.GetId().GetValue() == "" { + return "", api.NewInstanceResponse(api.InvalidInstanceID, req) + } + return req.GetId().GetValue(), nil + } + return checkInstanceTetrad(req) + } + + instanceID, resp := getInstanceID() + if resp != nil { + return resp + } + if err := s.storage.CleanInstance(instanceID); err != nil { + log.Error("Clean instance", zap.String("err", err.Error()), ZapRequestID(ParseRequestID(ctx))) + return api.NewInstanceResponse(api.StoreLayerException, req) + } + + log.Info("Clean instance", ZapRequestID(ParseRequestID(ctx)), zap.String("instanceID", instanceID)) + return api.NewInstanceResponse(api.ExecuteSuccess, req) +} + +// 获取上一次心跳的时间 +func (s *Server) GetLastHeartbeat(req *api.Instance) *api.Response { + if s.hbMgr == nil { + return api.NewInstanceResponse(api.HealthCheckNotOpen, req) + } + if s.caches == nil || s.caches.Instance() == nil || s.caches.Service() == nil { + return api.NewInstanceResponse(api.ClientAPINotOpen, req) + } + + id := req.GetId().GetValue() + if id == "" { + tmpID, resp := checkInstanceTetrad(req) + if resp != nil { + return resp + } + id = tmpID + } + if id == "" { + return api.NewInstanceResponse(api.InvalidInstanceID, req) + } + req.Id = utils.NewStringValue(id) + + instance := s.caches.Instance().GetInstance(id) + if instance == nil { + return api.NewInstanceResponse(api.NotFoundInstance, req) + } + service := s.caches.Service().GetServiceByID(instance.ServiceID) + if service == nil { + return api.NewInstanceResponse(api.NotFoundService, req) + } + + if err := s.hbMgr.acquireLastHeartbeat(req); err != nil { + return api.NewInstanceRespWithError(api.ExecuteException, err, req) + } + req.Service = utils.NewStringValue(service.Name) + req.Namespace = utils.NewStringValue(service.Namespace) + req.Host = instance.Proto.GetHost() + req.Port = instance.Proto.GetPort() + req.VpcId = instance.Proto.GetVpcId() + req.HealthCheck = instance.HealthCheck() + return api.NewInstanceResponse(api.ExecuteSuccess, req) +} + +// update/delete instance前置条件 +func (s *Server) execInstancePreStep(ctx context.Context, req *api.Instance) ( + *model.Service, *model.Instance, *api.Response) { + rid := ParseRequestID(ctx) + + // 参数检查 + instanceID, checkError := checkReviseInstance(req) + if checkError != nil { + return nil, nil, checkError + } + + // 检查服务实例是否存在 + instance, err := s.storage.GetInstance(instanceID) + if err != nil { + log.Error(err.Error(), ZapRequestID(rid)) + return nil, nil, api.NewInstanceResponse(api.StoreLayerException, req) + } + if instance == nil { + return nil, nil, api.NewInstanceResponse(api.NotFoundInstance, req) + } + + service, resp := s.instanceAuth(ctx, req, instance.ServiceID) + if resp != nil { + return nil, nil, resp + } + + return service, instance, nil +} + +// 实例鉴权 +func (s *Server) instanceAuth(ctx context.Context, req *api.Instance, serviceID string) ( + *model.Service, *api.Response) { + service, err := s.storage.GetServiceByID(serviceID) + if err != nil { + log.Error(err.Error(), ZapRequestID(ParseRequestID(ctx))) + return nil, api.NewInstanceResponse(api.StoreLayerException, req) + } + if service == nil { + return nil, api.NewInstanceResponse(api.NotFoundResource, req) + } + + // 鉴权 + if err := s.verifyInstanceAuth(ctx, service, req); err != nil { + return nil, err + } + return service, nil +} + +/** + * @brief 实例鉴权 + */ +func (s *Server) verifyInstanceAuth(ctx context.Context, service *model.Service, req *api.Instance) *api.Response { + if ok := s.verifyAuthByPlatform(ctx, service.PlatformID); !ok { + // 检查token是否存在 + serviceToken := parseInstanceReqToken(ctx, req) + if !s.authority.VerifyToken(serviceToken) { + return api.NewInstanceResponse(api.InvalidServiceToken, req) + } + + // 检查token是否ok + if ok := s.authority.VerifyInstance(service.Token, serviceToken); !ok { + return api.NewInstanceResponse(api.Unauthorized, req) + } + } + return nil +} + +// 获取api.instance +func (s *Server) getInstance(service *api.Service, instance *api.Instance) *api.Instance { + out := &api.Instance{ + Id: instance.GetId(), + Service: service.GetName(), + Namespace: service.GetNamespace(), + VpcId: instance.GetVpcId(), + Host: instance.GetHost(), + Port: instance.GetPort(), + Protocol: instance.GetProtocol(), + Version: instance.GetVersion(), + Priority: instance.GetPriority(), + Weight: instance.GetWeight(), + EnableHealthCheck: instance.GetEnableHealthCheck(), + HealthCheck: instance.GetHealthCheck(), + Healthy: instance.GetHealthy(), + Isolate: instance.GetIsolate(), + Location: instance.GetLocation(), + Metadata: instance.GetMetadata(), + LogicSet: instance.GetLogicSet(), + //Ctime: instance.GetCtime(), + Mtime: instance.GetMtime(), + Revision: instance.GetRevision(), + } + + s.packCmdb(out) + return out +} + +// 获取cmdb +func (s *Server) packCmdb(instance *api.Instance) { + if instance == nil || instance.GetLocation() != nil { + return + } + if s.cmdb == nil { + return + } + + location, err := s.cmdb.GetLocation(instance.GetHost().GetValue()) + if err == nil && location != nil { + instance.Location = location.Proto + } + return +} + +/* + * @brief 检查批量请求 + */ +func checkBatchInstance(req []*api.Instance) *api.BatchWriteResponse { + if len(req) == 0 { + return api.NewBatchWriteResponse(api.EmptyRequest) + } + + if len(req) > MaxBatchSize { + return api.NewBatchWriteResponse(api.BatchSizeOverLimit) + } + + return nil +} + +/* + * @brief 检查创建服务实例请求参数 + */ +func checkCreateInstance(req *api.Instance) (string, *api.Response) { + if req == nil { + return "", api.NewInstanceResponse(api.EmptyRequest, req) + } + + if err := checkMetadata(req.GetMetadata()); err != nil { + return "", api.NewInstanceResponse(api.InvalidMetadata, req) + } + + // 检查字段长度是否大于DB中对应字段长 + err, notOk := CheckDbInstanceFieldLen(req) + if notOk { + return "", err + } + + return checkInstanceTetrad(req) +} + +/* + * @brief 检查删除/修改服务实例请求参数 + */ +func checkReviseInstance(req *api.Instance) (string, *api.Response) { + if req == nil { + return "", api.NewInstanceResponse(api.EmptyRequest, req) + } + + if req.GetId() != nil { + if req.GetId().GetValue() == "" { + return "", api.NewInstanceResponse(api.InvalidInstanceID, req) + } + return req.GetId().GetValue(), nil + } + + // 检查字段长度是否大于DB中对应字段长 + err, notOk := CheckDbInstanceFieldLen(req) + if notOk { + return "", err + } + + return checkInstanceTetrad(req) +} + +/* + * @brief 检查心跳实例请求参数 + * 检查是否存在token,以及 id或者四元组 + * 注意:心跳上报只允许从client上报,因此token只会存在req中 + */ +func checkHeartbeatInstance(req *api.Instance) (string, *api.Response) { + if req == nil { + return "", api.NewInstanceResponse(api.EmptyRequest, req) + } + if req.GetId() != nil { + if req.GetId().GetValue() == "" { + return "", api.NewInstanceResponse(api.InvalidInstanceID, req) + } + return req.GetId().GetValue(), nil + } + return checkInstanceTetrad(req) +} + +/* + * @brief 根据服务实例四元组计算ID + */ +func checkInstanceTetrad(req *api.Instance) (string, *api.Response) { + if err := checkResourceName(req.GetService()); err != nil { + return "", api.NewInstanceResponse(api.InvalidServiceName, req) + } + + if err := checkResourceName(req.GetNamespace()); err != nil { + return "", api.NewInstanceResponse(api.InvalidNamespaceName, req) + } + + if err := checkInstanceHost(req.GetHost()); err != nil { + return "", api.NewInstanceResponse(api.InvalidInstanceHost, req) + } + + if err := checkInstancePort(req.GetPort()); err != nil { + return "", api.NewInstanceResponse(api.InvalidInstancePort, req) + } + + id, err := CalculateInstanceID(req.GetNamespace().GetValue(), req.GetService().GetValue(), + req.GetVpcId().GetValue(), + req.GetHost().GetValue(), + req.GetPort().GetValue(), + ) + if err != nil { + return "", api.NewInstanceResponse(api.ExecuteException, req) + } + + return id, nil +} + +// 获取instance请求的token信息 +func parseInstanceReqToken(ctx context.Context, req *api.Instance) string { + if reqToken := req.GetServiceToken().GetValue(); reqToken != "" { + return reqToken + } + + return ParseToken(ctx) +} + +// 实例查询前置处理 +func preGetInstances(query map[string]string) (map[string]string, map[string]string, *api.BatchQueryResponse) { + // 不允许全量查询服务实例 + if len(query) == 0 { + return nil, nil, api.NewBatchQueryResponse(api.EmptyQueryParameter) + } + _, serviceIsAvail := query["service"] + _, namespaceIsAvail := query["namespace"] + _, hostIsAvail := query["host"] + // 要么(service,namespace)存在,要么host存在,不然视为参数不完整 + if !((serviceIsAvail && namespaceIsAvail) || hostIsAvail) { + return nil, nil, api.NewBatchQueryResponse(api.InvalidQueryInsParameter) + } + + var metaFilter map[string]string + metaKey, metaKeyAvail := query["keys"] + metaValue, metaValueAvail := query["values"] + if metaKeyAvail != metaValueAvail { + return nil, nil, api.NewBatchQueryResponseWithMsg( + api.InvalidQueryInsParameter, "instance metadata key and value must be both provided") + } + if metaKeyAvail { + metaFilter = map[string]string{metaKey:metaValue} + } + + // 以healthy为准 + _, lhs := query["health_status"] + _, rhs := query["healthy"] + if lhs && rhs { + delete(query, "health_status") + } + + bool2Str := func(key string) { + val, ok := query[key] + if !ok { + return + } + if val == "true" { + query[key] = "1" + } else if val == "false" { + query[key] = "0" + } + } + + // 处理一下两个bool值的字段 + bool2Str("health_status") + bool2Str("healthy") + bool2Str("isolate") + + filters := make(map[string]string) + for key, value := range query { + if _, ok := InstanceFilterAttributes[key]; !ok { + log.Errorf("[Server][Instance][Query] attribute(%s) is not allowed", key) + return nil, metaFilter, api.NewBatchQueryResponseWithMsg(api.InvalidParameter, key+" is not allowed") + } + + if value == "" { + log.Errorf("[Server][Instance][Query] attribute(%s: %s) is not allowed empty", key, value) + return nil, metaFilter, + api.NewBatchQueryResponseWithMsg(api.InvalidParameter, "the value for "+key+" is empty") + } + if attr, ok := InsFilter2toreAttr[key]; ok { + key = attr + } + if !NotInsFilterAttr[key] { + filters[key] = value + } + } + + return filters, metaFilter, nil +} + +// instance metadata need update +func instanceMetaNeedUpdate(req map[string]string, old map[string]string) bool { + if req == nil { + return false + } + + if len(req) != len(old) { + return true + } + + needUpdate := false + for key, value := range req { + oldValue, ok := old[key] + if !ok { + needUpdate = true + break + } + if value != oldValue { + needUpdate = true + break + } + } + if needUpdate { + return needUpdate + } + + for key, value := range old { + newValue, ok := req[key] + if !ok { + needUpdate = true + break + } + if value != newValue { + needUpdate = true + break + } + } + + return needUpdate +} + +// 批量操作实例 +func batchOperateInstances(ctx context.Context, reqs []*api.Instance, + handler func(ctx context.Context, req *api.Instance) *api.Response) *api.BatchWriteResponse { + responses := api.NewBatchWriteResponse(api.ExecuteSuccess) + + chs := make([]chan *api.Response, 0, len(reqs)) + for i, instance := range reqs { + chs = append(chs, make(chan *api.Response)) + go func(index int, ins *api.Instance) { + chs[index] <- handler(ctx, ins) + }(i, instance) + } + + for _, ch := range chs { + resp := <-ch + responses.Collect(resp) + } + + return api.FormatBatchWriteResponse(responses) +} + +// wrapper instance store response +func wrapperInstanceStoreResponse(instance *api.Instance, err error) *api.Response { + resp := storeError2Response(err) + if resp == nil { + return nil + } + + resp.Instance = instance + return resp +} + +// 生成instance的记录entry +func instanceRecordEntry(ctx context.Context, service *model.Service, ins *model.Instance, + opt model.OperationType) *model.RecordEntry { + if service == nil || ins == nil { + return nil + } + entry := &model.RecordEntry{ + ResourceType: model.RInstance, + OperationType: opt, + Namespace: service.Namespace, + Service: service.Name, + Operator: ParseOperator(ctx), + CreateTime: time.Now(), + } + if opt == model.OCreate || opt == model.OUpdate { + entry.Context = fmt.Sprintf("host:%s,port:%d,weight:%d,healthy:%v,isolate:%v,priority:%d,meta:%+v", + ins.Host(), ins.Port(), ins.Weight(), ins.Healthy(), ins.Isolate(), + ins.Priority(), ins.Metadata()) + } else if opt == model.OUpdateIsolate { + entry.Context = fmt.Sprintf("host:%s,port=%d,isolate:%v", ins.Host(), ins.Port(), ins.Isolate()) + } else { + entry.Context = fmt.Sprintf("host:%s,port:%d", ins.Host(), ins.Port()) + } + return entry +} + +// 检查DB中service表对应的入参字段合法性 +func CheckDbInstanceFieldLen(req *api.Instance) (*api.Response, bool) { + if err := CheckDbStrFieldLen(req.GetService(), MaxDbServiceNameLength); err != nil { + return api.NewInstanceResponse(api.InvalidServiceName, req), true + } + if err := CheckDbStrFieldLen(req.GetNamespace(), MaxDbServiceNamespaceLength); err != nil { + return api.NewInstanceResponse(api.InvalidNamespaceName, req), true + } + if err := CheckDbStrFieldLen(req.GetHost(), MaxDbInsHostLength); err != nil { + return api.NewInstanceResponse(api.InvalidInstanceHost, req), true + } + if err := CheckDbStrFieldLen(req.GetProtocol(), MaxDbInsProtocolLength); err != nil { + return api.NewInstanceResponse(api.InvalidInstanceProtocol, req), true + } + if err := CheckDbStrFieldLen(req.GetVersion(), MaxDbInsVersionLength); err != nil { + return api.NewInstanceResponse(api.InvalidInstanceVersion, req), true + } + if err := CheckDbStrFieldLen(req.GetLogicSet(), MaxDbInsLogicSetLength); err != nil { + return api.NewInstanceResponse(api.InvalidInstanceLogicSet, req), true + } + if err := CheckDbMetaDataFieldLen(req.GetMetadata()); err != nil { + return api.NewInstanceResponse(api.InvalidMetadata, req), true + } + if req.GetPort().GetValue() > 65535 { + return api.NewInstanceResponse(api.InvalidInstancePort, req), true + } + + if req.GetWeight().GetValue() > 65535 { + return api.NewInstanceResponse(api.InvalidParameter, req), true + } + return nil, false +} diff --git a/naming/l5service.go b/naming/l5service.go new file mode 100644 index 000000000..3338c69b6 --- /dev/null +++ b/naming/l5service.go @@ -0,0 +1,621 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package naming + +import ( + "context" + "errors" + "fmt" + "github.com/golang/protobuf/proto" + "github.com/polarismesh/polaris-server/common/api/l5" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/common/utils" + "strconv" + "strings" + "sync/atomic" +) + +var ( + Namespace2SidLayoutID = map[string]uint32{ + "Production": 1, + "Development": 2, + "Pre-release": 3, + "Test": 4, + "Polaris": 5, + "default": 6, + } + + SidLayoutID2Namespace = map[uint32]string{ + 1: "Production", + 2: "Development", + 3: "Pre-release", + 4: "Test", + 5: "Polaris", + 6: "default", + } +) + +// 记录l5service发现中的一些状态 +type l5service struct { + discoverRevision string + discoverClusterCount uint32 +} + +// 根据sid获取路由信息 +// 老函数: +// Stat::instance()->inc_sync_req_cnt(); +// 保存client的IP,该函数只是存储到本地的缓存中 +// Stat::instance()->add_agent(sbac.agent_ip()); +func (s *Server) SyncByAgentCmd(ctx context.Context, sbac *l5.Cl5SyncByAgentCmd) ( + *l5.Cl5SyncByAgentAckCmd, error) { + clientIP := sbac.GetAgentIp() + optList := sbac.GetOptList().GetOpt() + + routes := s.getRoutes(clientIP, optList) + modIDList, callees, sidConfigs := s.getCallees(routes) + policys, sections := s.getPolicysAndSections(modIDList) + + sbaac := &l5.Cl5SyncByAgentAckCmd{ + AgentIp: proto.Int32(sbac.GetAgentIp()), + SyncFlow: proto.Int32(sbac.GetSyncFlow() + 1), + } + ipConfigs := make(map[uint32]*model.Location) // 所有的被调IP+主调IP + if len(callees) != 0 { + serverList := &l5.Cl5ServList{ + Serv: make([]*l5.Cl5ServObj, 0, len(callees)), + } + for _, entry := range callees { + server := &l5.Cl5ServObj{ + ModId: proto.Int32(int32(entry.ModID)), + CmdId: proto.Int32(int32(entry.CmdID)), + Ip: proto.Int32(int32(entry.IP)), + Port: proto.Int32(int32(entry.Port)), + Weight: proto.Int32(int32(entry.Weight)), + } + serverList.Serv = append(serverList.Serv, server) + ipConfigs[entry.IP] = entry.Location // 填充ipConfigs信息 + } + sbaac.ServList = serverList + } + + if len(policys) != 0 { + routeList := &l5.Cl5RuleList{ + Poly: make([]*l5.Cl5PolyObj, 0, len(policys)), + Sect: make([]*l5.Cl5SectObj, 0, len(sections)), + } + for _, entry := range policys { + obj := &l5.Cl5PolyObj{ + ModId: proto.Int32(int32(entry.ModID)), + Div: proto.Int32(int32(entry.Div)), + Mod: proto.Int32(int32(entry.Mod)), + } + routeList.Poly = append(routeList.Poly, obj) + } + for _, entry := range sections { + obj := &l5.Cl5SectObj{ + ModId: proto.Int32(int32(entry.ModID)), + From: proto.Int32(int32(entry.From)), + To: proto.Int32(int32(entry.To)), + CmdId: proto.Int32(int32(entry.Xid)), + } + routeList.Sect = append(routeList.Sect, obj) + } + sbaac.RuleList = routeList + } + + // 保持和cl5源码一致,agent的地域信息,如果找不到,则不加入到ipConfigs中 + if loc := s.getLocation(ParseIPInt2Str(uint32(sbac.GetAgentIp()))); loc != nil { + ipConfigs[uint32(sbac.GetAgentIp())] = loc + } + if len(ipConfigs) != 0 { + ipConfigList := &l5.Cl5IpcList{ + Ipc: make([]*l5.Cl5IpcObj, 0, len(ipConfigs)), + } + for key, entry := range ipConfigs { + obj := &l5.Cl5IpcObj{ + Ip: proto.Int32(int32(key)), + AreaId: proto.Int32(int32(entry.RegionID)), + CityId: proto.Int32(int32(entry.ZoneID)), + IdcId: proto.Int32(int32(entry.CampusID)), + } + ipConfigList.Ipc = append(ipConfigList.Ipc, obj) + } + sbaac.IpcList = ipConfigList + } + + sbaac.SidList = CreateCl5SidList(sidConfigs) + sbaac.L5SvrList = s.getCl5DiscoverList(ctx, uint32(clientIP)) + return sbaac, nil +} + +// get routes +func (s *Server) getRoutes(clientIP int32, optList []*l5.Cl5OptObj) []*model.Route { + cl5Cache := s.caches.CL5() + routes := cl5Cache.GetRouteByIP(uint32(clientIP)) + if routes == nil { + routes = make([]*model.Route, 0) + } + for _, entry := range optList { + modID := entry.GetModId() + cmdID := entry.GetCmdId() + if ok := cl5Cache.CheckRouteExisted(uint32(clientIP), uint32(modID), uint32(cmdID)); !ok { + route := &model.Route{ + IP: uint32(clientIP), + ModID: uint32(entry.GetModId()), + CmdID: uint32(entry.GetCmdId()), + SetID: "NOSET", + } + routes = append(routes, route) + // Stat::instance()->add_route(route.ip,route.modId,route.cmdId); TODO + } + } + + return routes +} + +// get callee +func (s *Server) getCallees(routes []*model.Route) (map[uint32]bool, []*model.Callee, []*model.SidConfig) { + modIDList := make(map[uint32]bool) + var callees []*model.Callee + var sidConfigs []*model.SidConfig + for _, entry := range routes { + servers := s.getCalleeByRoute(entry) // 返回nil代表没有找到任何实例 + if servers == nil { + log.Warnf("[Cl5] can not found the instances for sid(%d:%d)", entry.ModID, entry.CmdID) + // Stat::instance()->add_lost_route(sbac.agent_ip(),vt_route[i].modId,vt_route[i].cmdId); TODO + continue + } + if len(servers) != 0 { // 不为nil,但是数组长度为0,意味着实例的权重不符合规则 + callees = append(callees, servers...) + } + + modIDList[entry.ModID] = true + if sidConfig := s.getSidConfig(entry.ModID, entry.CmdID); sidConfig != nil { + sidConfigs = append(sidConfigs, sidConfig) + } + } + + return modIDList, callees, sidConfigs +} + +// get policy and section +func (s *Server) getPolicysAndSections(modIDList map[uint32]bool) ([]*model.Policy, []*model.Section) { + cl5Cache := s.caches.CL5() + var policys []*model.Policy + var sections []*model.Section + for modID := range modIDList { + if policy := cl5Cache.GetPolicy(modID); policy != nil { + policys = append(policys, policy) + } + if secs := cl5Cache.GetSection(modID); len(secs) != 0 { + sections = append(sections, secs...) + } + } + + return policys, sections +} + +// 根据名字获取sid信息 +func (s *Server) RegisterByNameCmd(rbnc *l5.Cl5RegisterByNameCmd) (*l5.Cl5RegisterByNameAckCmd, error) { + // Stat::instance()->inc_regist_req_cnt(); TODO + + nameList := rbnc.GetNameList() + sidConfigs := make([]*model.SidConfig, 0) + for _, name := range nameList.GetName() { + if sidConfig := s.getSidConfigByName(name); sidConfig != nil { + sidConfigs = append(sidConfigs, sidConfig) + } + } + + rbnac := &l5.Cl5RegisterByNameAckCmd{ + CallerIp: proto.Int32(rbnc.GetCallerIp()), + } + + rbnac.SidList = CreateCl5SidList(sidConfigs) + return rbnac, nil +} + +// 根据访问关系获取所有符合的被调信息 +func (s *Server) getCalleeByRoute(route *model.Route) []*model.Callee { + out := make([]*model.Callee, 0) + if route == nil { + return nil + } + + sidStr := utils.MarshalModCmd(route.ModID, route.CmdID) + // 根据sid找到所述命名空间 + namespace := ComputeNamespace(route.ModID, route.CmdID) + // 根据sid找到polaris服务,这里是源服务 + service := s.getServiceCache(sidStr, namespace) + if service == nil { + return nil + } + s.RecordDiscoverStatis(service.Name, service.Namespace) + + hasInstance := false + _ = s.caches.Instance().IteratorInstancesWithService(service.ID, + func(key string, entry *model.Instance) (b bool, e error) { + hasInstance = true + // 如果不存在internal-cl5-setId,则默认都是NOSET,适用于别名场景 + setValue := "NOSET" + metadata := entry.Metadata() + if val, ok := metadata["internal-cl5-setId"]; ok { + setValue = val + } + + // 与route的setID匹配,那么直接返回instance.weight + weight := entry.Weight() + found := false + if setValue == route.SetID { + found = true + } else if !strings.Contains(setValue, route.SetID) { + found = false + } else { + var weights []uint32 + if val, ok := metadata["internal-cl5-weight"]; ok { + weights = ParseWeight(val) + } + setIDs := ParseSetID(setValue) + for i, setID := range setIDs { + if setID == route.SetID { + found = true + if weights != nil && i < len(weights) { + weight = weights[i] + } + break + } + } + } + + // 该Set无被调或者被调的权重为0,则忽略 + if !found || weight == 0 { + return true, nil + } + + // 转换ipStr to int + ip := ParseIPStr2IntV2(entry.Host()) + + callee := &model.Callee{ + ModID: route.ModID, + CmdID: route.CmdID, + IP: ip, + Port: entry.Port(), + Weight: weight, + // TODO 没有设置 setID,cl5源码也是没有设置的 + } + // s.getLocation(entry.Host), // ip的地域信息,统一来源于cmdb插件的数据 + if loc := s.getLocation(entry.Host()); loc != nil { + callee.Location = loc + } else { + // 如果cmdb中找不到数据,则默认地域ID都为0,即默认结构体 + callee.Location = &model.Location{} + } + out = append(out, callee) + return true, nil + }) + + if hasInstance == false { + return nil + } + + return out +} + +// 根据sid读取sidConfig的配置信息 +// 注意,sid--> reference,通过索引服务才能拿到真实的数据 +func (s *Server) getSidConfig(modID uint32, cmdID uint32) *model.SidConfig { + sid := &model.Sid{ModID: modID, CmdID: cmdID} + sidStr := utils.MarshalSid(sid) + + // 先获取一下namespace + namespace := ComputeNamespace(modID, cmdID) + sidService := s.caches.Service().GetServiceByName(sidStr, namespace) + if sidService == nil { + return nil + } + + sidConfig := s.getRealSidConfigMeta(sidService) + if sidConfig == nil { + return nil + } + + sidConfig.ModID = modID + sidConfig.CmdID = cmdID + + return sidConfig +} + +// 根据名字找到sidConfig +// 注意:通过cache,根据cl5Name,找到对应的sid +func (s *Server) getSidConfigByName(name string) *model.SidConfig { + nameService := s.caches.Service().GetServiceByCl5Name(name) + if nameService == nil { + return nil + } + + sidConfig := s.getRealSidConfigMeta(nameService) + if sidConfig == nil { + return nil + } + + sidMeta, ok := nameService.Meta["internal-cl5-sid"] + if !ok { + log.Errorf("[Server] not found name(%s) sid", name) + return nil + } + + sid, err := utils.UnmarshalSid(sidMeta) + if err != nil { + log.Errorf("[Server] unmarshal sid(%s) err: %s", sidMeta, err.Error()) + return nil + } + + sidConfig.ModID = sid.ModID + sidConfig.CmdID = sid.CmdID + return sidConfig +} + +// 只返回服务名+policy属性 +func (s *Server) getRealSidConfigMeta(service *model.Service) *model.SidConfig { + if service == nil { + return nil + } + + realService := service + // 找一下,是否存在索引服务(别名服务) + // 如果存在索引服务,读取索引服务的属性 + if service.IsAlias() { + if referService := s.caches.Service().GetServiceByID(service.Reference); referService != nil { + realService = referService + } + } + + out := &model.SidConfig{ + Name: "", + Policy: 0, + } + if nameMeta, ok := realService.Meta["internal-cl5-name"]; ok { + out.Name = nameMeta + } + if policyMeta, ok := realService.Meta["internal-enable-nearby"]; ok { + if policyMeta == "true" { + out.Policy = 1 + } + } + + return out +} + +// 获取cl5.discover +func (s *Server) getCl5DiscoverList(ctx context.Context, clientIP uint32) *l5.Cl5L5SvrList { + clusterName, _ := ctx.Value(utils.Cl5ServerCluster{}).(string) + if clusterName == "" { + log.Warnf("[Cl5] get server cluster name is empty") + return nil + } + protocol, _ := ctx.Value(utils.Cl5ServerProtocol{}).(string) + + service := s.getCl5DiscoverService(clusterName, clientIP) + if service == nil { + log.Errorf("[Cl5] not found server cluster service(%s)", clusterName) + return nil + } + instances := s.caches.Instance().GetInstancesByServiceID(service.ID) + if len(instances) == 0 { + log.Errorf("[Cl5] not found any instances for the service(%s, %s)", + clusterName, "Polaris") + return nil + } + + var out l5.Cl5L5SvrList + out.Ip = make([]int32, 0, len(instances)) + for _, entry := range instances { + // 获取同协议的数据 + if entry.Protocol() != protocol { + continue + } + // 过滤掉不健康或者隔离状态的server + if entry.Healthy() == false || entry.Isolate() == true { + continue + } + ip := ParseIPStr2IntV2(entry.Host()) + out.Ip = append(out.Ip, int32(ip)) + } + // 如果没有任何数据,那直接返回空,使用agent配置的IPlist + if len(out.GetIp()) == 0 { + log.Errorf("[Cl5] get cl5 cluster(%s) instances count 0", service.Name) + return nil + } + + return &out +} + +// 根据集群名获取对应的服务 +func (s *Server) getCl5DiscoverService(clusterName string, clientIP uint32) *model.Service { + service := s.getServiceCache(clusterName, "Polaris") + if service == nil { + log.Errorf("[Cl5] not found server cluster service(%s)", clusterName) + return nil + } + + // 根据service的metadata判断,有多少个子集群 + clusterCount := uint32(0) + if service.Revision == s.l5service.discoverRevision { + clusterCount = atomic.LoadUint32(&s.l5service.discoverClusterCount) + } else { + if meta, ok := service.Meta["internal-cluster-count"]; ok { + count, err := strconv.Atoi(meta) + if err != nil { + log.Errorf("[Cl5] get service count , parse err: %s", err.Error()) + } else { + clusterCount = uint32(count) + s.l5service.discoverRevision = service.Revision + atomic.StoreUint32(&s.l5service.discoverClusterCount, clusterCount) + } + } + } + + // 如果集群数为0,那么返回埋点的集群 + if clusterCount == 0 { + return service + } + + subIndex := clientIP%uint32(clusterCount) + 1 + subClusterName := fmt.Sprintf("%s.%d", clusterName, subIndex) + //log.Infof("[Cl5] ip(%d), clusterCount(%d), name(%s)", clientIP, clusterCount, subClusterName) // TODO + subService := s.getServiceCache(subClusterName, "Polaris") + if subService == nil { + log.Errorf("[Cl5] not found server cluster for ip(%d), cluster count(%d), cluster name(%s)", + clientIP, clusterCount, subClusterName) + return service + } + + return subService +} + +// 构造sidConfigs +func CreateCl5SidList(sidConfigs []*model.SidConfig) *l5.Cl5SidList { + if len(sidConfigs) == 0 { + return nil + } + + sidList := &l5.Cl5SidList{ + Sid: make([]*l5.Cl5SidObj, 0, len(sidConfigs)), + } + for _, entry := range sidConfigs { + obj := &l5.Cl5SidObj{ + ModId: proto.Int32(int32(entry.ModID)), + CmdId: proto.Int32(int32(entry.CmdID)), + Name: proto.String(entry.Name), + Policy: proto.Int32(int32(entry.Policy)), + } + sidList.Sid = append(sidList.Sid, obj) + } + + return sidList +} + +// 解析metadata保存的setID字符串 +func ParseSetID(str string) []string { + if str == "" { + return nil + } + + out := strings.Split(str, ",") + return out +} + +// 解析metadata保存的weight字符串 +func ParseWeight(str string) []uint32 { + if str == "" { + return nil + } + + items := strings.Split(str, ",") + if len(items) == 0 { + return nil + } + out := make([]uint32, 0, len(items)) + for _, item := range items { + data, err := strconv.ParseUint(item, 10, 32) + if err != nil { + log.Errorf("[L5Service] parse uint (%s) err: %s", item, err.Error()) + return nil + } + + out = append(out, uint32(data)) + } + + return out +} + +// 字符串IP转为uint32 +// 转换失败的,需要明确错误 +func ParseIPStr2Int(ip string) (uint32, error) { + ips := strings.Split(ip, ".") + if len(ips) != 4 { + log.Errorf("[l5Service] ip str(%s) is invalid", ip) + return 0, errors.New("ip string is invalid") + } + + out := uint32(0) + for i := 0; i < 4; i++ { + tmp, err := strconv.ParseUint(ips[i], 10, 64) + if err != nil { + log.Errorf("[L5Service] ip str(%s) to int is err: %s", ip, err.Error()) + return 0, err + } + + out = out | (uint32(tmp) << uint(i*8)) + } + + return out, nil +} + +// 字符串IP转为Int,V2 +func ParseIPStr2IntV2(ip string) uint32 { + item := 0 + var sum uint32 + var index uint + for i := 0; i < len(ip); i++ { + if ip[i] == '.' { + sum = sum | (uint32(item) << (index * 8)) + item = 0 + index++ + } else { + item = item*10 + int(ip[i]) - int('0') + } + } + + sum = sum | (uint32(item) << (index * 8)) + return sum +} + +// uint32的IP转换为字符串型 +func ParseIPInt2Str(ip uint32) string { + ipStr := make([]uint32, 4) + for i := 0; i < 4; i++ { + ipStr[i] = (ip >> uint(i*8)) & 255 + } + str := fmt.Sprintf("%d.%d.%d.%d", ipStr[0], ipStr[1], ipStr[2], ipStr[3]) + return str +} + +// 根据SID分析,返回其对应的namespace +func ComputeNamespace(modID uint32, cmdID uint32) string { + // 为了兼容老的sid,只对新的别名sid才生效 + // 老的sid都属于生产环境的 + // 3000001是新的moduleID的开始值 + if moduleID := modID >> 6; moduleID < 3000001 { + return DefaultNamespace + } + + layoutID := modID & 63 // 63 -> 111111 + namespace, ok := SidLayoutID2Namespace[layoutID] + if !ok { + // 找不到命名空间的,全部返回默认的,也就是Production + log.Warnf("sid(%d:%d) found the layoutID is(%d), not match the namespace list", + modID, cmdID, layoutID) + return DefaultNamespace + } + + log.Infof("Sid(%d:%d) layoutID(%d), the namespace is: %s", + modID, cmdID, layoutID, namespace) + return namespace +} diff --git a/naming/namespace.go b/naming/namespace.go new file mode 100644 index 000000000..2bbdd126d --- /dev/null +++ b/naming/namespace.go @@ -0,0 +1,488 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package naming + +import ( + "context" + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/common/utils" + "go.uber.org/zap" + "time" +) + +/** + * @brief 批量创建命名空间 + */ +func (s *Server) CreateNamespaces(ctx context.Context, req []*api.Namespace) *api.BatchWriteResponse { + if checkError := checkBatchNamespace(req); checkError != nil { + return checkError + } + + responses := api.NewBatchWriteResponse(api.ExecuteSuccess) + for _, namespace := range req { + response := s.CreateNamespace(ctx, namespace) + responses.Collect(response) + } + + return responses +} + +/** + * @brief 创建单个命名空间 + */ +func (s *Server) CreateNamespace(ctx context.Context, req *api.Namespace) *api.Response { + requestID, _ := ctx.Value(utils.StringContext("request-id")).(string) + + // 参数检查 + if checkError := checkCreateNamespace(req); checkError != nil { + return checkError + } + + namespaceName := req.GetName().GetValue() + + // 检查是否存在 + namespace, err := s.storage.GetNamespace(namespaceName) + if err != nil { + log.Error(err.Error(), zap.String("request-id", requestID)) + return api.NewNamespaceResponse(api.StoreLayerException, req) + } + if namespace != nil { + return api.NewNamespaceResponse(api.ExistedResource, req) + } + + // + data := s.createNamespaceModel(req) + + // 存储层操作 + if err := s.storage.AddNamespace(data); err != nil { + log.Error(err.Error(), zap.String("request-id", requestID)) + return api.NewNamespaceResponse(api.StoreLayerException, req) + } + + msg := fmt.Sprintf("create namepsace: name=%v", namespaceName) + log.Info(msg, zap.String("request-id", requestID)) + s.RecordHistory(namespaceRecordEntry(ctx, req, model.OCreate)) + + out := &api.Namespace{ + Name: req.GetName(), + Token: utils.NewStringValue(data.Token), + } + + return api.NewNamespaceResponse(api.ExecuteSuccess, out) +} + +/** + * @brief 创建存储层命名空间模型 + */ +func (s *Server) createNamespaceModel(req *api.Namespace) *model.Namespace { + namespace := &model.Namespace{ + Name: req.GetName().GetValue(), + Comment: req.GetComment().GetValue(), + Owner: req.GetOwners().GetValue(), + Token: NewUUID(), + } + + return namespace +} + +/** + * @brief 批量删除命名空间 + */ +func (s *Server) DeleteNamespaces(ctx context.Context, req []*api.Namespace) *api.BatchWriteResponse { + if checkError := checkBatchNamespace(req); checkError != nil { + return checkError + } + + responses := api.NewBatchWriteResponse(api.ExecuteSuccess) + for _, namespace := range req { + response := s.DeleteNamespace(ctx, namespace) + responses.Collect(response) + } + + return responses +} + +/** + * @brief 删除单个命名空间 + */ +func (s *Server) DeleteNamespace(ctx context.Context, req *api.Namespace) *api.Response { + requestID, _ := ctx.Value(utils.StringContext("request-id")).(string) + + // 参数检查 + if checkError := checkReviseNamespace(ctx, req); checkError != nil { + return checkError + } + + tx, err := s.storage.CreateTransaction() + if err != nil { + log.Error(err.Error(), ZapRequestID(requestID)) + return api.NewNamespaceResponse(api.StoreLayerException, req) + } + defer func() { _ = tx.Commit() }() + + // 检查是否存在 + namespace, err := tx.LockNamespace(req.GetName().GetValue()) + if err != nil { + log.Error(err.Error(), zap.String("request-id", requestID)) + return api.NewNamespaceResponse(api.StoreLayerException, req) + } + if namespace == nil { + return api.NewNamespaceResponse(api.ExecuteSuccess, req) + } + + // 鉴权 + if ok := s.authority.VerifyNamespace(namespace.Token, parseNamespaceToken(ctx, req)); !ok { + return api.NewNamespaceResponse(api.Unauthorized, req) + } + + // 判断属于该命名空间的服务是否都已经被删除 + total, err := s.getServicesCountWithNamespace(namespace.Name) + if err != nil { + log.Error("get services count with namespace err", + zap.String("request-id", requestID), + zap.String("err", err.Error())) + return api.NewNamespaceResponse(api.StoreLayerException, req) + } + if total != 0 { + log.Error("the removed namespace has remain services", zap.String("request-id", requestID)) + return api.NewNamespaceResponse(api.NamespaceExistedServices, req) + } + + // 判断属于该命名空间的熔断规则是否都已经被删除 + total, err = s.getCircuitBreakerCountWithNamespace(namespace.Name) + if err != nil { + log.Error("get circuitBreakers count with namespace err", + zap.String("request-id", requestID), + zap.String("err", err.Error())) + return api.NewNamespaceResponse(api.StoreLayerException, req) + } + if total != 0 { + log.Error("the removed namespace has remain circuitBreakers", zap.String("request-id", requestID)) + return api.NewNamespaceResponse(api.NamespaceExistedCircuitBreakers, req) + } + + // 存储层操作 + if err := tx.DeleteNamespace(namespace.Name); err != nil { + log.Error(err.Error(), zap.String("request-id", requestID)) + return api.NewNamespaceResponse(api.StoreLayerException, req) + } + + msg := fmt.Sprintf("delete namepsace: name=%v", namespace.Name) + log.Info(msg, zap.String("request-id", requestID)) + s.RecordHistory(namespaceRecordEntry(ctx, req, model.ODelete)) + + return api.NewNamespaceResponse(api.ExecuteSuccess, req) +} + +/** + * @brief 批量修改命名空间 + */ +func (s *Server) UpdateNamespaces(ctx context.Context, req []*api.Namespace) *api.BatchWriteResponse { + if checkError := checkBatchNamespace(req); checkError != nil { + return checkError + } + + responses := api.NewBatchWriteResponse(api.ExecuteSuccess) + for _, namespace := range req { + response := s.UpdateNamespace(ctx, namespace) + responses.Collect(response) + } + + return responses +} + +/** + * @brief 修改单个命名空间 + */ +func (s *Server) UpdateNamespace(ctx context.Context, req *api.Namespace) *api.Response { + // 参数检查 + if resp := checkReviseNamespace(ctx, req); resp != nil { + return resp + } + + // 权限校验 + namespace, resp := s.checkNamespaceAuthority(ctx, req) + if resp != nil { + return resp + } + + rid := ParseRequestID(ctx) + // 修改 + s.updateNamespaceAttribute(req, namespace) + + // 存储层操作 + if err := s.storage.UpdateNamespace(namespace); err != nil { + log.Error(err.Error(), zap.String("request-id", rid)) + return api.NewNamespaceResponse(api.StoreLayerException, req) + } + + msg := fmt.Sprintf("update namepsace: name=%v", namespace.Name) + log.Info(msg, zap.String("request-id", rid)) + s.RecordHistory(namespaceRecordEntry(ctx, req, model.OUpdate)) + + return api.NewNamespaceResponse(api.ExecuteSuccess, req) +} + +/** + * @brief 修改命名空间属性 + */ +func (s *Server) updateNamespaceAttribute(req *api.Namespace, namespace *model.Namespace) { + if req.GetComment() != nil { + namespace.Comment = req.GetComment().GetValue() + } + + if req.GetOwners() != nil { + namespace.Owner = req.GetOwners().GetValue() + } +} + +/** + * @brief 更新命名空间token + */ +func (s *Server) UpdateNamespaceToken(ctx context.Context, req *api.Namespace) *api.Response { + if resp := checkReviseNamespace(ctx, req); resp != nil { + return resp + } + namespace, resp := s.checkNamespaceAuthority(ctx, req) + if resp != nil { + return resp + } + + rid := ParseRequestID(ctx) + // 生成token + token := NewUUID() + + // 存储层操作 + if err := s.storage.UpdateNamespaceToken(namespace.Name, token); err != nil { + log.Error(err.Error(), zap.String("request-id", rid)) + return api.NewNamespaceResponse(api.StoreLayerException, req) + } + + msg := fmt.Sprintf("update namepsace token: name=%v", namespace.Name) + log.Info(msg, zap.String("request-id", rid)) + s.RecordHistory(namespaceRecordEntry(ctx, req, model.OUpdateToken)) + + out := &api.Namespace{ + Name: req.GetName(), + Token: utils.NewStringValue(token), + } + + return api.NewNamespaceResponse(api.ExecuteSuccess, out) +} + +/** + * @brief 查询命名空间 + */ +func (s *Server) GetNamespaces(query map[string][]string) *api.BatchQueryResponse { + filter, offset, limit, checkError := checkGetNamespace(query) + if checkError != nil { + return checkError + } + + namespaces, amount, err := s.storage.GetNamespaces(filter, offset, limit) + if err != nil { + return api.NewBatchQueryResponse(api.StoreLayerException) + } + + out := api.NewBatchQueryResponse(api.ExecuteSuccess) + out.Amount = utils.NewUInt32Value(amount) + out.Size = utils.NewUInt32Value(uint32(len(namespaces))) + for _, namespace := range namespaces { + out.AddNamespace(&api.Namespace{ + Name: utils.NewStringValue(namespace.Name), + Comment: utils.NewStringValue(namespace.Comment), + Owners: utils.NewStringValue(namespace.Owner), + Ctime: utils.NewStringValue(time2String(namespace.CreateTime)), + Mtime: utils.NewStringValue(time2String(namespace.ModifyTime)), + }) + } + + return out +} + +// 获取命名空间的token +func (s *Server) GetNamespaceToken(ctx context.Context, req *api.Namespace) *api.Response { + if resp := checkReviseNamespace(ctx, req); resp != nil { + return resp + } + + namespace, resp := s.checkNamespaceAuthority(ctx, req) + if resp != nil { + return resp + } + + //s.RecordHistory(namespaceRecordEntry(ctx, req, model.OGetToken)) + // 构造返回数据 + out := &api.Namespace{ + Name: req.GetName(), + Token: utils.NewStringValue(namespace.Token), + } + return api.NewNamespaceResponse(api.ExecuteSuccess, out) +} + +// 根据命名空间查询服务总数 +func (s *Server) getServicesCountWithNamespace(namespace string) (uint32, error) { + filter := map[string]string{"namespace": namespace} + total, _, err := s.storage.GetServices(filter, nil, nil, 0, 1) + if err != nil { + return 0, err + } + return total, nil +} + +// 根据命名空间查询熔断规则数量 +func (s *Server) getCircuitBreakerCountWithNamespace(namespace string) (uint32, error) { + filter := map[string]string{"namespace": namespace} + details, err := s.storage.ListMasterCircuitBreakers(filter, 0, 1) + if err != nil { + return 0, err + } + return details.Total, nil +} + +// 检查namespace的权限,并且返回namespace +func (s *Server) checkNamespaceAuthority(ctx context.Context, req *api.Namespace) (*model.Namespace, *api.Response) { + rid := ParseRequestID(ctx) + namespaceName := req.GetName().GetValue() + namespaceToken := parseNamespaceToken(ctx, req) + + // 检查是否存在 + namespace, err := s.storage.GetNamespace(namespaceName) + if err != nil { + log.Error(err.Error(), ZapRequestID(rid)) + return nil, api.NewNamespaceResponse(api.StoreLayerException, req) + } + if namespace == nil { + return nil, api.NewNamespaceResponse(api.NotFoundResource, req) + } + + // 鉴权 + if ok := s.authority.VerifyNamespace(namespace.Token, namespaceToken); !ok { + return nil, api.NewNamespaceResponse(api.Unauthorized, req) + } + + return namespace, nil +} + +/* + * @brief 检查批量请求 + */ +func checkBatchNamespace(req []*api.Namespace) *api.BatchWriteResponse { + if len(req) == 0 { + return api.NewBatchWriteResponse(api.EmptyRequest) + } + + if len(req) > MaxBatchSize { + return api.NewBatchWriteResponse(api.BatchSizeOverLimit) + } + + return nil +} + +/* + * @brief 检查创建命名空间请求参数 + */ +func checkCreateNamespace(req *api.Namespace) *api.Response { + if req == nil { + return api.NewNamespaceResponse(api.EmptyRequest, req) + } + + if err := checkResourceName(req.GetName()); err != nil { + return api.NewNamespaceResponse(api.InvalidNamespaceName, req) + } + + if err := checkResourceOwners(req.GetOwners()); err != nil { + return api.NewNamespaceResponse(api.InvalidNamespaceOwners, req) + } + + return nil +} + +/* + * @brief 检查删除/修改命名空间请求参数 + */ +func checkReviseNamespace(ctx context.Context, req *api.Namespace) *api.Response { + if req == nil { + return api.NewNamespaceResponse(api.EmptyRequest, req) + } + + if err := checkResourceName(req.GetName()); err != nil { + return api.NewNamespaceResponse(api.InvalidNamespaceName, req) + } + + if token := parseNamespaceToken(ctx, req); token == "" { + return api.NewNamespaceResponse(api.InvalidNamespaceToken, req) + } + + return nil +} + +/* + * @brief 检查查询命名空间请求参数 + */ +func checkGetNamespace(query map[string][]string) (map[string][]string, int, int, *api.BatchQueryResponse) { + filter := make(map[string][]string) + + if value := query["name"]; len(value) > 0 { + filter["name"] = value + } + + if value := query["owner"]; len(value) > 0 { + filter["owner"] = value + } + + offset, err := checkQueryOffset(query["offset"]) + if err != nil { + return nil, 0, 0, api.NewBatchQueryResponse(api.InvalidParameter) + } + + limit, err := checkQueryLimit(query["limit"]) + if err != nil { + return nil, 0, 0, api.NewBatchQueryResponse(api.InvalidParameter) + } + + return filter, offset, limit, nil +} + +// 返回命名空间请求的token +// 默认先从req中获取,不存在,则使用header的 +func parseNamespaceToken(ctx context.Context, req *api.Namespace) string { + if reqToken := req.GetToken().GetValue(); reqToken != "" { + return reqToken + } + + if headerToken := ParseToken(ctx); headerToken != "" { + return headerToken + } + + return "" +} + +// 生成命名空间的记录entry +func namespaceRecordEntry(ctx context.Context, req *api.Namespace, opt model.OperationType) *model.RecordEntry { + return &model.RecordEntry{ + ResourceType: model.RNamespace, + OperationType: opt, + Namespace: req.GetName().GetValue(), + Operator: ParseOperator(ctx), + CreateTime: time.Now(), + } +} diff --git a/naming/platform.go b/naming/platform.go new file mode 100644 index 000000000..b775dfdd8 --- /dev/null +++ b/naming/platform.go @@ -0,0 +1,573 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package naming + +import ( + "context" + "errors" + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/common/utils" + "github.com/golang/protobuf/ptypes/wrappers" + "go.uber.org/zap" + "regexp" + "unicode/utf8" +) + +var ( + platformFilterAttributes = map[string]bool{ + "id": true, + "name": true, + "owner": true, + "department": true, + "offset": true, + "limit": true, + } +) + +/** + * @brief 批量创建平台信息 + */ +func (s *Server) CreatePlatforms(ctx context.Context, req []*api.Platform) *api.BatchWriteResponse { + if checkErr := checkBatchPlatform(req); checkErr != nil { + return checkErr + } + + responses := api.NewBatchWriteResponse(api.ExecuteSuccess) + for _, platform := range req { + response := s.CreatePlatform(ctx, platform) + responses.Collect(response) + } + + return api.FormatBatchWriteResponse(responses) +} + +/** + * @brief 创建单个平台 + */ +func (s *Server) CreatePlatform(ctx context.Context, req *api.Platform) *api.Response { + requestID := ParseRequestID(ctx) + + // 参数检查 + if err := checkPlatformParams(req); err != nil { + return err + } + + // 检查平台信息是否存在 + platform, err := s.storage.GetPlatformById(req.GetId().GetValue()) + if err != nil { + log.Error(err.Error(), ZapRequestID(requestID)) + return api.NewPlatformResponse(api.StoreLayerException, req) + } + + if platform != nil { + return api.NewPlatformResponse(api.ExistedResource, req) + } + + // 存储层操作 + data := createPlatformModel(req) + if err := s.storage.CreatePlatform(data); err != nil { + log.Error(err.Error(), ZapRequestID(requestID)) + return wrapperPlatformStoreResponse(req, err) + } + + msg := fmt.Sprintf("create platform: id=%s", req.GetId().GetValue()) + log.Info(msg, ZapRequestID(requestID)) + + // todo 打印操作记录 + + // 返回请求结果 + req.Token = utils.NewStringValue(data.Token) + + return api.NewPlatformResponse(api.ExecuteSuccess, req) +} + +/** + * @brief 批量修改平台 + */ +func (s *Server) UpdatePlatforms(ctx context.Context, req []*api.Platform) *api.BatchWriteResponse { + if checkErr := checkBatchPlatform(req); checkErr != nil { + return checkErr + } + + responses := api.NewBatchWriteResponse(api.ExecuteSuccess) + for _, platform := range req { + response := s.UpdatePlatform(ctx, platform) + responses.Collect(response) + } + + return api.FormatBatchWriteResponse(responses) +} + +/** + * @brief 修改平台信息 + */ +func (s *Server) UpdatePlatform(ctx context.Context, req *api.Platform) *api.Response { + requestID := ParseRequestID(ctx) + + // 参数检查 + if err := checkPlatformParams(req); err != nil { + return err + } + // 检查token + if token := parsePlatformToken(ctx, req); token == "" { + return api.NewPlatformResponse(api.InvalidPlatformToken, req) + } + + platform, resp := s.checkRevisePlatform(ctx, req) + if resp != nil { + return resp + } + + // 判断是否需要更新 + needUpdate := s.updatePlatformAttribute(req, platform) + if !needUpdate { + log.Info("update platform data no change, no need update", ZapRequestID(requestID), + zap.String("platform", req.String())) + return api.NewPlatformResponse(api.NoNeedUpdate, req) + } + + // 执行存储层操作 + if err := s.storage.UpdatePlatform(platform); err != nil { + log.Error(err.Error(), ZapRequestID(requestID)) + return wrapperPlatformStoreResponse(req, err) + } + + msg := fmt.Sprintf("update platform: %+v", platform) + log.Info(msg, ZapRequestID(requestID)) + // todo 操作记录 + + return api.NewPlatformResponse(api.ExecuteSuccess, req) +} + +/** + * @brief 批量删除平台信息 + */ +func (s *Server) DeletePlatforms(ctx context.Context, req []*api.Platform) *api.BatchWriteResponse { + if checkErr := checkBatchPlatform(req); checkErr != nil { + return checkErr + } + + responses := api.NewBatchWriteResponse(api.ExecuteSuccess) + for _, platform := range req { + response := s.DeletePlatform(ctx, platform) + responses.Collect(response) + } + + return api.FormatBatchWriteResponse(responses) +} + +/** + * @brief 删除平台信息 + */ +func (s *Server) DeletePlatform(ctx context.Context, req *api.Platform) *api.Response { + requestID := ParseRequestID(ctx) + + // 参数检查 + if err := checkDeletePlatformParams(ctx, req); err != nil { + return err + } + + // 检查平台是否存在并鉴权 + if _, resp := s.checkRevisePlatform(ctx, req); resp != nil { + if resp.GetCode().GetValue() == api.NotFoundPlatform { + return api.NewPlatformResponse(api.ExecuteSuccess, req) + } + return resp + } + + // 执行存储层操作 + if err := s.storage.DeletePlatform(req.GetId().GetValue()); err != nil { + log.Error(err.Error(), ZapRequestID(requestID)) + return wrapperPlatformStoreResponse(req, err) + } + + msg := fmt.Sprintf("delete platform: id=%s", req.GetId().GetValue()) + log.Info(msg, ZapRequestID(requestID)) + + // todo 操作记录 + + return api.NewPlatformResponse(api.ExecuteSuccess, req) +} + +/** + * @brief 查询平台信息 + */ +func (s *Server) GetPlatforms(query map[string]string) *api.BatchQueryResponse { + for key := range query { + if _, ok := platformFilterAttributes[key]; !ok { + log.Errorf("get platforms attribute(%s) is not allowed", key) + return api.NewBatchQueryResponseWithMsg(api.InvalidParameter, key+" is not allowed") + } + } + + // 处理offset和limit + offset, limit, err := ParseOffsetAndLimit(query) + if err != nil { + return api.NewBatchQueryResponseWithMsg(api.InvalidParameter, err.Error()) + } + + total, platforms, err := s.storage.GetPlatforms(query, offset, limit) + if err != nil { + log.Errorf("get platforms store err: %s", err.Error()) + return api.NewBatchQueryResponse(api.StoreLayerException) + } + + resp := api.NewBatchQueryResponse(api.ExecuteSuccess) + resp.Amount = utils.NewUInt32Value(total) + resp.Size = utils.NewUInt32Value(uint32(len(platforms))) + resp.Platforms = platforms2API(platforms) + return resp +} + +/** + * @brief 查询平台Token + */ +func (s *Server) GetPlatformToken(ctx context.Context, req *api.Platform) *api.Response { + // 参数检查 + if err := checkDeletePlatformParams(ctx, req); err != nil { + return err + } + + // 检查平台是否存在并鉴权 + platform, resp := s.checkRevisePlatform(ctx, req) + if resp != nil { + return resp + } + + req.Token = utils.NewStringValue(platform.Token) + return api.NewPlatformResponse(api.ExecuteSuccess, req) +} + +/** + * @brief 检查批量请求 + */ +func checkBatchPlatform(req []*api.Platform) *api.BatchWriteResponse { + if len(req) == 0 { + return api.NewBatchWriteResponse(api.EmptyRequest) + } + + if len(req) > MaxBatchSize { + return api.NewBatchWriteResponse(api.BatchSizeOverLimit) + } + + return nil +} + +/** + * @brief 检查创建/修改平台参数 + */ +func checkPlatformParams(req *api.Platform) *api.Response { + if req == nil { + return api.NewPlatformResponse(api.EmptyRequest, req) + } + + if err := checkPlatformID(req.GetId()); err != nil { + return api.NewPlatformResponseWithMsg(api.InvalidPlatformID, req, err.Error()) + } + + if err := checkPlatformName(req.GetName()); err != nil { + return api.NewPlatformResponseWithMsg(api.InvalidPlatformName, req, err.Error()) + } + + if err := checkPlatformDomain(req.GetDomain()); err != nil { + return api.NewPlatformResponseWithMsg(api.InvalidPlatformDomain, req, err.Error()) + } + + if err := checkPlatformQPS(req.GetQps()); err != nil { + return api.NewPlatformResponseWithMsg(api.InvalidPlatformQPS, req, err.Error()) + } + + if err := checkResourceOwners(req.GetOwner()); err != nil { + return api.NewPlatformResponseWithMsg(api.InvalidPlatformOwner, req, err.Error()) + } + + if err := checkPlatformDepartment(req.GetDepartment()); err != nil { + return api.NewPlatformResponseWithMsg(api.InvalidPlatformDepartment, req, err.Error()) + } + + if err := checkPlatformComment(req.GetComment()); err != nil { + return api.NewPlatformResponseWithMsg(api.InvalidPlatformComment, req, err.Error()) + } + + return nil +} + +/** + * @brief 检查删除平台参数 + */ +func checkDeletePlatformParams(ctx context.Context, req *api.Platform) *api.Response { + if req == nil { + return api.NewPlatformResponse(api.EmptyRequest, req) + } + + if err := checkPlatformID(req.GetId()); err != nil { + return api.NewPlatformResponse(api.InvalidPlatformID, req) + } + + // 检查token + if token := parsePlatformToken(ctx, req); token == "" { + return api.NewPlatformResponse(api.InvalidPlatformToken, req) + } + + return nil +} + +/** + * @brief 修改和删除平台信息的公共检查 + */ +func (s *Server) checkRevisePlatform(ctx context.Context, req *api.Platform) (*model.Platform, *api.Response) { + requestID := ParseRequestID(ctx) + + // 检查平台是否存在 + platform, err := s.storage.GetPlatformById(req.GetId().GetValue()) + if err != nil { + log.Error(err.Error(), ZapRequestID(requestID)) + return nil, api.NewPlatformResponse(api.StoreLayerException, req) + } + if platform == nil { + return nil, api.NewPlatformResponse(api.NotFoundPlatform, req) + } + + // 鉴权 + if ok := s.authority.VerifyPlatform(platform.Token, parsePlatformToken(ctx, req)); !ok { + return nil, api.NewPlatformResponse(api.Unauthorized, req) + } + return platform, nil +} + +/** + * @brief 检查平台ID + */ +func checkPlatformID(id *wrappers.StringValue) error { + if id == nil { + return errors.New("id is nil") + } + + if id.GetValue() == "" { + return errors.New("id is empty") + } + + // 允许0-9 a-z A-Z - . _ + regStr := "^[0-9A-Za-z-._]+$" + ok, err := regexp.MatchString(regStr, id.GetValue()) + if err != nil { + return err + } + if !ok { + return errors.New("platform id contains invalid character") + } + + if utf8.RuneCountInString(id.GetValue()) > MaxPlatformIDLength { + return errors.New("platform id too long") + } + + return nil +} + +/** + * @brief 检查平台Name + */ +func checkPlatformName(name *wrappers.StringValue) error { + if name.GetValue() == "" { + return errors.New("name is empty") + } + + if utf8.RuneCountInString(name.GetValue()) > MaxPlatformNameLength { + return errors.New("name too long") + } + + return nil +} + +/** + * @brief 检查平台域名 + */ +func checkPlatformDomain(domain *wrappers.StringValue) error { + if domain.GetValue() == "" { + return errors.New("domain is empty") + } + + if utf8.RuneCountInString(domain.GetValue()) > MaxPlatformDomainLength { + return errors.New("domain too long") + } + + return nil +} + +/** + * @brief 检查QPS + */ +func checkPlatformQPS(qps *wrappers.UInt32Value) error { + if qps.GetValue() == 0 { + return errors.New("qps is empty") + } + + if qps.GetValue() > MaxPlatformQPS { + return errors.New("qps too long") + } + return nil +} + +/** + * @brief 检查部门 + */ +func checkPlatformDepartment(department *wrappers.StringValue) error { + if department.GetValue() == "" { + return errors.New("department is empty") + } + + if utf8.RuneCountInString(department.GetValue()) > MaxDepartmentLength { + return errors.New("department too long") + } + + return nil +} + +/** + * @brief 检查描述 + */ +func checkPlatformComment(comment *wrappers.StringValue) error { + if comment.GetValue() == "" { + return errors.New("comment is empty") + } + + if utf8.RuneCountInString(comment.GetValue()) > MaxCommentLength { + return errors.New("comment too long") + } + return nil +} + +/** + * @brief 创建存储层模型 + */ +func createPlatformModel(req *api.Platform) *model.Platform { + platform := &model.Platform{ + ID: req.GetId().GetValue(), + Name: req.GetName().GetValue(), + Domain: req.GetDomain().GetValue(), + QPS: req.GetQps().GetValue(), + Token: NewUUID(), + Owner: req.GetOwner().GetValue(), + Department: req.GetDepartment().GetValue(), + Comment: req.GetComment().GetValue(), + } + + return platform +} + +/** + * @brief platform数组转换为[]*api.Platform + */ +func platforms2API(platforms []*model.Platform) []*api.Platform { + out := make([]*api.Platform, 0, len(platforms)) + for _, entry := range platforms { + out = append(out, platform2Api(entry)) + } + + return out +} + +/** + * @brief model.Platform转为api.Platform + */ +func platform2Api(platform *model.Platform) *api.Platform { + if platform == nil { + return nil + } + + // token不返回 + out := &api.Platform{ + Id: utils.NewStringValue(platform.ID), + Name: utils.NewStringValue(platform.Name), + Domain: utils.NewStringValue(platform.Domain), + Qps: utils.NewUInt32Value(platform.QPS), + Owner: utils.NewStringValue(platform.Owner), + Department: utils.NewStringValue(platform.Department), + Comment: utils.NewStringValue(platform.Comment), + Ctime: utils.NewStringValue(time2String(platform.CreateTime)), + Mtime: utils.NewStringValue(time2String(platform.ModifyTime)), + } + + return out +} + +/** + * @brief 修改平台字段 + */ +func (s *Server) updatePlatformAttribute(req *api.Platform, platform *model.Platform) bool { + needUpdate := false + + if req.GetName() != nil && req.GetName().GetValue() != platform.Name { + platform.Name = req.GetName().GetValue() + needUpdate = true + } + + if req.GetDomain() != nil && req.GetDomain().GetValue() != platform.Domain { + platform.Domain = req.GetDomain().GetValue() + needUpdate = true + } + + if req.GetQps() != nil && req.GetQps().GetValue() != platform.QPS { + platform.QPS = req.GetQps().GetValue() + needUpdate = true + } + + if req.GetOwner() != nil && req.GetOwner().GetValue() != platform.Owner { + platform.Owner = req.GetOwner().GetValue() + needUpdate = true + } + + if req.GetDepartment() != nil && req.GetDepartment().GetValue() != platform.Department { + platform.Department = req.GetDepartment().GetValue() + needUpdate = true + } + + if req.GetComment() != nil && req.GetComment().GetValue() != platform.Comment { + platform.Comment = req.GetComment().GetValue() + needUpdate = true + } + + return needUpdate +} + +/** + * @brief 封装存储层错误 + */ +func wrapperPlatformStoreResponse(platform *api.Platform, err error) *api.Response { + resp := storeError2Response(err) + if resp == nil { + return nil + } + + resp.Platform = platform + return resp +} + +/** + * @brief 获取平台的token信息 + */ +func parsePlatformToken(ctx context.Context, req *api.Platform) string { + if token := req.GetToken().GetValue(); token != "" { + return token + } + + return ParseToken(ctx) +} diff --git a/naming/ratelimit_config.go b/naming/ratelimit_config.go new file mode 100644 index 000000000..fc229d2fe --- /dev/null +++ b/naming/ratelimit_config.go @@ -0,0 +1,570 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package naming + +import ( + "context" + "encoding/json" + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/common/utils" + "github.com/golang/protobuf/ptypes" + "time" +) + +var ( + RateLimitFilters = map[string]bool{ + "service": true, + "namespace": true, + "labels": true, + "offset": true, + "limit": true, + } +) + +/** + * @brief 批量创建限流规则 + */ +func (s *Server) CreateRateLimits(ctx context.Context, request []*api.Rule) *api.BatchWriteResponse { + if err := checkBatchRateLimits(request); err != nil { + return err + } + + responses := api.NewBatchWriteResponse(api.ExecuteSuccess) + for _, rateLimit := range request { + response := s.CreateRateLimit(ctx, rateLimit) + responses.Collect(response) + } + return api.FormatBatchWriteResponse(responses) +} + +/** + * @brief 创建限流规则 + */ +func (s *Server) CreateRateLimit(ctx context.Context, req *api.Rule) *api.Response { + requestID := ParseRequestID(ctx) + platformID := ParsePlatformID(ctx) + + // 参数校验 + if resp := checkRateLimitParams(req); resp != nil { + return resp + } + + if resp := checkRateLimitRuleParams(requestID, req); resp != nil { + return resp + } + + tx, err := s.storage.CreateTransaction() + if err != nil { + log.Error(err.Error(), ZapRequestID(requestID), ZapPlatformID(platformID)) + return api.NewRateLimitResponse(api.StoreLayerException, req) + } + defer func() { + _ = tx.Commit() + }() + + // 锁住服务,防止服务被删除 + service, err := tx.RLockService(req.GetService().GetValue(), req.GetNamespace().GetValue()) + if err != nil { + log.Error(err.Error(), ZapRequestID(requestID), ZapPlatformID(platformID)) + return api.NewRateLimitResponse(api.StoreLayerException, req) + } + if service == nil { + return api.NewRateLimitResponse(api.NotFoundService, req) + } + if service.IsAlias() { + return api.NewRateLimitResponse(api.NotAllowAliasCreateRateLimit, req) + } + if err := s.verifyRateLimitAuth(ctx, service, req); err != nil { + return err + } + + clusterID := "" + + // 构造底层数据结构 + data, err := api2RateLimit(service.ID, clusterID, req) + if err != nil { + log.Error(err.Error(), ZapRequestID(requestID)) + return api.NewRateLimitResponse(api.ParseRateLimitException, req) + } + data.ID = NewUUID() + + // 存储层操作 + if err := s.storage.CreateRateLimit(data); err != nil { + log.Error(err.Error(), ZapRequestID(requestID), ZapPlatformID(platformID)) + return wrapperRateLimitStoreResponse(req, err) + } + + msg := fmt.Sprintf("create rate limit rule: id=%v, namespace=%v, service=%v, labels=%v", + data.ID, req.GetNamespace().GetValue(), req.GetService().GetValue(), data.Labels) + log.Info(msg, ZapRequestID(requestID), ZapPlatformID(platformID)) + + s.RecordHistory(rateLimitRecordEntry(ctx, req.GetNamespace().GetValue(), req.GetService().GetValue(), + data, model.OCreate)) + + req.Id = utils.NewStringValue(data.ID) + return api.NewRateLimitResponse(api.ExecuteSuccess, req) +} + +/** + * @brief 批量删除限流规则 + */ +func (s *Server) DeleteRateLimits(ctx context.Context, request []*api.Rule) *api.BatchWriteResponse { + if err := checkBatchRateLimits(request); err != nil { + return err + } + + responses := api.NewBatchWriteResponse(api.ExecuteSuccess) + for _, entry := range request { + resp := s.DeleteRateLimit(ctx, entry) + responses.Collect(resp) + } + return api.FormatBatchWriteResponse(responses) +} + +/** + * @brief 删除单个限流规则 + */ +func (s *Server) DeleteRateLimit(ctx context.Context, req *api.Rule) *api.Response { + requestID := ParseRequestID(ctx) + platformID := ParsePlatformID(ctx) + + // 参数校验 + if resp := checkRevisedRateLimitParams(req); resp != nil { + return resp + } + + // 检查限流规则是否存在 + rateLimit, resp := s.checkRateLimitExisted(req.GetId().GetValue(), requestID, req) + if resp != nil { + if resp.GetCode().GetValue() == api.NotFoundRateLimit { + return api.NewRateLimitResponse(api.ExecuteSuccess, req) + } + return resp + } + + // 鉴权 + service, resp := s.checkRateLimitValid(ctx, rateLimit.ServiceID, req) + if resp != nil { + return resp + } + + // 生成新的revision + rateLimit.Revision = NewUUID() + + // 存储层操作 + if err := s.storage.DeleteRateLimit(rateLimit); err != nil { + log.Error(err.Error(), ZapRequestID(requestID), ZapPlatformID(platformID)) + return wrapperRateLimitStoreResponse(req, err) + } + + msg := fmt.Sprintf("delete rate limit rule: id=%v, namespace=%v, service=%v, labels=%v", + rateLimit.ID, service.Namespace, service.Name, rateLimit.Labels) + log.Info(msg, ZapRequestID(requestID), ZapPlatformID(platformID)) + + s.RecordHistory(rateLimitRecordEntry(ctx, service.Namespace, service.Name, rateLimit, model.ODelete)) + return api.NewRateLimitResponse(api.ExecuteSuccess, req) +} + +/** + * @brief 批量更新限流规则 + */ +func (s *Server) UpdateRateLimits(ctx context.Context, request []*api.Rule) *api.BatchWriteResponse { + if err := checkBatchRateLimits(request); err != nil { + return err + } + + responses := api.NewBatchWriteResponse(api.ExecuteSuccess) + for _, entry := range request { + response := s.UpdateRateLimit(ctx, entry) + responses.Collect(response) + } + return api.FormatBatchWriteResponse(responses) +} + +/** + * @brief 更新限流规则 + */ +func (s *Server) UpdateRateLimit(ctx context.Context, req *api.Rule) *api.Response { + requestID := ParseRequestID(ctx) + platformID := ParsePlatformID(ctx) + + // 参数校验 + if resp := checkRevisedRateLimitParams(req); resp != nil { + return resp + } + + if resp := checkRateLimitRuleParams(requestID, req); resp != nil { + return resp + } + + // 检查限流规则是否存在 + data, resp := s.checkRateLimitExisted(req.GetId().GetValue(), requestID, req) + if resp != nil { + return resp + } + + // 鉴权 + service, resp := s.checkRateLimitValid(ctx, data.ServiceID, req) + if resp != nil { + return resp + } + + clusterID := "" + + // 构造底层数据结构 + rateLimit, err := api2RateLimit(service.ID, clusterID, req) + if err != nil { + log.Error(err.Error(), ZapRequestID(requestID), ZapPlatformID(platformID)) + return api.NewRateLimitResponse(api.ParseRateLimitException, req) + } + rateLimit.ID = data.ID + + if err := s.storage.UpdateRateLimit(rateLimit); err != nil { + log.Error(err.Error(), ZapRequestID(requestID), ZapPlatformID(platformID)) + return wrapperRateLimitStoreResponse(req, err) + } + + msg := fmt.Sprintf("update rate limit: id=%v, namespace=%v, service=%v, labels=%v", + rateLimit.ID, service.Namespace, service.Name, rateLimit.Labels) + log.Info(msg, ZapRequestID(requestID), ZapPlatformID(platformID)) + + s.RecordHistory(rateLimitRecordEntry(ctx, service.Namespace, service.Name, rateLimit, model.OUpdate)) + return api.NewRateLimitResponse(api.ExecuteSuccess, req) +} + +/** + * @brief 查询限流规则 + */ +func (s *Server) GetRateLimits(query map[string]string) *api.BatchQueryResponse { + for key := range query { + if _, ok := RateLimitFilters[key]; !ok { + log.Errorf("params %s is not allowed in querying rate limits", key) + return api.NewBatchQueryResponse(api.InvalidParameter) + } + } + + // service转化为name + if serviceName, ok := query["service"]; ok { + query["name"] = serviceName + delete(query, "service") + } + + // 处理offset和limit + offset, limit, err := ParseOffsetAndLimit(query) + if err != nil { + return api.NewBatchQueryResponse(api.InvalidParameter) + } + + total, extendRateLimits, err := s.storage.GetExtendRateLimits(query, offset, limit) + if err != nil { + log.Errorf("get rate limits store err: %s", err.Error()) + return api.NewBatchQueryResponse(api.StoreLayerException) + } + + out := api.NewBatchQueryResponse(api.ExecuteSuccess) + out.Amount = utils.NewUInt32Value(total) + out.Size = utils.NewUInt32Value(uint32(len(extendRateLimits))) + out.RateLimits = make([]*api.Rule, 0, len(extendRateLimits)) + for _, item := range extendRateLimits { + limit, err := rateLimit2api(item.ServiceName, item.NamespaceName, item.RateLimit) + if err != nil { + log.Errorf("get rate limits convert err: %s", err.Error()) + return api.NewBatchQueryResponse(api.ParseRateLimitException) + } + out.RateLimits = append(out.RateLimits, limit) + } + + return out +} + +/** + * @brief 检查批量请求 + */ +func checkBatchRateLimits(req []*api.Rule) *api.BatchWriteResponse { + if len(req) == 0 { + return api.NewBatchWriteResponse(api.EmptyRequest) + } + + if len(req) > MaxBatchSize { + return api.NewBatchWriteResponse(api.BatchSizeOverLimit) + } + + return nil +} + +/** + * @brief 检查限流规则是否允许修改/删除 + */ +func (s *Server) checkRateLimitValid(ctx context.Context, serviceID string, req *api.Rule) ( + *model.Service, *api.Response) { + requestID := ParseRequestID(ctx) + + service, err := s.storage.GetServiceByID(serviceID) + if err != nil { + log.Error(err.Error(), ZapRequestID(requestID)) + return nil, api.NewRateLimitResponse(api.StoreLayerException, req) + } + + if err := s.verifyRateLimitAuth(ctx, service, req); err != nil { + return nil, err + } + return service, nil +} + +/** + * @brief 检查限流规则基础参数 + */ +func checkRateLimitParams(req *api.Rule) *api.Response { + if req == nil { + return api.NewRateLimitResponse(api.EmptyRequest, req) + } + if err := checkResourceName(req.GetNamespace()); err != nil { + return api.NewRateLimitResponse(api.InvalidNamespaceName, req) + } + if err := checkResourceName(req.GetService()); err != nil { + return api.NewRateLimitResponse(api.InvalidServiceName, req) + } + if err := CheckDbStrFieldLen(req.GetService(), MaxDbServiceNameLength); err != nil { + return api.NewRateLimitResponse(api.InvalidServiceName, req) + } + if err := CheckDbStrFieldLen(req.GetNamespace(), MaxDbServiceNamespaceLength); err != nil { + return api.NewRateLimitResponse(api.InvalidNamespaceName, req) + } + if err := CheckDbStrFieldLen(req.GetServiceToken(), MaxDbServiceToken); err != nil { + return api.NewRateLimitResponse(api.InvalidServiceToken, req) + } + return nil +} + +/* + * @brief 检查限流规则其他参数 + */ +func checkRateLimitRuleParams(requestID string, req *api.Rule) *api.Response { + // 检查业务维度标签 + if req.GetLabels() == nil { + return api.NewRateLimitResponse(api.InvalidRateLimitLabels, req) + } + // 检查amounts是否有重复周期 + amounts := req.GetAmounts() + durations := make(map[time.Duration]bool) + for _, amount := range amounts { + d := amount.GetValidDuration() + duration, err := ptypes.Duration(d) + if err != nil { + log.Error(err.Error(), ZapRequestID(requestID)) + return api.NewRateLimitResponse(api.InvalidRateLimitAmounts, req) + } + durations[duration] = true + } + if len(amounts) != len(durations) { + return api.NewRateLimitResponse(api.InvalidRateLimitAmounts, req) + } + return nil +} + +/** + * @brief 检查修改/删除限流规则基础参数 + */ +func checkRevisedRateLimitParams(req *api.Rule) *api.Response { + if req == nil { + return api.NewRateLimitResponse(api.EmptyRequest, req) + } + if req.GetId().GetValue() == "" { + return api.NewRateLimitResponse(api.InvalidRateLimitID, req) + } + return nil +} + +/** + * @brief 检查限流规则是否存在 + */ +func (s *Server) checkRateLimitExisted(id, requestID string, req *api.Rule) (*model.RateLimit, *api.Response) { + rateLimit, err := s.storage.GetRateLimitWithID(id) + if err != nil { + log.Error(err.Error(), ZapRequestID(requestID)) + return nil, api.NewRateLimitResponse(api.StoreLayerException, req) + } + if rateLimit == nil { + return nil, api.NewRateLimitResponse(api.NotFoundRateLimit, req) + } + return rateLimit, nil +} + +/** + * @brief 获取限流规则请求的token信息 + */ +func parseRateLimitReqToken(ctx context.Context, req *api.Rule) string { + if reqToken := req.GetServiceToken().GetValue(); reqToken != "" { + return reqToken + } + + return ParseToken(ctx) +} + +/** + * @brief 限流鉴权 + */ +func (s *Server) verifyRateLimitAuth(ctx context.Context, service *model.Service, req *api.Rule) *api.Response { + // 使用平台id及token鉴权 + if ok := s.verifyAuthByPlatform(ctx, service.PlatformID); !ok { + // 检查token是否存在 + token := parseRateLimitReqToken(ctx, req) + if !s.authority.VerifyToken(token) { + return api.NewRateLimitResponse(api.InvalidServiceToken, req) + } + + // 检查token是否ok + if ok := s.authority.VerifyService(service.Token, token); !ok { + return api.NewRateLimitResponse(api.Unauthorized, req) + } + } + + return nil +} + +/** + * @brief 把API参数转化为内部数据结构 + */ +func api2RateLimit(serviceID string, clusterID string, req *api.Rule) (*model.RateLimit, error) { + labels, err := marshalRateLimitLabels(req.GetLabels()) + if err != nil { + return nil, err + } + rule, err := marshalRateLimitRules(req) + if err != nil { + return nil, err + } + + out := &model.RateLimit{ + ServiceID: serviceID, + ClusterID: clusterID, + Labels: labels, + Priority: req.GetPriority().GetValue(), + Rule: rule, + Revision: NewUUID(), + } + return out, nil +} + +/** + * @brief 把内部数据结构转化为API参数 + */ +func rateLimit2api(service string, namespace string, rateLimit *model.RateLimit) ( + *api.Rule, error) { + if rateLimit == nil { + return nil, nil + } + + rule := &api.Rule{} + + // 反序列化rule + if err := json.Unmarshal([]byte(rateLimit.Rule), rule); err != nil { + return nil, err + } + + // 反序列化labels + labels := make(map[string]*api.MatchString) + if err := json.Unmarshal([]byte(rateLimit.Labels), &labels); err != nil { + return nil, err + } + + // 暂时不返回Cluster + rule.Id = utils.NewStringValue(rateLimit.ID) + rule.Service = utils.NewStringValue(service) + rule.Namespace = utils.NewStringValue(namespace) + rule.Priority = utils.NewUInt32Value(rateLimit.Priority) + rule.Labels = labels + rule.Ctime = utils.NewStringValue(time2String(rateLimit.CreateTime)) + rule.Mtime = utils.NewStringValue(time2String(rateLimit.ModifyTime)) + rule.Revision = utils.NewStringValue(rateLimit.Revision) + + return rule, nil +} + +/** + * @brief 格式化限流规则labels + */ +func marshalRateLimitLabels(l map[string]*api.MatchString) (string, error) { + labels, err := json.Marshal(l) + if err != nil { + return "", err + } + return string(labels), nil +} + +/** + * @brief 序列化限流规则具体内容 + */ +func marshalRateLimitRules(req *api.Rule) (string, error) { + r := &api.Rule{ + Subset: req.GetSubset(), + Resource: req.GetResource(), + Type: req.GetType(), + Amounts: req.GetAmounts(), + Action: req.GetAction(), + Disable: req.GetDisable(), + Report: req.GetReport(), + Adjuster: req.GetAdjuster(), + RegexCombine: req.GetRegexCombine(), + AmountMode: req.GetAmountMode(), + Failover: req.GetFailover(), + Cluster: req.GetCluster(), + } + + rule, err := json.Marshal(r) + if err != nil { + return "", err + } + return string(rule), nil +} + +/** + * @brief 构建rateLimit的记录entry + */ +func rateLimitRecordEntry(ctx context.Context, namespace string, service string, md *model.RateLimit, + opt model.OperationType) *model.RecordEntry { + entry := &model.RecordEntry{ + ResourceType: model.RRateLimit, + OperationType: opt, + Namespace: namespace, + Service: service, + Operator: ParseOperator(ctx), + CreateTime: time.Now(), + } + + if md != nil { + entry.Context = fmt.Sprintf("id:%s,label:%s,priority:%d,rule:%s,revision:%s", + md.ID, md.Labels, md.Priority, md.Rule, md.Revision) + } + return entry +} + +/** + * @brief 封装路由存储层错误 + */ +func wrapperRateLimitStoreResponse(rule *api.Rule, err error) *api.Response { + resp := storeError2Response(err) + if resp == nil { + return nil + } + resp.RateLimit = rule + return resp +} diff --git a/naming/ratelimit_config_test.go b/naming/ratelimit_config_test.go new file mode 100644 index 000000000..94fb30d35 --- /dev/null +++ b/naming/ratelimit_config_test.go @@ -0,0 +1,64 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package naming + +import ( + "fmt" + "github.com/gogo/protobuf/jsonpb" + "github.com/golang/protobuf/ptypes/duration" + "github.com/golang/protobuf/ptypes/wrappers" + api "github.com/polarismesh/polaris-server/common/api/v1" + "testing" +) + +func TestServer_CreateRateLimitJson(t *testing.T) { + rule := &api.Rule { + Namespace: &wrappers.StringValue{Value: "Test"}, + Service: &wrappers.StringValue{Value: "TestService1"}, + Resource: api.Rule_QPS, + Type: api.Rule_LOCAL, + Method: &api.MatchString{ + Type: 0, + Value: &wrappers.StringValue{Value: "/info"}, + }, + Labels: map[string]*api.MatchString{ + "uin": &api.MatchString{ + Type: 0, + Value: &wrappers.StringValue{Value: "109870111"}, + }, + }, + AmountMode: api.Rule_GLOBAL_TOTAL, + Amounts: []*api.Amount{ + { + MaxAmount: &wrappers.UInt32Value{Value: 1000}, + ValidDuration: &duration.Duration{ + Seconds: 1, + }, + }, + }, + Action: &wrappers.StringValue{Value: "reject"}, + Failover: api.Rule_FAILOVER_LOCAL, + Disable: &wrappers.BoolValue{Value: false}, + } + marshaler := &jsonpb.Marshaler{} + ruleStr, err := marshaler.MarshalToString(rule) + if nil != err { + t.Fatal(err) + } + fmt.Printf(ruleStr) +} \ No newline at end of file diff --git a/naming/routing_config.go b/naming/routing_config.go new file mode 100644 index 000000000..80889e653 --- /dev/null +++ b/naming/routing_config.go @@ -0,0 +1,440 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package naming + +import ( + "context" + "encoding/json" + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/common/utils" + "time" +) + +var ( + RoutingConfigFilterAttrs = map[string]bool{ + "service": true, + "namespace": true, + "offset": true, + "limit": true, + } +) + +// 批量创建路由配置 +func (s *Server) CreateRoutingConfigs(ctx context.Context, req []*api.Routing) *api.BatchWriteResponse { + if err := checkBatchRoutingConfig(req); err != nil { + return err + } + + resps := api.NewBatchWriteResponse(api.ExecuteSuccess) + for _, entry := range req { + resp := s.CreateRoutingConfig(ctx, entry) + resps.Collect(resp) + } + + return api.FormatBatchWriteResponse(resps) +} + +// 创建一个路由配置 +// 创建路由配置需要锁住服务,防止服务被删除 +func (s *Server) CreateRoutingConfig(ctx context.Context, req *api.Routing) *api.Response { + rid := ParseRequestID(ctx) + pid := ParsePlatformID(ctx) + if resp := checkRoutingConfig(req); resp != nil { + return resp + } + + tx, err := s.storage.CreateTransaction() + if err != nil { + log.Error(err.Error(), ZapRequestID(rid), ZapPlatformID(pid)) + return api.NewRoutingResponse(api.StoreLayerException, req) + } + defer func() { _ = tx.Commit() }() + + serviceName := req.GetService().GetValue() + namespaceName := req.GetNamespace().GetValue() + service, err := tx.RLockService(serviceName, namespaceName) + if err != nil { + log.Error(err.Error(), ZapRequestID(rid), ZapPlatformID(pid)) + return api.NewRoutingResponse(api.StoreLayerException, req) + } + if service == nil { + return api.NewRoutingResponse(api.NotFoundService, req) + } + if service.IsAlias() { + return api.NewRoutingResponse(api.NotAllowAliasCreateRouting, req) + } + + // 鉴权 + if err := s.verifyRoutingAuth(ctx, service, req); err != nil { + return err + } + + // 检查路由配置是否已经存在了 + routingConfig, err := s.storage.GetRoutingConfigWithService(service.Name, service.Namespace) + if err != nil { + log.Error(err.Error(), ZapRequestID(rid), ZapPlatformID(pid)) + return api.NewRoutingResponse(api.StoreLayerException, req) + } + if routingConfig != nil { + return api.NewRoutingResponse(api.ExistedResource, req) + } + + // 构造底层数据结构,并且写入store + conf, err := api2RoutingConfig(service.ID, req) + if err != nil { + log.Error(err.Error(), ZapRequestID(rid), ZapPlatformID(pid)) + return api.NewRoutingResponse(api.ExecuteException, req) + } + if err := s.storage.CreateRoutingConfig(conf); err != nil { + log.Error(err.Error(), ZapRequestID(rid), ZapPlatformID(pid)) + return wrapperRoutingStoreResponse(req, err) + } + + s.RecordHistory(routingRecordEntry(ctx, req, conf, model.OCreate)) + return api.NewRoutingResponse(api.ExecuteSuccess, req) +} + +// 批量删除路由配置 +func (s *Server) DeleteRoutingConfigs(ctx context.Context, req []*api.Routing) *api.BatchWriteResponse { + if err := checkBatchRoutingConfig(req); err != nil { + return err + } + + out := api.NewBatchWriteResponse(api.ExecuteSuccess) + for _, entry := range req { + resp := s.DeleteRoutingConfig(ctx, entry) + out.Collect(resp) + } + + return api.FormatBatchWriteResponse(out) +} + +// 删除一个路由配置 +func (s *Server) DeleteRoutingConfig(ctx context.Context, req *api.Routing) *api.Response { + rid := ParseRequestID(ctx) + pid := ParsePlatformID(ctx) + service, resp := s.routingConfigCommonCheck(ctx, req) + if resp != nil { + return resp + } + + // store操作 + if err := s.storage.DeleteRoutingConfig(service.ID); err != nil { + log.Error(err.Error(), ZapRequestID(rid), ZapPlatformID(pid)) + return wrapperRoutingStoreResponse(req, err) + } + + s.RecordHistory(routingRecordEntry(ctx, req, nil, model.ODelete)) + return api.NewRoutingResponse(api.ExecuteSuccess, req) +} + +// 批量更新路由配置 +func (s *Server) UpdateRoutingConfigs(ctx context.Context, req []*api.Routing) *api.BatchWriteResponse { + if err := checkBatchRoutingConfig(req); err != nil { + return err + } + + out := api.NewBatchWriteResponse(api.ExecuteSuccess) + for _, entry := range req { + resp := s.UpdateRoutingConfig(ctx, entry) + out.Collect(resp) + } + + return api.FormatBatchWriteResponse(out) +} + +// 更新单个路由配置 +func (s *Server) UpdateRoutingConfig(ctx context.Context, req *api.Routing) *api.Response { + rid := ParseRequestID(ctx) + pid := ParsePlatformID(ctx) + service, resp := s.routingConfigCommonCheck(ctx, req) + if resp != nil { + return resp + } + + // 检查路由配置是否存在 + conf, err := s.storage.GetRoutingConfigWithService(service.Name, service.Namespace) + if err != nil { + log.Error(err.Error(), ZapRequestID(rid), ZapPlatformID(pid)) + return api.NewRoutingResponse(api.StoreLayerException, req) + } + if conf == nil { + return api.NewRoutingResponse(api.NotFoundRouting, req) + } + + // 作为一个整体进行Update,所有参数都要传递 + reqModel, err := api2RoutingConfig(service.ID, req) + if err != nil { + log.Error(err.Error(), ZapRequestID(rid), ZapPlatformID(pid)) + return api.NewRoutingResponse(api.ParseRoutingException, req) + } + + if err := s.storage.UpdateRoutingConfig(reqModel); err != nil { + log.Error(err.Error(), ZapRequestID(rid), ZapPlatformID(pid)) + return wrapperRoutingStoreResponse(req, err) + } + + s.RecordHistory(routingRecordEntry(ctx, req, reqModel, model.OUpdate)) + return api.NewRoutingResponse(api.ExecuteSuccess, req) +} + +// 提供给OSS的查询路由配置的接口 +func (s *Server) GetRoutingConfigs(ctx context.Context, query map[string]string) *api.BatchQueryResponse { + rid := ParseRequestID(ctx) + pid := ParsePlatformID(ctx) + + // 先处理offset和limit + offset, limit, err := ParseOffsetAndLimit(query) + if err != nil { + return api.NewBatchQueryResponse(api.InvalidParameter) + } + + // 处理剩余的参数 + filter := make(map[string]string) + for key, value := range query { + if _, ok := RoutingConfigFilterAttrs[key]; !ok { + log.Errorf("[Server][RoutingConfig][Query] attribute(%s) is not allowed", key) + return api.NewBatchQueryResponse(api.InvalidParameter) + } + filter[key] = value + } + // service -- > name 这个特殊处理一下 + if service, ok := filter["service"]; ok { + filter["name"] = service + delete(filter, "service") + } + + // 可以根据name和namespace过滤 + total, routings, err := s.storage.GetRoutingConfigs(filter, offset, limit) + if err != nil { + log.Error(err.Error(), ZapRequestID(rid), ZapPlatformID(pid)) + return api.NewBatchQueryResponse(api.StoreLayerException) + } + + // 格式化输出 + resp := api.NewBatchQueryResponse(api.ExecuteSuccess) + resp.Amount = utils.NewUInt32Value(total) + resp.Size = utils.NewUInt32Value(uint32(len(routings))) + resp.Routings = make([]*api.Routing, 0, len(routings)) + for _, entry := range routings { + routing, err := routingConfig2API(entry.Config, entry.ServiceName, entry.NamespaceName) + if err != nil { + log.Error(err.Error(), ZapRequestID(rid), ZapPlatformID(pid)) + return api.NewBatchQueryResponse(api.ParseRoutingException) + } + resp.Routings = append(resp.Routings, routing) + } + + return resp +} + +// 路由配置操作的公共检查 +func (s *Server) routingConfigCommonCheck(ctx context.Context, req *api.Routing) (*model.Service, *api.Response) { + if resp := checkRoutingConfig(req); resp != nil { + return nil, resp + } + + rid := ParseRequestID(ctx) + pid := ParsePlatformID(ctx) + serviceName := req.GetService().GetValue() + namespaceName := req.GetNamespace().GetValue() + + service, err := s.storage.GetService(serviceName, namespaceName) + if err != nil { + log.Error(err.Error(), ZapRequestID(rid), ZapPlatformID(pid)) + return nil, api.NewRoutingResponse(api.StoreLayerException, req) + } + if service == nil { + return nil, api.NewRoutingResponse(api.NotFoundService, req) + } + + // 鉴权 + if err := s.verifyRoutingAuth(ctx, service, req); err != nil { + return nil, err + } + + return service, nil +} + +// 检查路由配置基础参数有效性 +func checkRoutingConfig(req *api.Routing) *api.Response { + if req == nil { + return api.NewRoutingResponse(api.EmptyRequest, req) + } + if err := checkResourceName(req.GetService()); err != nil { + return api.NewRoutingResponse(api.InvalidServiceName, req) + } + + if err := checkResourceName(req.GetNamespace()); err != nil { + return api.NewRoutingResponse(api.InvalidNamespaceName, req) + } + + if err := CheckDbStrFieldLen(req.GetService(), MaxDbServiceNameLength); err != nil { + return api.NewRoutingResponse(api.InvalidServiceName, req) + } + if err := CheckDbStrFieldLen(req.GetNamespace(), MaxDbServiceNamespaceLength); err != nil { + return api.NewRoutingResponse(api.InvalidNamespaceName, req) + } + if err := CheckDbStrFieldLen(req.GetServiceToken(), MaxDbServiceToken); err != nil { + return api.NewRoutingResponse(api.InvalidServiceToken, req) + } + + return nil +} + +// 从routingConfig请求参数中获取token +func parseServiceRoutingToken(ctx context.Context, req *api.Routing) string { + if reqToken := req.GetServiceToken().GetValue(); reqToken != "" { + return reqToken + } + + return ParseToken(ctx) +} + +// 把API参数转换为内部的数据结构 +func api2RoutingConfig(serviceID string, req *api.Routing) (*model.RoutingConfig, error) { + inBounds, outBounds, err := marshalRoutingConfig(req.GetInbounds(), req.GetOutbounds()) + if err != nil { + return nil, err + } + + out := &model.RoutingConfig{ + ID: serviceID, + InBounds: string(inBounds), + OutBounds: string(outBounds), + Revision: NewUUID(), + } + + return out, nil +} + +// 把内部数据结构转换为API参数传递出去 +func routingConfig2API(req *model.RoutingConfig, service string, namespace string) (*api.Routing, error) { + if req == nil { + return nil, nil + } + + out := &api.Routing{ + Service: utils.NewStringValue(service), + Namespace: utils.NewStringValue(namespace), + Revision: utils.NewStringValue(req.Revision), + Ctime: utils.NewStringValue(time2String(req.CreateTime)), + Mtime: utils.NewStringValue(time2String(req.ModifyTime)), + } + + if req.InBounds != "" { + var inBounds []*api.Route + if err := json.Unmarshal([]byte(req.InBounds), &inBounds); err != nil { + return nil, err + } + out.Inbounds = inBounds + } + if req.OutBounds != "" { + var outBounds []*api.Route + if err := json.Unmarshal([]byte(req.OutBounds), &outBounds); err != nil { + return nil, err + } + out.Outbounds = outBounds + } + + return out, nil +} + +// 格式化inbounds和outbounds +func marshalRoutingConfig(in []*api.Route, out []*api.Route) ([]byte, []byte, error) { + inBounds, err := json.Marshal(in) + if err != nil { + return nil, nil, err + } + + outBounds, err := json.Marshal(out) + if err != nil { + return nil, nil, err + } + + return inBounds, outBounds, nil +} + +/* + * @brief 检查批量请求 + */ +func checkBatchRoutingConfig(req []*api.Routing) *api.BatchWriteResponse { + if len(req) == 0 { + return api.NewBatchWriteResponse(api.EmptyRequest) + } + + if len(req) > MaxBatchSize { + return api.NewBatchWriteResponse(api.BatchSizeOverLimit) + } + + return nil +} + +// 构建routingConfig的记录entry +func routingRecordEntry(ctx context.Context, req *api.Routing, md *model.RoutingConfig, + opt model.OperationType) *model.RecordEntry { + entry := &model.RecordEntry{ + ResourceType: model.RRouting, + OperationType: opt, + Namespace: req.GetNamespace().GetValue(), + Service: req.GetService().GetValue(), + Operator: ParseOperator(ctx), + CreateTime: time.Now(), + } + + if md != nil { + entry.Context = fmt.Sprintf("inBounds:%s,outBounds:%s,revision:%s", + md.InBounds, md.OutBounds, md.Revision) + } + return entry +} + +/** + * @brief 封装路由存储层错误 + */ +func wrapperRoutingStoreResponse(routing *api.Routing, err error) *api.Response { + resp := storeError2Response(err) + if resp == nil { + return nil + } + resp.Routing = routing + return resp +} + +/** + * @brief 路由鉴权 + */ +func (s *Server) verifyRoutingAuth(ctx context.Context, service *model.Service, req *api.Routing) *api.Response { + // 使用平台id及token鉴权 + if ok := s.verifyAuthByPlatform(ctx, service.PlatformID); !ok { + // 检查token是否存在 + token := parseServiceRoutingToken(ctx, req) + if !s.authority.VerifyToken(token) { + return api.NewRoutingResponse(api.InvalidServiceToken, req) + } + + // 检查token是否ok + if ok := s.authority.VerifyService(service.Token, token); !ok { + return api.NewRoutingResponse(api.Unauthorized, req) + } + } + return nil +} diff --git a/naming/server.go b/naming/server.go new file mode 100644 index 000000000..729cc01c5 --- /dev/null +++ b/naming/server.go @@ -0,0 +1,312 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package naming + +import ( + "context" + "encoding/hex" + "errors" + "github.com/polarismesh/polaris-server/naming/batch" + "go.uber.org/zap" + "sync" + "time" + + uuid "github.com/google/uuid" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/naming/auth" + "github.com/polarismesh/polaris-server/naming/cache" + "github.com/polarismesh/polaris-server/plugin" + "github.com/polarismesh/polaris-server/store" +) + +const ( + MaxBatchSize = 100 + MaxQuerySize = 100 +) + +const ( + SystemNamespace = "Polaris" + DefaultNamespace = "default" + DefaultTLL = 5 +) + +var ( + server = new(Server) + once = sync.Once{} + finishInit = false +) + +/** + * @brief 核心逻辑层配置 + */ +type Config struct { + Auth map[string]interface{} `yaml:"auth"` + HealthCheck HealthCheckConfig `yaml:"healthcheck"` + Batch map[string]interface{} `yaml:"batch"` +} + +/** + * @brief 对接API层的server层,用以处理业务逻辑 + */ +type Server struct { + storage store.Store + + caches *cache.NamingCache + authority auth.Authority + hbMgr *HeartBeatMgr + bc *batch.Controller + + cmdb plugin.CMDB + history plugin.History + ratelimit plugin.Ratelimit + discoverStatis plugin.DiscoverStatis + auth plugin.Auth + + l5service *l5service +} + +/** + * @brief 初始化 + */ +func Initialize(ctx context.Context, namingOpt *Config, cacheOpt *cache.Config) error { + var err error + once.Do(func() { + err = initialize(ctx, namingOpt, cacheOpt) + }) + + if err != nil { + return err + } + + finishInit = true + return nil +} + +// 获取已经初始化好的Server +func GetServer() (*Server, error) { + if !finishInit { + return nil, errors.New("Server has not done InitializeServer") + } + + return server, nil +} + +/** + * @brief 返回鉴权对象,获取鉴权信息 + */ +func (s *Server) Authority() auth.Authority { + return s.authority +} + +/** + * @brief 返回Cache + */ +func (s *Server) Cache() *cache.NamingCache { + return s.caches +} + +// server对外提供history插件的简单封装 +func (s *Server) RecordHistory(entry *model.RecordEntry) { + // 如果插件没有初始化,那么不记录history + if s.history == nil { + return + } + // 如果数据为空,则不需要打印了 + if entry == nil { + return + } + + // 调用插件记录history + s.history.Record(entry) +} + +/** + * @brief 打印服务发现统计 + */ +func (s *Server) RecordDiscoverStatis(service, namespace string) { + if s.discoverStatis == nil { + return + } + + s.discoverStatis.AddDiscoverCall(service, namespace, time.Now()) +} + +// 获取服务实例的revision +func (s *Server) GetServiceInstanceRevision(serviceID string, instances []*model.Instance) (string, error) { + revision := s.caches.GetServiceInstanceRevision(serviceID) + if revision != "" { + return revision, nil + } + + data, err := cache.ComputeRevision(serviceID, instances) + if err != nil { + return "", err + } + + return data, nil +} + +// 封装一下cmdb的GetLocation +func (s *Server) getLocation(host string) *model.Location { + if s.cmdb == nil { + return nil + } + + location, err := s.cmdb.GetLocation(host) + if err != nil { + log.Errorf("[Server] get location(%s) err: %s", host, err.Error()) + return nil + } + return location +} + +// 实例访问限流 +func (s *Server) allowInstanceAccess(instanceID string) bool { + if s.ratelimit == nil { + return true + } + + if ok := s.ratelimit.Allow(plugin.InstanceRatelimit, instanceID); !ok { + log.Error("[Server][ratelimit] instance is not allow access", zap.String("instance", instanceID)) + return false + } + + return true + +} + +// 内部初始化函数 +func initialize(ctx context.Context, namingOpt *Config, cacheOpt *cache.Config) error { + // 获取存储层对象 + s, err := store.GetStore() + if err != nil { + log.Errorf("[Naming][Server] can not get store, err: %s", err.Error()) + return errors.New("can not get store") + } + if s == nil { + log.Errorf("[Naming][Server] store is null") + return errors.New("store is null") + } + server.storage = s + + // 初始化鉴权模块 + authority, err := auth.NewAuthority(namingOpt.Auth) + if err != nil { + log.Errorf("[Naming][Server] new auth err: %s", err.Error()) + return err + } + server.authority = authority + + // cache模块,可以不开启 + // 对于控制台集群,只访问控制台接口的,可以不开启cache + if cacheOpt.Open { + cache.SetCacheConfig(cacheOpt) + log.Infof("cache is open, can access the client api function") + caches, cacheErr := cache.NewNamingCache(s) + if cacheErr != nil { + log.Errorf("[Naming][Server] new naming cache err: %s", cacheErr.Error()) + return cacheErr + } + server.caches = caches + if startErr := server.caches.Start(ctx); startErr != nil { + log.Errorf("[Naming][Server] start naming cache err: %s", startErr.Error()) + return startErr + } + } + + // 启动健康检查 + if healthCheckConf.Open { + hbMgr, err = NewHeartBeatMgr(ctx) + if err != nil { + log.Errorf("[Naming][Server] new heartbeat mgr failed: %s", err.Error()) + return err + } + + server.hbMgr = hbMgr + server.hbMgr.Start() + } + // 批量控制器 + batchConfig, err := batch.ParseBatchConfig(namingOpt.Batch) + if err != nil { + return err + } + bc, err := batch.NewBatchCtrlWithConfig(server.storage, server.authority, plugin.GetAuth(), batchConfig) + if err != nil { + log.Errorf("new batch ctrl with config err: %s", err.Error()) + return err + } + server.bc = bc + if server.bc != nil { + server.bc.Start(ctx) + } + + // l5service + server.l5service = &l5service{} + + // 插件初始化 + pluginInitialize() + + return nil +} + +// 插件初始化 +func pluginInitialize() { + // 获取CMDB插件 + server.cmdb = plugin.GetCMDB() + if server.cmdb == nil { + log.Warnf("Not Found CMDB Plugin") + } + + // 获取History插件,注意:插件的配置在bootstrap已经设置好 + server.history = plugin.GetHistory() + if server.history == nil { + log.Warnf("Not Found History Log Plugin") + } + + // 获取限流插件 + server.ratelimit = plugin.GetRatelimit() + if server.ratelimit == nil { + log.Warnf("Not found Ratelimit Plugin") + } + + // 获取DiscoverStatis插件 + server.discoverStatis = plugin.GetDiscoverStatis() + if server.discoverStatis == nil { + log.Warnf("Not Found Discover Statis Plugin") + } + + // 获取鉴权插件 + server.auth = plugin.GetAuth() + if server.auth == nil { + log.Warnf("Not found Auth Plugin") + } +} + +/** + * @brief 返回一个随机的UUID + */ +func NewUUID() string { + uuidBytes := uuid.New() + return hex.EncodeToString(uuidBytes[:]) +} + +// time.Time转为字符串时间 +func time2String(t time.Time) string { + return t.Format("2006-01-02 15:04:05") +} diff --git a/naming/service.go b/naming/service.go new file mode 100644 index 000000000..d502dc261 --- /dev/null +++ b/naming/service.go @@ -0,0 +1,996 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package naming + +import ( + "context" + "fmt" + "time" + + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/common/utils" + "go.uber.org/zap" +) + +// *model.service转换为*api.service +type Service2Api func(service *model.Service) *api.Service + +var ( + serviceFilter = 1 // 过滤服务的 + instanceFilter = 2 // 过滤实例的 + serviceMetaFilter = 3 // 过滤service Metadata的 + ServiceFilterAttributes = map[string]int{ + "name": serviceFilter, + "namespace": serviceFilter, + "business": serviceFilter, + "department": serviceFilter, + "cmdb_mod1": serviceFilter, + "cmdb_mod2": serviceFilter, + "cmdb_mod3": serviceFilter, + "owner": serviceFilter, + "offset": serviceFilter, + "limit": serviceFilter, + "platform_id": serviceFilter, + "host": instanceFilter, + "port": instanceFilter, + "keys": serviceMetaFilter, + "values": serviceMetaFilter, + } +) + +/** + * @brief 批量创建服务 + */ +func (s *Server) CreateServices(ctx context.Context, req []*api.Service) *api.BatchWriteResponse { + if checkError := checkBatchService(req); checkError != nil { + return checkError + } + + responses := api.NewBatchWriteResponse(api.ExecuteSuccess) + for _, service := range req { + response := s.CreateService(ctx, service) + responses.Collect(response) + } + + return api.FormatBatchWriteResponse(responses) +} + +/** + * @brief 创建单个服务 + */ +func (s *Server) CreateService(ctx context.Context, req *api.Service) *api.Response { + requestID := ParseRequestID(ctx) + platformID := ParsePlatformID(ctx) + // 参数检查 + if checkError := checkCreateService(req); checkError != nil { + return checkError + } + + namespaceName := req.GetNamespace().GetValue() + serviceName := req.GetName().GetValue() + // 检查命名空间是否存在 + namespace, err := s.storage.GetNamespace(namespaceName) + if err != nil { + log.Error(err.Error(), ZapRequestID(requestID), ZapPlatformID(platformID)) + return api.NewServiceResponse(api.StoreLayerException, req) + } + if namespace == nil { + return api.NewServiceResponse(api.NotFoundNamespace, req) + } + // 检查是否存在 + service, err := s.storage.GetService(serviceName, namespaceName) + if err != nil { + log.Error(err.Error(), ZapRequestID(requestID), ZapPlatformID(platformID)) + return api.NewServiceResponse(api.StoreLayerException, req) + } + if service != nil { + return api.NewServiceResponse(api.ExistedResource, req) + } + + // 存储层操作 + data := s.createServiceModel(req) + if err := s.storage.AddService(data); err != nil { + log.Error(err.Error(), ZapRequestID(requestID), ZapPlatformID(platformID)) + return wrapperServiceStoreResponse(req, err) + } + + msg := fmt.Sprintf("create service: namespace=%v, name=%v, meta=%+v", + namespaceName, serviceName, req.GetMetadata()) + log.Info(msg, ZapRequestID(requestID), ZapPlatformID(platformID)) + s.RecordHistory(serviceRecordEntry(ctx, req, data, model.OCreate)) + + out := &api.Service{ + Name: req.GetName(), + Namespace: req.GetNamespace(), + Token: utils.NewStringValue(data.Token), + } + + return api.NewServiceResponse(api.ExecuteSuccess, out) +} + +/** + * @brief 批量删除服务 + */ +func (s *Server) DeleteServices(ctx context.Context, req []*api.Service) *api.BatchWriteResponse { + if checkError := checkBatchService(req); checkError != nil { + return checkError + } + + responses := api.NewBatchWriteResponse(api.ExecuteSuccess) + for _, service := range req { + response := s.DeleteService(ctx, service) + responses.Collect(response) + } + + return api.FormatBatchWriteResponse(responses) +} + +/** + * @brief 删除单个服务 + * 删除操作需要对服务进行加锁操作, + * 防止有与服务关联的实例或者配置有新增的操作 + */ +func (s *Server) DeleteService(ctx context.Context, req *api.Service) *api.Response { + requestID := ParseRequestID(ctx) + platformID := ParsePlatformID(ctx) + + // 参数检查 + if checkError := checkReviseService(req); checkError != nil { + return checkError + } + + namespaceName := req.GetNamespace().GetValue() + serviceName := req.GetName().GetValue() + + // 检查是否存在 + service, err := s.storage.GetService(serviceName, namespaceName) + if err != nil { + log.Error(err.Error(), ZapRequestID(requestID), ZapPlatformID(platformID)) + return api.NewServiceResponse(api.StoreLayerException, req) + } + if service == nil { + return api.NewServiceResponse(api.ExecuteSuccess, req) + } + + // 鉴权 + if err := s.verifyServiceAuth(ctx, service, req); err != nil { + return err + } + + // 判断service下的资源是否已经全部被删除 + if resp := s.isServiceExistedResource(requestID, platformID, service); resp != nil { + return resp + } + + if err := s.storage.DeleteService(service.ID, serviceName, namespaceName); err != nil { + log.Error(err.Error(), ZapRequestID(requestID), ZapPlatformID(platformID)) + return wrapperServiceStoreResponse(req, err) + } + + msg := fmt.Sprintf("delete service: namespace=%v, name=%v", namespaceName, serviceName) + log.Info(msg, ZapRequestID(requestID), ZapPlatformID(platformID)) + s.RecordHistory(serviceRecordEntry(ctx, req, nil, model.ODelete)) + + return api.NewServiceResponse(api.ExecuteSuccess, req) +} + +/** + * @brief 批量修改服务 + */ +func (s *Server) UpdateServices(ctx context.Context, req []*api.Service) *api.BatchWriteResponse { + if checkError := checkBatchService(req); checkError != nil { + return checkError + } + + responses := api.NewBatchWriteResponse(api.ExecuteSuccess) + for _, service := range req { + response := s.UpdateService(ctx, service) + responses.Collect(response) + } + + return api.FormatBatchWriteResponse(responses) +} + +/** + * @brief 修改单个服务 + */ +func (s *Server) UpdateService(ctx context.Context, req *api.Service) *api.Response { + requestID := ParseRequestID(ctx) + platformID := ParsePlatformID(ctx) + // 校验基础参数合法性 + if resp := checkReviseService(req); resp != nil { + return resp + } + + // 鉴权 + service, _, resp := s.checkServiceAuthority(ctx, req) + if resp != nil { + return resp + } + + // [2020.02.18]如果该服务是别名服务,不允许修改 TODO + if service.IsAlias() { + return api.NewServiceResponse(api.NotAllowAliasUpdate, req) + } + + log.Info(fmt.Sprintf("old service: %+v", service), ZapRequestID(requestID), ZapPlatformID(platformID)) + + // 修改 + err, needUpdate, needUpdateOwner := s.updateServiceAttribute(req, service) + if err != nil { + return err + } + // 判断是否需要更新 + if !needUpdate { + log.Info("update service data no change, no need update", + ZapRequestID(requestID), ZapPlatformID(platformID), zap.String("service", req.String())) + return api.NewServiceResponse(api.NoNeedUpdate, req) + } + + // 存储层操作 + if err := s.storage.UpdateService(service, needUpdateOwner); err != nil { + log.Error(err.Error(), zap.String("request-id", requestID)) + return wrapperServiceStoreResponse(req, err) + } + + msg := fmt.Sprintf("update service: namespace=%v, name=%v", service.Namespace, service.Name) + log.Info(msg, ZapRequestID(requestID), ZapPlatformID(platformID)) + s.RecordHistory(serviceRecordEntry(ctx, req, service, model.OUpdate)) + + return api.NewServiceResponse(api.ExecuteSuccess, req) +} + +// 更新服务token +func (s *Server) UpdateServiceToken(ctx context.Context, req *api.Service) *api.Response { + // 校验参数合法性 + if resp := checkReviseService(req); resp != nil { + return resp + } + + // 鉴权 + service, _, resp := s.checkServiceAuthority(ctx, req) + if resp != nil { + return resp + } + if service.IsAlias() { + return api.NewServiceResponse(api.NotAllowAliasUpdate, req) + } + rid := ParseRequestID(ctx) + pid := ParsePlatformID(ctx) + + // 生成一个新的token和revision + service.Token = NewUUID() + service.Revision = NewUUID() + // 更新数据库 + if err := s.storage.UpdateServiceToken(service.ID, service.Token, service.Revision); err != nil { + log.Error(err.Error(), ZapRequestID(rid), ZapPlatformID(pid)) + return wrapperServiceStoreResponse(req, err) + } + log.Info("update service token", zap.String("namespace", service.Namespace), + zap.String("name", service.Name), zap.String("service-id", service.ID), + ZapRequestID(rid), ZapPlatformID(pid)) + s.RecordHistory(serviceRecordEntry(ctx, req, service, model.OUpdateToken)) + + // 填充新的token返回 + out := &api.Service{ + Name: req.GetName(), + Namespace: req.GetNamespace(), + Token: utils.NewStringValue(service.Token), + } + return api.NewServiceResponse(api.ExecuteSuccess, out) +} + +/** + * @brief 查询服务 + * 注意:不包括别名 + */ +func (s *Server) GetServices(query map[string]string) *api.BatchQueryResponse { + serviceFilters := make(map[string]string) + instanceFilters := make(map[string]string) + var metaKeys, metaValues string + for key, value := range query { + typ, ok := ServiceFilterAttributes[key] + if !ok { + log.Errorf("[Server][Service][Query] attribute(%s) it not allowed", key) + return api.NewBatchQueryResponseWithMsg(api.InvalidParameter, key+" is not allowed") + } + // 元数据value允许为空 + if key != "values" && value == "" { + log.Errorf("[Server][Service][Query] attribute(%s: %s) is not allowed empty", key, value) + return api.NewBatchQueryResponseWithMsg(api.InvalidParameter, "the value for "+key+" is empty") + } + switch { + case typ == serviceFilter: + serviceFilters[key] = value + case typ == serviceMetaFilter: + if key == "keys" { + metaKeys = value + } else { + metaValues = value + } + default: + instanceFilters[key] = value + } + } + + instanceArgs, err := ParseInstanceArgs(instanceFilters) + if err != nil { + log.Errorf("[Server][Service][Query] instance args error: %s", err.Error()) + return api.NewBatchQueryResponseWithMsg(api.InvalidParameter, err.Error()) + } + + // 解析metaKeys,metaValues + serviceMetas := make(map[string]string) + if metaKeys != "" { + serviceMetas[metaKeys] = metaValues + } + + // 判断offset和limit是否为int,并从filters清除offset/limit参数 + offset, limit, err := ParseOffsetAndLimit(serviceFilters) + if err != nil { + return api.NewBatchQueryResponse(api.InvalidParameter) + } + + total, services, err := s.storage.GetServices(serviceFilters, serviceMetas, instanceArgs, + offset, limit) + if err != nil { + log.Errorf("[Server][Service][Query] req(%+v) store err: %s", query, err.Error()) + return api.NewBatchQueryResponse(api.StoreLayerException) + } + + resp := api.NewBatchQueryResponse(api.ExecuteSuccess) + resp.Amount = utils.NewUInt32Value(total) + resp.Size = utils.NewUInt32Value(uint32(len(services))) + resp.Services = services2Api(services, service2Api) + return resp +} + +/** + * @brief 查询服务总数 + */ +func (s *Server) GetServicesCount() *api.BatchQueryResponse { + count, err := s.storage.GetServicesCount() + if err != nil { + log.Errorf("[Server][Service][Count] get service count storage err: %s", err.Error()) + return api.NewBatchQueryResponse(api.StoreLayerException) + } + + out := api.NewBatchQueryResponse(api.ExecuteSuccess) + out.Amount = utils.NewUInt32Value(count) + out.Services = make([]*api.Service, 0) + return out +} + +// 查询Service的token +func (s *Server) GetServiceToken(ctx context.Context, req *api.Service) *api.Response { + // 校验参数合法性 + if resp := checkReviseService(req); resp != nil { + return resp + } + + // 鉴权 + _, token, resp := s.checkServiceAuthority(ctx, req) + if resp != nil { + return resp + } + + //s.RecordHistory(serviceRecordEntry(ctx, req, model.OGetToken)) + out := api.NewResponse(api.ExecuteSuccess) + out.Service = &api.Service{ + Name: req.GetName(), + Namespace: req.GetNamespace(), + Token: utils.NewStringValue(token), + } + return out +} + +/** + * @brief 查询服务负责人 + */ +func (s *Server) GetServiceOwner(ctx context.Context, req []*api.Service) *api.BatchQueryResponse { + requestID := ParseRequestID(ctx) + platformID := ParseRequestID(ctx) + + if err := checkBatchReadService(req); err != nil { + return err + } + + services, err := s.storage.GetServicesBatch(apis2ServicesName(req)) + if err != nil { + log.Error(err.Error(), ZapRequestID(requestID), ZapPlatformID(platformID)) + return api.NewBatchQueryResponseWithMsg(api.StoreLayerException, err.Error()) + } + + resp := api.NewBatchQueryResponse(api.ExecuteSuccess) + resp.Amount = utils.NewUInt32Value(uint32(len(services))) + resp.Size = utils.NewUInt32Value(uint32(len(services))) + resp.Services = services2Api(services, serviceOwner2Api) + return resp +} + +/** + * @brief 创建存储层服务模型 + */ +func (s *Server) createServiceModel(req *api.Service) *model.Service { + service := &model.Service{ + ID: NewUUID(), + Name: req.GetName().GetValue(), + Namespace: req.GetNamespace().GetValue(), + Meta: req.GetMetadata(), + Ports: req.GetPorts().GetValue(), + Business: req.GetBusiness().GetValue(), + Department: req.GetDepartment().GetValue(), + CmdbMod1: req.GetCmdbMod1().GetValue(), + CmdbMod2: req.GetCmdbMod2().GetValue(), + CmdbMod3: req.GetCmdbMod3().GetValue(), + Comment: req.GetComment().GetValue(), + Owner: req.GetOwners().GetValue(), + PlatformID: req.GetPlatformId().GetValue(), + Token: NewUUID(), + Revision: NewUUID(), + } + + return service +} + +/** + * @brief 修改服务属性 + */ +func (s *Server) updateServiceAttribute(req *api.Service, service *model.Service) (*api.Response, bool, bool) { + // 待更新的参数检查 + if err := checkMetadata(req.GetMetadata()); err != nil { + return api.NewServiceResponse(api.InvalidMetadata, req), false, false + } + + needUpdate := false + needNewRevision := false + needUpdateOwner := false + if req.GetMetadata() != nil { + if need := serviceMetaNeedUpdate(req, service); need { + needUpdate = need + needNewRevision = true + service.Meta = req.GetMetadata() + } + } + if !needUpdate { + // 不需要更新metadata + service.Meta = nil + } + + if req.GetPorts() != nil && req.GetPorts().GetValue() != service.Ports { + service.Ports = req.GetPorts().GetValue() + needUpdate = true + } + + if req.GetBusiness() != nil && req.GetBusiness().GetValue() != service.Business { + service.Business = req.GetBusiness().GetValue() + needUpdate = true + } + + if req.GetDepartment() != nil && req.GetDepartment().GetValue() != service.Department { + service.Department = req.GetDepartment().GetValue() + needUpdate = true + } + + if req.GetCmdbMod1() != nil && req.GetCmdbMod1().GetValue() != service.CmdbMod1 { + service.CmdbMod1 = req.GetCmdbMod1().GetValue() + needUpdate = true + } + if req.GetCmdbMod2() != nil && req.GetCmdbMod2().GetValue() != service.CmdbMod2 { + service.CmdbMod2 = req.GetCmdbMod2().GetValue() + needUpdate = true + } + if req.GetCmdbMod3() != nil && req.GetCmdbMod3().GetValue() != service.CmdbMod3 { + service.CmdbMod3 = req.GetCmdbMod3().GetValue() + needUpdate = true + } + + if req.GetComment() != nil && req.GetComment().GetValue() != service.Comment { + service.Comment = req.GetComment().GetValue() + needUpdate = true + } + + if req.GetOwners() != nil && req.GetOwners().GetValue() != service.Owner { + service.Owner = req.GetOwners().GetValue() + needUpdate = true + needUpdateOwner = true + } + + if req.GetPlatformId() != nil && req.GetPlatformId().GetValue() != service.PlatformID { + service.PlatformID = req.GetPlatformId().GetValue() + needUpdate = true + } + + if needNewRevision { + service.Revision = NewUUID() + } + + return nil, needUpdate, needUpdateOwner +} + +// 获取服务下别名的总数 +func (s *Server) getServiceAliasCountWithService(name string, namespace string) (uint32, error) { + filter := map[string]string{ + "service": name, + "namespace": namespace, + } + total, _, err := s.storage.GetServiceAliases(filter, 0, 1) + if err != nil { + return 0, err + } + return total, nil +} + +// 获取服务下实例的总数 +func (s *Server) getInstancesCountWithService(name string, namespace string) (uint32, error) { + filter := map[string]string{ + "name": name, + "namespace": namespace, + } + total, _, err := s.storage.GetExpandInstances(filter, nil, 0, 1) + if err != nil { + return 0, err + } + return total, nil +} + +// 获取服务下路由配置总数 +func (s *Server) getRoutingCountWithService(id string) (uint32, error) { + routing, err := s.storage.GetRoutingConfigWithID(id) + if err != nil { + return 0, err + } + + if routing == nil { + return 0, nil + } + return 1, nil +} + +// 获取服务下限流规则总数 +func (s *Server) getRateLimitingCountWithService(name string, namespace string) (uint32, error) { + filter := map[string]string{ + "name": name, + "namespace": namespace, + } + total, _, err := s.storage.GetExtendRateLimits(filter, 0, 1) + if err != nil { + return 0, err + } + return total, nil +} + +// 获取服务下熔断规则总数 +func (s *Server) getCircuitBreakerCountWithService(name string, namespace string) (uint32, error) { + circuitBreaker, err := s.storage.GetCircuitBreakersByService(name, namespace) + if err != nil { + return 0, err + } + + if circuitBreaker == nil { + return 0, nil + } + return 1, nil +} + +// 检查服务下的资源存在情况,在删除服务的时候需要用到 +func (s *Server) isServiceExistedResource(rid, pid string, service *model.Service) *api.Response { + // 服务别名,不需要判断 + if service.IsAlias() { + return nil + } + out := &api.Service{ + Name: utils.NewStringValue(service.Name), + Namespace: utils.NewStringValue(service.Namespace), + } + total, err := s.getInstancesCountWithService(service.Name, service.Namespace) + if err != nil { + log.Error(err.Error(), ZapRequestID(rid), ZapPlatformID(pid)) + return api.NewServiceResponse(api.StoreLayerException, out) + } + if total != 0 { + return api.NewServiceResponse(api.ServiceExistedInstances, out) + } + + total, err = s.getServiceAliasCountWithService(service.Name, service.Namespace) + if err != nil { + log.Error(err.Error(), ZapRequestID(rid), ZapPlatformID(pid)) + return api.NewServiceResponse(api.StoreLayerException, out) + } + if total != 0 { + return api.NewServiceResponse(api.ServiceExistedAlias, out) + } + + total, err = s.getRoutingCountWithService(service.ID) + if err != nil { + log.Error(err.Error(), ZapRequestID(rid), ZapPlatformID(pid)) + return api.NewServiceResponse(api.StoreLayerException, out) + } + + if total != 0 { + return api.NewServiceResponse(api.ServiceExistedRoutings, out) + } + + total, err = s.getRateLimitingCountWithService(service.Name, service.Namespace) + if err != nil { + log.Error(err.Error(), ZapRequestID(rid), ZapPlatformID(pid)) + return api.NewServiceResponse(api.StoreLayerException, out) + } + if total != 0 { + return api.NewServiceResponse(api.ServiceExistedRateLimits, out) + } + + total, err = s.getCircuitBreakerCountWithService(service.Name, service.Namespace) + if err != nil { + log.Error(err.Error(), ZapRequestID(rid), ZapPlatformID(pid)) + return api.NewServiceResponse(api.StoreLayerException, out) + } + if total != 0 { + return api.NewServiceResponse(api.ServiceExistedCircuitBreakers, out) + } + + return nil +} + +// 对服务进行鉴权,并且返回model.Service +// return service, token, response +func (s *Server) checkServiceAuthority(ctx context.Context, req *api.Service) (*model.Service, + string, *api.Response) { + rid := ParseRequestID(ctx) + pid := ParsePlatformID(ctx) + namespaceName := req.GetNamespace().GetValue() + serviceName := req.GetName().GetValue() + + // 检查是否存在 + service, err := s.storage.GetService(serviceName, namespaceName) + if err != nil { + log.Error(err.Error(), ZapRequestID(rid), ZapPlatformID(pid)) + return nil, "", api.NewServiceResponse(api.StoreLayerException, req) + } + if service == nil { + return nil, "", api.NewServiceResponse(api.NotFoundResource, req) + } + expectToken := service.Token + + if err := s.verifyServiceAuth(ctx, service, req); err != nil { + return nil, "", err + } + + return service, expectToken, nil +} + +/** + * @brief 服务鉴权 + */ +func (s *Server) verifyServiceAuth(ctx context.Context, service *model.Service, req *api.Service) *api.Response { + // 使用平台id及token鉴权 + if ok := s.verifyAuthByPlatform(ctx, service.PlatformID); !ok { + // 检查token是否存在 + token := parseRequestToken(ctx, req.GetToken().GetValue()) + if !s.authority.VerifyToken(token) { + return api.NewServiceResponse(api.InvalidServiceToken, req) + } + + // 检查token是否ok + if ok := s.authority.VerifyService(service.Token, token); !ok { + return api.NewServiceResponse(api.Unauthorized, req) + } + } + + return nil +} + +// model.Service 转为 api.Service +func service2Api(service *model.Service) *api.Service { + if service == nil { + return nil + } + + // note: 不包括token,token比较特殊 + out := &api.Service{ + Name: utils.NewStringValue(service.Name), + Namespace: utils.NewStringValue(service.Namespace), + Metadata: service.Meta, + Ports: utils.NewStringValue(service.Ports), + Business: utils.NewStringValue(service.Business), + Department: utils.NewStringValue(service.Department), + CmdbMod1: utils.NewStringValue(service.CmdbMod1), + CmdbMod2: utils.NewStringValue(service.CmdbMod2), + CmdbMod3: utils.NewStringValue(service.CmdbMod3), + Comment: utils.NewStringValue(service.Comment), + Owners: utils.NewStringValue(service.Owner), + Revision: utils.NewStringValue(service.Revision), + PlatformId: utils.NewStringValue(service.PlatformID), + Ctime: utils.NewStringValue(time2String(service.CreateTime)), + Mtime: utils.NewStringValue(time2String(service.ModifyTime)), + } + + return out +} + +/** + * @brief model.Service转为api.Service + * @note 只转name+namespace+owner + */ +func serviceOwner2Api(service *model.Service) *api.Service { + if service == nil { + return nil + } + out := &api.Service{ + Name: utils.NewStringValue(service.Name), + Namespace: utils.NewStringValue(service.Namespace), + Owners: utils.NewStringValue(service.Owner), + } + return out +} + +// service数组转为[]*api.Service +func services2Api(services []*model.Service, handler Service2Api) []*api.Service { + out := make([]*api.Service, 0, len(services)) + for _, entry := range services { + out = append(out, handler(entry)) + } + + return out +} + +/** + * @brief api数组转为[]*model.Service + */ +func apis2ServicesName(reqs []*api.Service) []*model.Service { + if reqs == nil { + return nil + } + + out := make([]*model.Service, 0, len(reqs)) + for _, req := range reqs { + out = append(out, api2ServiceName(req)) + } + return out +} + +/** + * @brief api转为*model.Service + */ +func api2ServiceName(req *api.Service) *model.Service { + if req == nil { + return nil + } + service := &model.Service{ + Name: req.GetName().GetValue(), + Namespace: req.GetNamespace().GetValue(), + } + return service +} + +// 检查服务metadata是否需要更新 +func serviceMetaNeedUpdate(req *api.Service, service *model.Service) bool { + // 收到的请求的metadata为空,则代表metadata不需要更新 + if req.GetMetadata() == nil { + return false + } + + // metadata个数不一致,肯定需要更新 + if len(req.GetMetadata()) != len(service.Meta) { + return true + } + + needUpdate := false + // 新数据为标准,对比老数据,发现不一致,则需要更新 + for key, value := range req.GetMetadata() { + oldValue, ok := service.Meta[key] + if !ok { + needUpdate = true + break + } + if value != oldValue { + needUpdate = true + break + } + } + if needUpdate { + return true + } + + // 老数据作为标准,对比新数据,发现不一致,则需要更新 + for key, value := range service.Meta { + newValue, ok := req.Metadata[key] + if !ok { + needUpdate = true + break + } + if value != newValue { + needUpdate = true + break + } + } + + return needUpdate +} + +/* + * @brief 检查批量请求 + */ +func checkBatchService(req []*api.Service) *api.BatchWriteResponse { + if len(req) == 0 { + return api.NewBatchWriteResponse(api.EmptyRequest) + } + + if len(req) > MaxBatchSize { + return api.NewBatchWriteResponse(api.BatchSizeOverLimit) + } + + return nil +} + +/** + * @brief 检查批量读请求 + */ +func checkBatchReadService(req []*api.Service) *api.BatchQueryResponse { + if len(req) == 0 { + return api.NewBatchQueryResponse(api.EmptyRequest) + } + + if len(req) > MaxBatchSize { + return api.NewBatchQueryResponse(api.BatchSizeOverLimit) + } + + return nil +} + +/* + * @brief 检查创建服务请求参数 + */ +func checkCreateService(req *api.Service) *api.Response { + if req == nil { + return api.NewServiceResponse(api.EmptyRequest, req) + } + + if err := checkResourceName(req.GetName()); err != nil { + return api.NewServiceResponse(api.InvalidServiceName, req) + } + + if err := checkResourceName(req.GetNamespace()); err != nil { + return api.NewServiceResponse(api.InvalidNamespaceName, req) + } + + if err := checkResourceOwners(req.GetOwners()); err != nil { + return api.NewServiceResponse(api.InvalidServiceOwners, req) + } + + if err := checkMetadata(req.GetMetadata()); err != nil { + return api.NewServiceResponse(api.InvalidMetadata, req) + } + + // 检查字段长度是否大于DB中对应字段长 + err, notOk := CheckDbServiceFieldLen(req) + if notOk { + return err + } + + return nil +} + +/* + * @brief 检查删除/修改/服务token的服务请求参数 + */ +func checkReviseService(req *api.Service) *api.Response { + if req == nil { + return api.NewServiceResponse(api.EmptyRequest, req) + } + + if err := checkResourceName(req.GetName()); err != nil { + return api.NewServiceResponse(api.InvalidServiceName, req) + } + + if err := checkResourceName(req.GetNamespace()); err != nil { + return api.NewServiceResponse(api.InvalidNamespaceName, req) + } + + // 检查字段长度是否大于DB中对应字段长 + err, notOk := CheckDbServiceFieldLen(req) + if notOk { + return err + } + + return nil +} + +// wrapper service error +func wrapperServiceStoreResponse(service *api.Service, err error) *api.Response { + resp := storeError2Response(err) + if resp == nil { + return nil + } + + resp.Service = service + return resp +} + +// 从request中获取服务token +func parseRequestToken(ctx context.Context, value string) string { + if value != "" { + return value + } + + return ParseToken(ctx) +} + +// 生成服务的记录entry +func serviceRecordEntry(ctx context.Context, req *api.Service, md *model.Service, + operationType model.OperationType) *model.RecordEntry { + entry := &model.RecordEntry{ + ResourceType: model.RService, + OperationType: operationType, + Namespace: req.GetNamespace().GetValue(), + Service: req.GetName().GetValue(), + Operator: ParseOperator(ctx), + CreateTime: time.Now(), + } + if md != nil { + entry.Context = fmt.Sprintf("platformID:%s,meta:%+v,revision:%s", md.PlatformID, md.Meta, md.Revision) + } + + return entry +} + +// 检查DB中service表对应的入参字段合法性 +func CheckDbServiceFieldLen(req *api.Service) (*api.Response, bool) { + if err := CheckDbStrFieldLen(req.GetName(), MaxDbServiceNameLength); err != nil { + return api.NewServiceResponse(api.InvalidServiceName, req), true + } + if err := CheckDbStrFieldLen(req.GetNamespace(), MaxDbServiceNamespaceLength); err != nil { + return api.NewServiceResponse(api.InvalidNamespaceName, req), true + } + if err := CheckDbMetaDataFieldLen(req.GetMetadata()); err != nil { + return api.NewServiceResponse(api.InvalidMetadata, req), true + } + if err := CheckDbStrFieldLen(req.GetPorts(), MaxDbServicePortsLength); err != nil { + return api.NewServiceResponse(api.InvalidServicePorts, req), true + } + if err := CheckDbStrFieldLen(req.GetBusiness(), MaxDbServiceBusinessLength); err != nil { + return api.NewServiceResponse(api.InvalidServiceBusiness, req), true + } + if err := CheckDbStrFieldLen(req.GetDepartment(), MaxDbServiceDeptLength); err != nil { + return api.NewServiceResponse(api.InvalidServiceDepartment, req), true + } + if err := CheckDbStrFieldLen(req.GetCmdbMod1(), MaxDbServiceCMDBLength); err != nil { + return api.NewServiceResponse(api.InvalidServiceCMDB, req), true + } + if err := CheckDbStrFieldLen(req.GetCmdbMod2(), MaxDbServiceCMDBLength); err != nil { + return api.NewServiceResponse(api.InvalidServiceCMDB, req), true + } + if err := CheckDbStrFieldLen(req.GetCmdbMod3(), MaxDbServiceCMDBLength); err != nil { + return api.NewServiceResponse(api.InvalidServiceCMDB, req), true + } + if err := CheckDbStrFieldLen(req.GetComment(), MaxDbServiceCommentLength); err != nil { + return api.NewServiceResponse(api.InvalidServiceComment, req), true + } + if err := CheckDbStrFieldLen(req.GetOwners(), MaxDbServiceOwnerLength); err != nil { + return api.NewServiceResponse(api.InvalidServiceOwners, req), true + } + if err := CheckDbStrFieldLen(req.GetToken(), MaxDbServiceToken); err != nil { + return api.NewServiceResponse(api.InvalidServiceToken, req), true + } + if err := CheckDbStrFieldLen(req.GetPlatformId(), MaxPlatformIDLength); err != nil { + return api.NewServiceResponse(api.InvalidPlatformID, req), true + } + return nil, false +} diff --git a/naming/service_alias.go b/naming/service_alias.go new file mode 100644 index 000000000..27e01893f --- /dev/null +++ b/naming/service_alias.go @@ -0,0 +1,556 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package naming + +import ( + "context" + "errors" + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/common/utils" + "github.com/polarismesh/polaris-server/store" + "go.uber.org/zap" +) + +var ( + AliasFilterAttributes = map[string]bool{ + "alias": true, + "namespace": true, + "service": true, + "owner": true, + "offset": true, + "limit": true, + } +) + +// 创建服务别名 +func (s *Server) CreateServiceAlias(ctx context.Context, req *api.ServiceAlias) *api.Response { + if resp := checkServiceAliasReq(ctx, req); resp != nil { + return resp + } + + rid := ParseRequestID(ctx) + tx, err := s.storage.CreateTransaction() + if err != nil { + log.Error(err.Error(), ZapRequestID(rid)) + return api.NewServiceAliasResponse(api.StoreLayerException, req) + } + defer func() { _ = tx.Commit() }() + + service, response, done := s.checkPointServiceAlias(err, tx, req, rid) + if done { + return response + } + + // 鉴权 + actualToken := parseRequestToken(ctx, req.GetServiceToken().GetValue()) + if ok := s.authority.VerifyService(service.Token, actualToken); !ok { + return api.NewServiceAliasResponse(api.Unauthorized, req) + } + + // 检查是否存在同名的alias + if req.GetAlias().GetValue() != "" { + oldAlias, getErr := s.storage.GetService(req.GetAlias().GetValue(), + req.GetNamespace().GetValue()) + if getErr != nil { + log.Error(getErr.Error(), ZapRequestID(rid)) + return api.NewServiceAliasResponse(api.StoreLayerException, req) + } + if oldAlias != nil { + return api.NewServiceAliasResponse(api.ExistedResource, req) + } + } + + // 构建别名的信息,这里包括了创建SID + input, resp := s.createServiceAliasModel(req, service, service.Token, service.Owner) + if resp != nil { + return resp + } + if err := s.storage.AddService(input); err != nil { + log.Error(err.Error(), ZapRequestID(rid)) + return api.NewServiceAliasResponse(api.StoreLayerException, req) + } + + log.Info(fmt.Sprintf("create service alias, service(%s, %s), alias(%s)", + req.Service.Value, req.Namespace.Value, input.Name), ZapRequestID(rid)) + out := &api.ServiceAlias{ + Service: req.Service, + Namespace: req.Namespace, + Alias: req.Alias, + } + if out.GetAlias().GetValue() == "" { + out.Alias = utils.NewStringValue(input.Name) + } + record := &api.Service{Name: out.Alias, Namespace: out.Namespace} + s.RecordHistory(serviceRecordEntry(ctx, record, input, model.OCreate)) + return api.NewServiceAliasResponse(api.ExecuteSuccess, out) +} + +/** + * @brief 创建服务别名 + */ +func (s *Server) CreateServiceAliasNoAuth(ctx context.Context, req *api.ServiceAlias) *api.Response { + rid := ParseRequestID(ctx) + + // 检查请求参数 + if resp := checkServiceAliasReqWithNoAuth(req); resp != nil { + return resp + } + + // 检查服务别名是否存在 + if req.GetAlias().GetValue() != "" { + alias, err := s.storage.GetService(req.GetAlias().GetValue(), req.GetNamespace().GetValue()) + if err != nil { + log.Error(err.Error(), ZapRequestID(rid)) + return api.NewServiceAliasResponse(api.StoreLayerException, req) + } + if alias != nil { + return api.NewServiceAliasResponse(api.ExistedResource, req) + } + } + + tx, err := s.storage.CreateTransaction() + if err != nil { + log.Error(err.Error(), ZapRequestID(rid)) + return api.NewServiceAliasResponse(api.StoreLayerException, req) + } + defer func() { _ = tx.Commit() }() + + service, response, done := s.checkPointServiceAlias(err, tx, req, rid) + if done { + return response + } + + // 构造存储层模型,这里包含了创建SID + data, resp := s.createServiceAliasModel(req, service, NewUUID(), req.GetOwners().GetValue()) + if resp != nil { + return resp + } + + // 执行存储层操作 + if err := s.storage.AddService(data); err != nil { + log.Error(err.Error(), ZapRequestID(rid)) + return api.NewServiceAliasResponse(api.StoreLayerException, req) + } + + log.Info(fmt.Sprintf("create service alias, service(%s, %s), alias(%s)", + req.GetService().GetValue(), req.GetNamespace().GetValue(), data.Name), ZapRequestID(rid)) + + if req.GetAlias().GetValue() == "" { + req.Alias = utils.NewStringValue(data.Name) + } + req.ServiceToken = utils.NewStringValue(data.Token) + + record := &api.Service{Name: req.Alias, Namespace: req.Namespace} + s.RecordHistory(serviceRecordEntry(ctx, record, data, model.OCreate)) + + return api.NewServiceAliasResponse(api.ExecuteSuccess, req) +} + +func (s *Server) checkPointServiceAlias( + err error, tx store.Transaction, req *api.ServiceAlias, rid string) (*model.Service, *api.Response, bool) { + // 检查指向服务是否存在以及是否为别名 + service, err := tx.LockService(req.GetService().GetValue(), req.GetNamespace().GetValue()) + if err != nil { + log.Error(err.Error(), ZapRequestID(rid)) + return nil, api.NewServiceAliasResponse(api.StoreLayerException, req), true + } + if service == nil { + return nil, api.NewServiceAliasResponse(api.NotFoundService, req), true + } + // 检查该服务是否已经是一个别名服务,不允许再为别名创建别名 + if service.IsAlias() { + return nil, api.NewServiceAliasResponse(api.NotAllowCreateAliasForAlias, req), true + } + return service, nil, false +} + +/** + * @brief 删除服务别名 + * @note 需要带上源服务name,namespace,token + * @note 另外一种删除别名的方式,是直接调用删除服务的接口,也是可行的 + */ +func (s *Server) DeleteServiceAlias(ctx context.Context, req *api.ServiceAlias) *api.Response { + if resp := checkReviseServiceAliasReq(ctx, req); resp != nil { + return resp + } + rid := ParseRequestID(ctx) + service, err := s.storage.GetService(req.GetService().GetValue(), + req.GetNamespace().GetValue()) + if err != nil { + log.Error(err.Error(), ZapRequestID(rid)) + return api.NewServiceAliasResponse(api.StoreLayerException, req) + } + if service == nil { + return api.NewServiceAliasResponse(api.NotFoundService, req) + } + // 鉴权 + actualToken := parseRequestToken(ctx, req.GetServiceToken().GetValue()) + if ok := s.authority.VerifyService(service.Token, actualToken); !ok { + return api.NewServiceAliasResponse(api.Unauthorized, req) + } + + // 直接删除alias + if err := s.storage.DeleteServiceAlias(req.GetAlias().GetValue(), + req.GetNamespace().GetValue()); err != nil { + log.Error(err.Error(), ZapRequestID(rid)) + return api.NewServiceAliasResponse(api.StoreLayerException, req) + } + + return api.NewServiceAliasResponse(api.ExecuteSuccess, req) +} + +/** + * @brief 修改服务别名 + */ +func (s *Server) UpdateServiceAlias(ctx context.Context, req *api.ServiceAlias) *api.Response { + rid := ParseRequestID(ctx) + + // 检查请求参数 + if resp := checkReviseServiceAliasReq(ctx, req); resp != nil { + return resp + } + + // 检查别名负责人 + if err := checkResourceOwners(req.GetOwners()); err != nil { + return api.NewServiceAliasResponse(api.InvalidServiceAliasOwners, req) + } + + // 检查服务别名是否存在 + alias, err := s.storage.GetService(req.GetAlias().GetValue(), req.GetNamespace().GetValue()) + if err != nil { + log.Error(err.Error(), ZapRequestID(rid)) + return api.NewServiceAliasResponse(api.StoreLayerException, req) + } + if alias == nil { + return api.NewServiceAliasResponse(api.NotFoundServiceAlias, req) + } + + // 鉴权 + actualToken := parseRequestToken(ctx, req.GetServiceToken().GetValue()) + if ok := s.authority.VerifyService(alias.Token, actualToken); !ok { + return api.NewServiceAliasResponse(api.Unauthorized, req) + } + + // 检查将要指向的服务是否存在 + service, err := s.storage.GetService(req.GetService().GetValue(), req.GetNamespace().GetValue()) + if err != nil { + log.Error(err.Error(), ZapRequestID(rid)) + return api.NewServiceAliasResponse(api.StoreLayerException, req) + } + if service == nil { + return api.NewServiceAliasResponse(api.NotFoundService, req) + } + // 检查该服务是否已经是一个别名服务,不允许再为别名创建别名 + if service.IsAlias() { + return api.NewServiceAliasResponse(api.NotAllowCreateAliasForAlias, req) + } + + // 判断是否需要修改 + resp, needUpdate, needUpdateOwner := s.updateServiceAliasAttribute(req, alias, service.ID) + if resp != nil { + return resp + } + + if !needUpdate { + log.Info("update service alias data no change, no need update", ZapRequestID(rid), + zap.String("service alias", req.String())) + return api.NewServiceAliasResponse(api.NoNeedUpdate, req) + } + + // 执行存储层操作 + if err := s.storage.UpdateServiceAlias(alias, needUpdateOwner); err != nil { + log.Error(err.Error(), ZapRequestID(rid)) + return wrapperServiceAliasResponse(req, err) + } + + log.Info(fmt.Sprintf("update service alias, service(%s, %s), alias(%s)", + req.GetService().GetValue(), req.GetNamespace().GetValue(), req.GetAlias().GetValue()), ZapRequestID(rid)) + + record := &api.Service{Name: req.Alias, Namespace: req.Namespace} + s.RecordHistory(serviceRecordEntry(ctx, record, alias, model.OUpdate)) + + return api.NewServiceAliasResponse(api.ExecuteSuccess, req) +} + +/** + * @brief 查找服务别名 + */ +func (s *Server) GetServiceAliases(query map[string]string) *api.BatchQueryResponse { + // 先处理offset和limit + offset, limit, err := ParseOffsetAndLimit(query) + if err != nil { + return api.NewBatchQueryResponse(api.InvalidParameter) + } + + // 处理剩余的参数 + filter := make(map[string]string) + for key, value := range query { + if _, ok := AliasFilterAttributes[key]; !ok { + log.Errorf("[Server][Alias][Query] attribute(%s) is not allowed", key) + return api.NewBatchQueryResponse(api.InvalidParameter) + } + filter[key] = value + } + + total, aliases, err := s.storage.GetServiceAliases(filter, offset, limit) + if err != nil { + log.Errorf("[Server][Alias] get aliases err: %s", err.Error()) + return api.NewBatchQueryResponse(api.StoreLayerException) + } + + resp := api.NewBatchQueryResponse(api.ExecuteSuccess) + resp.Amount = utils.NewUInt32Value(total) + resp.Size = utils.NewUInt32Value(uint32(len(aliases))) + resp.Aliases = make([]*api.ServiceAlias, 0, len(aliases)) + for _, entry := range aliases { + item := &api.ServiceAlias{ + Service: utils.NewStringValue(entry.Service), + Namespace: utils.NewStringValue(entry.Namespace), + Alias: utils.NewStringValue(entry.Alias), + Owners: utils.NewStringValue(entry.Owner), + Comment: utils.NewStringValue(entry.Comment), + Ctime: utils.NewStringValue(time2String(entry.CreateTime)), + Mtime: utils.NewStringValue(time2String(entry.ModifyTime)), + } + resp.Aliases = append(resp.Aliases, item) + } + + return resp +} + +// 检查别名请求 +func checkServiceAliasReq(ctx context.Context, req *api.ServiceAlias) *api.Response { + response, done := preCheckAlias(req) + if done { + return response + } + + if token := parseRequestToken(ctx, req.GetServiceToken().GetValue()); token == "" { + return api.NewServiceAliasResponse(api.InvalidServiceToken, req) + } + // 检查字段长度是否大于DB中对应字段长 + err, notOk := CheckDbServiceAliasFieldLen(req) + if notOk { + return err + } + return nil +} + +func preCheckAlias(req *api.ServiceAlias) (*api.Response, bool) { + if req == nil { + return api.NewServiceAliasResponse(api.EmptyRequest, req), true + } + + if err := checkResourceName(req.GetService()); err != nil { + return api.NewServiceAliasResponse(api.InvalidServiceName, req), true + } + + if err := checkResourceName(req.GetNamespace()); err != nil { + return api.NewServiceAliasResponse(api.InvalidNamespaceName, req), true + } + + // 默认类型,需要检查alias是否为空 + if req.GetType() == api.AliasType_DEFAULT { + if err := checkResourceName(req.GetAlias()); err != nil { + return api.NewServiceAliasResponse(api.InvalidServiceAlias, req), true + } + } + return nil, false +} + +/** + * @brief 检查创建别名请求(无鉴权) + */ +func checkServiceAliasReqWithNoAuth(req *api.ServiceAlias) *api.Response { + response, done := preCheckAlias(req) + if done { + return response + } + // 检查负责人 + if err := checkResourceOwners(req.GetOwners()); err != nil { + return api.NewServiceAliasResponse(api.InvalidServiceAliasOwners, req) + } + // 检查字段长度是否大于DB中对应字段长 + err, notOk := CheckDbServiceAliasFieldLen(req) + if notOk { + return err + } + + return nil +} + +/** + * @brief 检查删除、修改别名请求 + */ +func checkReviseServiceAliasReq(ctx context.Context, req *api.ServiceAlias) *api.Response { + if req == nil { + return api.NewServiceAliasResponse(api.EmptyRequest, req) + } + + // 检查服务别名 + if err := checkResourceName(req.GetAlias()); err != nil { + return api.NewServiceAliasResponse(api.InvalidServiceAlias, req) + } + + // 检查token + if token := parseRequestToken(ctx, req.GetServiceToken().GetValue()); token == "" { + return api.NewServiceAliasResponse(api.InvalidServiceToken, req) + } + + // 检查服务名 + if err := checkResourceName(req.GetService()); err != nil { + return api.NewServiceAliasResponse(api.InvalidServiceName, req) + } + + // 检查命名空间 + if err := checkResourceName(req.GetNamespace()); err != nil { + return api.NewServiceAliasResponse(api.InvalidNamespaceName, req) + } + + // 检查字段长度是否大于DB中对应字段长 + err, notOk := CheckDbServiceAliasFieldLen(req) + if notOk { + return err + } + + return nil +} + +/** + * @brief 修改服务别名属性 + */ +func (s *Server) updateServiceAliasAttribute(req *api.ServiceAlias, alias *model.Service, serviceID string) ( + *api.Response, bool, bool) { + needUpdate := false + needUpdateOwner := false + + // 获取当前指向服务 + service, err := s.storage.GetServiceByID(alias.Reference) + if err != nil { + return api.NewServiceAliasResponse(api.StoreLayerException, req), needUpdate, needUpdateOwner + } + + if service.ID != serviceID { + alias.Reference = serviceID + needUpdate = true + } + + if req.GetOwners().GetValue() != alias.Owner { + alias.Owner = req.GetOwners().GetValue() + needUpdate = true + needUpdateOwner = true + } + + if req.GetComment() != nil && req.GetComment().GetValue() != alias.Comment { + alias.Comment = req.GetComment().GetValue() + needUpdate = true + } + + if needUpdate { + alias.Revision = NewUUID() + } + + return nil, needUpdate, needUpdateOwner +} + +/** + * @brief 构建存储结构 + */ +func (s *Server) createServiceAliasModel(req *api.ServiceAlias, service *model.Service, token string, owner string) ( + *model.Service, *api.Response) { + out := &model.Service{ + ID: NewUUID(), + Name: req.GetAlias().GetValue(), + Namespace: req.GetNamespace().GetValue(), + Reference: service.ID, + Token: token, + Owner: owner, + Comment: req.GetComment().GetValue(), + Revision: NewUUID(), + } + + // sid类型,则创建SID + if req.GetType() == api.AliasType_CL5SID { + layoutID, ok := Namespace2SidLayoutID[req.GetNamespace().GetValue()] + if !ok { + log.Errorf("[Server][Alias] namespace(%s) not allow to create sid alias", + req.GetNamespace().GetValue()) + return nil, api.NewServiceAliasResponse(api.InvalidNamespaceWithAlias, req) + } + sid, err := s.storage.GenNextL5Sid(layoutID) + if err != nil { + log.Errorf("[Server] gen next l5 sid err: %s", err.Error()) + return nil, api.NewServiceAliasResponse(api.StoreLayerException, req) + } + out.Name = sid + } + + return out, nil +} + +// 根据Reference获取源服务的token +func (s *Server) getSourceServiceToken(refer string) (string, uint32, error) { + if refer == "" { + return "", 0, nil + } + service, err := s.storage.GetServiceByID(refer) + if err != nil { + return "", api.StoreLayerException, err + } + if service == nil { + return "", api.NotFoundSourceService, errors.New("not found source service") + } + + return service.Token, 0, nil +} + +/** + * @brief wrapper service alias error + */ +func wrapperServiceAliasResponse(alias *api.ServiceAlias, err error) *api.Response { + resp := storeError2Response(err) + if resp == nil { + return nil + } + + resp.Alias = alias + return resp +} + +// 检查DB中service表对应的入参字段合法性 +func CheckDbServiceAliasFieldLen(req *api.ServiceAlias) (*api.Response, bool) { + if err := CheckDbStrFieldLen(req.GetService(), MaxDbServiceNameLength); err != nil { + return api.NewServiceAliasResponse(api.InvalidServiceName, req), true + } + if err := CheckDbStrFieldLen(req.GetNamespace(), MaxDbServiceNamespaceLength); err != nil { + return api.NewServiceAliasResponse(api.InvalidNamespaceName, req), true + } + if err := CheckDbStrFieldLen(req.GetAlias(), MaxDbServiceNameLength); err != nil { + return api.NewServiceAliasResponse(api.InvalidServiceAlias, req), true + } + if err := CheckDbStrFieldLen(req.GetComment(), MaxDbServiceCommentLength); err != nil { + return api.NewServiceAliasResponse(api.InvalidServiceAliasComment, req), true + } + if err := CheckDbStrFieldLen(req.GetOwners(), MaxDbServiceOwnerLength); err != nil { + return api.NewServiceAliasResponse(api.InvalidServiceAliasOwners, req), true + } + return nil, false +} diff --git a/naming/test/README.md b/naming/test/README.md new file mode 100644 index 000000000..baad60938 --- /dev/null +++ b/naming/test/README.md @@ -0,0 +1,13 @@ +# 接口测试 + +> 对业务逻辑进行完整性测试 + +## 依赖 +* 数据库地址,配置一个测试的数据库地址 + + + +## 执行 +``` +go test -v +``` \ No newline at end of file diff --git a/naming/test/circuitbreaker_config_test.go b/naming/test/circuitbreaker_config_test.go new file mode 100644 index 000000000..600ed52d6 --- /dev/null +++ b/naming/test/circuitbreaker_config_test.go @@ -0,0 +1,1308 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package test + +import ( + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/utils" + "github.com/polarismesh/polaris-server/naming" + "sync" + "testing" + "time" +) + +/** + * @brief 测试创建熔断规则 + */ +func TestCreateCircuitBreaker(t *testing.T) { + t.Run("正常创建熔断规则,返回成功", func(t *testing.T) { + circuitBreakerReq, circuitBreakerResp := createCommonCircuitBreaker(t, 0) + defer cleanCircuitBreaker(circuitBreakerResp.GetId().GetValue(), circuitBreakerResp.GetVersion().GetValue()) + checkCircuitBreaker(t, circuitBreakerReq, circuitBreakerReq, circuitBreakerResp) + }) + + t.Run("重复创建熔断规则,返回错误", func(t *testing.T) { + _, circuitBreakerResp := createCommonCircuitBreaker(t, 0) + defer cleanCircuitBreaker(circuitBreakerResp.GetId().GetValue(), circuitBreakerResp.GetVersion().GetValue()) + + if resp := server.CreateCircuitBreaker(defaultCtx, circuitBreakerResp); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("创建熔断规则,删除,再创建,返回成功", func(t *testing.T) { + _, circuitBreakerResp := createCommonCircuitBreaker(t, 0) + defer cleanCircuitBreaker(circuitBreakerResp.GetId().GetValue(), circuitBreakerResp.GetVersion().GetValue()) + deleteCircuitBreaker(t, circuitBreakerResp) + + newCircuitBreakerReq, newCircuitBreakerResp := createCommonCircuitBreaker(t, 0) + checkCircuitBreaker(t, newCircuitBreakerReq, newCircuitBreakerReq, newCircuitBreakerResp) + cleanCircuitBreaker(newCircuitBreakerResp.GetId().GetValue(), newCircuitBreakerResp.GetVersion().GetValue()) + }) + + t.Run("创建熔断规则时,没有传递负责人,返回错误", func(t *testing.T) { + circuitBreaker := &api.CircuitBreaker{} + if resp := server.CreateCircuitBreaker(defaultCtx, circuitBreaker); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("创建熔断规则时,没有传递规则名,返回错误", func(t *testing.T) { + circuitBreaker := &api.CircuitBreaker{ + Namespace: utils.NewStringValue(naming.DefaultNamespace), + Owners: utils.NewStringValue("test"), + } + if resp := server.CreateCircuitBreaker(defaultCtx, circuitBreaker); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("创建熔断规则时,没有传递命名空间,返回错误", func(t *testing.T) { + circuitBreaker := &api.CircuitBreaker{ + Name: utils.NewStringValue("name-test-1"), + Owners: utils.NewStringValue("test"), + } + if resp := server.CreateCircuitBreaker(defaultCtx, circuitBreaker); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("并发创建熔断规则,返回成功", func(t *testing.T) { + var wg sync.WaitGroup + for i := 0; i < 500; i++ { + wg.Add(1) + go func(index int) { + defer wg.Done() + _, circuitBreakerResp := createCommonCircuitBreaker(t, index) + cleanCircuitBreaker(circuitBreakerResp.GetId().GetValue(), circuitBreakerResp.GetVersion().GetValue()) + }(i) + } + wg.Wait() + }) +} + +/** + * @brief 测试创建熔断规则版本 + */ +func TestCreateCircuitBreakerVersion(t *testing.T) { + _, cbResp := createCommonCircuitBreaker(t, 0) + defer cleanCircuitBreaker(cbResp.GetId().GetValue(), cbResp.GetVersion().GetValue()) + + t.Run("正常创建熔断规则版本", func(t *testing.T) { + cbVersionReq, cbVersionResp := createCommonCircuitBreakerVersion(t, cbResp, 0) + defer cleanCircuitBreaker(cbResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + checkCircuitBreaker(t, cbVersionReq, cbVersionReq, cbVersionResp) + }) + + t.Run("传递id,正常创建熔断规则版本", func(t *testing.T) { + cbVersionReq := &api.CircuitBreaker{ + Id: cbResp.GetId(), + Version: utils.NewStringValue("test"), + Token: cbResp.GetToken(), + } + + resp := server.CreateCircuitBreakerVersion(defaultCtx, cbVersionReq) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + cbVersionResp := resp.GetCircuitBreaker() + + defer cleanCircuitBreaker(cbResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + + checkCircuitBreaker(t, cbVersionReq, cbVersionReq, cbVersionResp) + }) + + t.Run("传递name和namespace,正常创建熔断规则版本", func(t *testing.T) { + cbVersionReq := &api.CircuitBreaker{ + Version: utils.NewStringValue("test"), + Name: cbResp.GetName(), + Namespace: cbResp.GetNamespace(), + Token: cbResp.GetToken(), + } + + resp := server.CreateCircuitBreakerVersion(defaultCtx, cbVersionReq) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + cbVersionResp := resp.GetCircuitBreaker() + + defer cleanCircuitBreaker(cbResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + + checkCircuitBreaker(t, cbVersionReq, cbVersionReq, cbVersionResp) + }) + + t.Run("创建熔断规则版本,删除,再创建,返回成功", func(t *testing.T) { + cbVersionReq, cbVersionResp := createCommonCircuitBreakerVersion(t, cbResp, 0) + defer cleanCircuitBreaker(cbResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + + deleteCircuitBreaker(t, cbVersionResp) + cbVersionReq, cbVersionResp = createCommonCircuitBreakerVersion(t, cbResp, 0) + checkCircuitBreaker(t, cbVersionReq, cbVersionReq, cbVersionResp) + }) + + t.Run("为不存在的熔断规则创建版本,返回错误", func(t *testing.T) { + _, cbResp := createCommonCircuitBreaker(t, 1) + cleanCircuitBreaker(cbResp.GetId().GetValue(), cbResp.GetVersion().GetValue()) + + version := &api.CircuitBreaker{ + Id: cbResp.GetId(), + Version: utils.NewStringValue("test"), + Token: cbResp.GetToken(), + Owners: cbResp.GetOwners(), + } + + if resp := server.CreateCircuitBreakerVersion(defaultCtx, version); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("创建master版本的熔断规则,返回错误", func(t *testing.T) { + if resp := server.CreateCircuitBreakerVersion(defaultCtx, cbResp); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("创建熔断规则版本时,没有传递version,返回错误", func(t *testing.T) { + version := &api.CircuitBreaker{ + Id: cbResp.GetId(), + Token: cbResp.GetToken(), + Owners: cbResp.GetOwners(), + } + if resp := server.CreateCircuitBreakerVersion(defaultCtx, version); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("创建熔断规则版本时,没有传递token,返回错误", func(t *testing.T) { + version := &api.CircuitBreaker{ + Id: cbResp.GetId(), + Version: cbResp.GetVersion(), + Owners: cbResp.GetOwners(), + } + if resp := server.CreateCircuitBreakerVersion(defaultCtx, version); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("创建熔断规则版本时,没有传递name,返回错误", func(t *testing.T) { + version := &api.CircuitBreaker{ + Version: cbResp.GetVersion(), + Token: cbResp.GetToken(), + Namespace: cbResp.GetNamespace(), + } + if resp := server.CreateCircuitBreakerVersion(defaultCtx, version); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("创建熔断规则版本时,没有传递namespace,返回错误", func(t *testing.T) { + version := &api.CircuitBreaker{ + Version: cbResp.GetVersion(), + Token: cbResp.GetToken(), + Name: cbResp.GetName(), + } + if resp := server.CreateCircuitBreakerVersion(defaultCtx, version); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("并发创建同一个规则的多个版本,返回成功", func(t *testing.T) { + var wg sync.WaitGroup + for i := 0; i <= 500; i++ { + wg.Add(1) + go func(index int) { + defer wg.Done() + cbVersionReq, cbVersionResp := createCommonCircuitBreakerVersion(t, cbResp, index) + checkCircuitBreaker(t, cbVersionReq, cbVersionReq, cbVersionResp) + defer cleanCircuitBreaker(cbResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + }(i) + } + wg.Wait() + t.Log("pass") + }) +} + +/** + * @brief 删除熔断规则 + */ +func TestDeleteCircuitBreaker(t *testing.T) { + getCircuitBreakerVersions := func(t *testing.T, id string, expectNum uint32) { + filters := map[string]string{ + "id": id, + } + resp := server.GetCircuitBreakerVersions(filters) + if !respSuccess(resp) { + t.Fatal("error") + } + if resp.GetAmount().GetValue() != expectNum { + t.Fatalf("error, actual num is %d, expect num is %d", resp.GetAmount().GetValue(), expectNum) + } else { + t.Log("pass") + } + } + + t.Run("根据name和namespace删除master版本的熔断规则", func(t *testing.T) { + // 创建熔断规则 + _, cbResp := createCommonCircuitBreaker(t, 0) + defer cleanCircuitBreaker(cbResp.GetId().GetValue(), cbResp.GetVersion().GetValue()) + + // 创建熔断规则版本 + for i := 1; i <= 10; i++ { + _, cbVersionResp := createCommonCircuitBreakerVersion(t, cbResp, i) + defer cleanCircuitBreaker(cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + } + + rule := &api.CircuitBreaker{ + Version: cbResp.GetVersion(), + Name: cbResp.GetName(), + Namespace: cbResp.GetNamespace(), + Token: cbResp.GetToken(), + } + + deleteCircuitBreaker(t, rule) + getCircuitBreakerVersions(t, cbResp.GetId().GetValue(), 0) + }) + + t.Run("删除master版本的熔断规则", func(t *testing.T) { + // 创建熔断规则 + _, cbResp := createCommonCircuitBreaker(t, 0) + defer cleanCircuitBreaker(cbResp.GetId().GetValue(), cbResp.GetVersion().GetValue()) + + // 创建熔断规则版本 + for i := 1; i <= 10; i++ { + _, cbVersionResp := createCommonCircuitBreakerVersion(t, cbResp, i) + defer cleanCircuitBreaker(cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + } + + deleteCircuitBreaker(t, cbResp) + getCircuitBreakerVersions(t, cbResp.GetId().GetValue(), 0) + }) + + t.Run("删除非master版本的熔断规则", func(t *testing.T) { + // 创建熔断规则 + _, cbResp := createCommonCircuitBreaker(t, 0) + defer cleanCircuitBreaker(cbResp.GetId().GetValue(), cbResp.GetVersion().GetValue()) + + // 创建熔断规则版本 + _, cbVersionResp := createCommonCircuitBreakerVersion(t, cbResp, 0) + defer cleanCircuitBreaker(cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + + // 创建熔断规则版本 + for i := 1; i <= 10; i++ { + _, cbVersionResp := createCommonCircuitBreakerVersion(t, cbResp, i) + defer cleanCircuitBreaker(cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + } + + // 删除特定版本的熔断规则 + deleteCircuitBreaker(t, cbVersionResp) + + getCircuitBreakerVersions(t, cbResp.GetId().GetValue(), 1+10) + }) + + t.Run("根据name和namespace删除非master版本的熔断规则", func(t *testing.T) { + // 创建熔断规则 + _, cbResp := createCommonCircuitBreaker(t, 0) + defer cleanCircuitBreaker(cbResp.GetId().GetValue(), cbResp.GetVersion().GetValue()) + + // 创建熔断规则版本 + _, cbVersionResp := createCommonCircuitBreakerVersion(t, cbResp, 0) + defer cleanCircuitBreaker(cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + + // 创建熔断规则版本 + for i := 1; i <= 10; i++ { + _, cbVersionResp := createCommonCircuitBreakerVersion(t, cbResp, i) + defer cleanCircuitBreaker(cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + } + + // 删除特定版本的熔断规则 + rule := &api.CircuitBreaker{ + Version: cbVersionResp.GetVersion(), + Name: cbVersionResp.GetName(), + Namespace: cbVersionResp.GetNamespace(), + Token: cbVersionResp.GetToken(), + } + deleteCircuitBreaker(t, rule) + + getCircuitBreakerVersions(t, cbResp.GetId().GetValue(), 1+10) + }) + + t.Run("删除不存在的熔断规则,返回成功", func(t *testing.T) { + // 创建熔断规则 + _, cbResp := createCommonCircuitBreaker(t, 0) + cleanCircuitBreaker(cbResp.GetId().GetValue(), cbResp.GetVersion().GetValue()) + + deleteCircuitBreaker(t, cbResp) + getCircuitBreakerVersions(t, cbResp.GetId().GetValue(), 0) + }) + + t.Run("删除熔断规则时,没有传递token,返回错误", func(t *testing.T) { + // 创建熔断规则 + _, cbResp := createCommonCircuitBreaker(t, 0) + defer cleanCircuitBreaker(cbResp.GetId().GetValue(), cbResp.GetVersion().GetValue()) + + rule := &api.CircuitBreaker{ + Id: cbResp.GetId(), + Version: cbResp.GetVersion(), + } + + if resp := server.DeleteCircuitBreaker(defaultCtx, rule); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("删除熔断规则时,没有传递name和id,返回错误", func(t *testing.T) { + // 创建熔断规则 + _, cbResp := createCommonCircuitBreaker(t, 0) + defer cleanCircuitBreaker(cbResp.GetId().GetValue(), cbResp.GetVersion().GetValue()) + + rule := &api.CircuitBreaker{ + Version: cbResp.GetVersion(), + Namespace: cbResp.GetNamespace(), + Token: cbResp.GetToken(), + } + + if resp := server.DeleteCircuitBreaker(defaultCtx, rule); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("删除已发布的规则,返回错误", func(t *testing.T) { + // 创建服务 + _, serviceResp := createCommonService(t, 0) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + // 创建熔断规则 + _, cbResp := createCommonCircuitBreaker(t, 0) + defer cleanCircuitBreaker(cbResp.GetId().GetValue(), cbResp.GetVersion().GetValue()) + + // 创建熔断规则版本 + _, cbVersionResp := createCommonCircuitBreakerVersion(t, cbResp, 0) + defer cleanCircuitBreaker(cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + + // 发布熔断规则 + releaseCircuitBreaker(t, cbVersionResp, serviceResp) + defer cleanCircuitBreakerRelation(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue(), + cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + + // 删除master版本 + if resp := server.DeleteCircuitBreaker(defaultCtx, cbResp); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + + // 删除其他版本 + if resp := server.DeleteCircuitBreaker(defaultCtx, cbVersionResp); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("创建多个版本的规则,并发布其中一个规则,删除未发布规则,可以正常删除", func(t *testing.T) { + // 创建服务 + _, serviceResp := createCommonService(t, 0) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + // 创建熔断规则 + _, cbResp := createCommonCircuitBreaker(t, 0) + defer cleanCircuitBreaker(cbResp.GetId().GetValue(), cbResp.GetVersion().GetValue()) + + // 创建熔断规则版本 + _, cbVersionResp := createCommonCircuitBreakerVersion(t, cbResp, 0) + defer cleanCircuitBreaker(cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + + // 创建熔断规则版本 + _, newCbVersionResp := createCommonCircuitBreakerVersion(t, cbResp, 1) + defer cleanCircuitBreaker(newCbVersionResp.GetId().GetValue(), newCbVersionResp.GetVersion().GetValue()) + + // 发布熔断规则 + releaseCircuitBreaker(t, cbVersionResp, serviceResp) + defer cleanCircuitBreakerRelation(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue(), + cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + + deleteCircuitBreaker(t, newCbVersionResp) + getCircuitBreakerVersions(t, cbResp.GetId().GetValue(), 1+1) + }) + + t.Run("并发删除熔断规则,可以正常删除", func(t *testing.T) { + var wg sync.WaitGroup + for i := 1; i <= 500; i++ { + wg.Add(1) + go func(index int) { + defer wg.Done() + _, cbResp := createCommonCircuitBreaker(t, index) + defer cleanCircuitBreaker(cbResp.GetId().GetValue(), cbResp.GetVersion().GetValue()) + deleteCircuitBreaker(t, cbResp) + }(i) + } + wg.Wait() + t.Log("pass") + }) +} + +/** + * @brief 测试更新熔断规则 + */ +func TestUpdateCircuitBreaker(t *testing.T) { + // 创建熔断规则 + _, cbResp := createCommonCircuitBreaker(t, 0) + defer cleanCircuitBreaker(cbResp.GetId().GetValue(), cbResp.GetVersion().GetValue()) + + t.Run("更新master版本的熔断规则,返回成功", func(t *testing.T) { + cbResp.Inbounds = []*api.CbRule{} + updateCircuitBreaker(t, cbResp) + + filters := map[string]string{ + "id": cbResp.GetId().GetValue(), + "version": cbResp.GetVersion().GetValue(), + } + + resp := server.GetCircuitBreaker(filters) + if !respSuccess(resp) { + t.Fatal("error") + } + checkCircuitBreaker(t, cbResp, cbResp, resp.GetConfigWithServices()[0].GetCircuitBreaker()) + }) + + t.Run("没有更新任何字段,返回不需要更新", func(t *testing.T) { + if resp := server.UpdateCircuitBreaker(defaultCtx, cbResp); respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("没有传递任何可更新的字段,返回不需要更新", func(t *testing.T) { + rule := &api.CircuitBreaker{ + Id: cbResp.GetId(), + Version: cbResp.GetVersion(), + Token: cbResp.GetToken(), + } + if resp := server.UpdateCircuitBreaker(defaultCtx, rule); respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("负责人为空,返回错误", func(t *testing.T) { + rule := &api.CircuitBreaker{ + Id: cbResp.GetId(), + Version: cbResp.GetVersion(), + Token: cbResp.GetToken(), + Owners: utils.NewStringValue(""), + } + if resp := server.UpdateCircuitBreaker(defaultCtx, rule); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("更新其他版本的熔断规则,返回错误", func(t *testing.T) { + // 创建熔断规则版本 + _, cbVersionResp := createCommonCircuitBreakerVersion(t, cbResp, 0) + defer cleanCircuitBreaker(cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + + if resp := server.UpdateCircuitBreaker(defaultCtx, cbVersionResp); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("更新不存在的熔断规则,返回错误", func(t *testing.T) { + cleanCircuitBreaker(cbResp.GetId().GetValue(), cbResp.GetVersion().GetValue()) + if resp := server.UpdateCircuitBreaker(defaultCtx, cbResp); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("更新熔断规则时,没有传递token,返回错误", func(t *testing.T) { + rule := &api.CircuitBreaker{ + Id: cbResp.GetId(), + Version: cbResp.GetVersion(), + } + if resp := server.UpdateCircuitBreaker(defaultCtx, rule); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("并发更新熔断规则时,可以正常更新", func(t *testing.T) { + var wg sync.WaitGroup + for i := 1; i <= 500; i++ { + wg.Add(1) + go func(index int) { + defer wg.Done() + + // 创建熔断规则 + _, cbResp := createCommonCircuitBreaker(t, index) + defer cleanCircuitBreaker(cbResp.GetId().GetValue(), cbResp.GetVersion().GetValue()) + + cbResp.Owners = utils.NewStringValue(fmt.Sprintf("test-owner-%d", index)) + + updateCircuitBreaker(t, cbResp) + + filters := map[string]string{ + "id": cbResp.GetId().GetValue(), + "version": cbResp.GetVersion().GetValue(), + } + resp := server.GetCircuitBreaker(filters) + if !respSuccess(resp) { + t.Fatal("error") + } + checkCircuitBreaker(t, cbResp, cbResp, resp.GetConfigWithServices()[0].GetCircuitBreaker()) + }(i) + } + wg.Wait() + }) +} + +/** + * @brief 测试发布熔断规则 + */ +func TestReleaseCircuitBreaker(t *testing.T) { + // 创建服务 + _, serviceResp := createCommonService(t, 0) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + // 创建熔断规则 + _, cbResp := createCommonCircuitBreaker(t, 0) + defer cleanCircuitBreaker(cbResp.GetId().GetValue(), cbResp.GetVersion().GetValue()) + + // 创建熔断规则的版本 + _, cbVersionResp := createCommonCircuitBreakerVersion(t, cbResp, 0) + defer cleanCircuitBreaker(cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + + t.Run("正常发布熔断规则", func(t *testing.T) { + _ = server.Cache().Clear() + + releaseCircuitBreaker(t, cbVersionResp, serviceResp) + defer cleanCircuitBreakerRelation(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue(), + cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + + // 等待缓存更新 + time.Sleep(updateCacheInterval) + + resp := server.GetCircuitBreakerWithCache(defaultCtx, serviceResp) + checkCircuitBreaker(t, cbVersionResp, cbResp, resp.GetCircuitBreaker()) + }) + + t.Run("根据name和namespace发布熔断规则", func(t *testing.T) { + _ = server.Cache().Clear() + + rule := &api.CircuitBreaker{ + Version: cbVersionResp.GetVersion(), + Name: cbVersionResp.GetName(), + Namespace: cbVersionResp.GetNamespace(), + } + releaseCircuitBreaker(t, rule, serviceResp) + defer cleanCircuitBreakerRelation(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue(), + cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + + // 等待缓存更新 + time.Sleep(updateCacheInterval) + + resp := server.GetCircuitBreakerWithCache(defaultCtx, serviceResp) + checkCircuitBreaker(t, cbVersionResp, cbResp, resp.GetCircuitBreaker()) + }) + + t.Run("为同一个服务发布多条不同熔断规则", func(t *testing.T) { + _ = server.Cache().Clear() + + releaseCircuitBreaker(t, cbVersionResp, serviceResp) + defer cleanCircuitBreakerRelation(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue(), + cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + + // 创建熔断规则的版本 + _, cbVersionResp := createCommonCircuitBreakerVersion(t, cbResp, 1) + defer cleanCircuitBreaker(cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + + // 再次发布熔断规则 + releaseCircuitBreaker(t, cbVersionResp, serviceResp) + defer cleanCircuitBreakerRelation(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue(), + cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + + // 等待缓存更新 + time.Sleep(updateCacheInterval) + + resp := server.GetCircuitBreakerWithCache(defaultCtx, serviceResp) + checkCircuitBreaker(t, cbVersionResp, cbResp, resp.GetCircuitBreaker()) + }) + + t.Run("为不同服务发布相同熔断规则,返回成功", func(t *testing.T) { + _ = server.Cache().Clear() + + releaseCircuitBreaker(t, cbVersionResp, serviceResp) + defer cleanCircuitBreakerRelation(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue(), + cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + + // 创建服务 + _, serviceResp2 := createCommonService(t, 1) + defer cleanServiceName(serviceResp2.GetName().GetValue(), serviceResp2.GetNamespace().GetValue()) + + releaseCircuitBreaker(t, cbVersionResp, serviceResp2) + defer cleanCircuitBreakerRelation(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue(), + cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + + // 等待缓存更新 + time.Sleep(updateCacheInterval) + + resp := server.GetCircuitBreakerWithCache(defaultCtx, serviceResp) + checkCircuitBreaker(t, cbVersionResp, cbResp, resp.GetCircuitBreaker()) + + resp = server.GetCircuitBreakerWithCache(defaultCtx, serviceResp2) + checkCircuitBreaker(t, cbVersionResp, cbResp, resp.GetCircuitBreaker()) + }) + + t.Run("规则命名空间与服务命名空间不一致,返回错误", func(t *testing.T) { + release := &api.ConfigRelease{ + Service: &api.Service{ + Name: serviceResp.GetName(), + Namespace: utils.NewStringValue("Test"), + Token: serviceResp.GetToken(), + }, + CircuitBreaker: cbVersionResp, + } + + if resp := server.ReleaseCircuitBreaker(defaultCtx, release); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("为同一个服务发布多条相同熔断规则,返回错误", func(t *testing.T) { + releaseCircuitBreaker(t, cbVersionResp, serviceResp) + defer cleanCircuitBreakerRelation(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue(), + cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + + release := &api.ConfigRelease{ + Service: serviceResp, + CircuitBreaker: cbVersionResp, + } + + if resp := server.ReleaseCircuitBreaker(defaultCtx, release); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("发布熔断规则时,没有传递token,返回错误", func(t *testing.T) { + release := &api.ConfigRelease{ + Service: &api.Service{ + Name: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + }, + CircuitBreaker: cbVersionResp, + } + if resp := server.ReleaseCircuitBreaker(defaultCtx, release); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("发布服务不存在的熔断规则,返回错误", func(t *testing.T) { + _, serviceResp := createCommonService(t, 1) + cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + release := &api.ConfigRelease{ + Service: serviceResp, + CircuitBreaker: cbVersionResp, + } + if resp := server.ReleaseCircuitBreaker(defaultCtx, release); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("发布的熔断规则为master版本,返回错误", func(t *testing.T) { + release := &api.ConfigRelease{ + Service: serviceResp, + CircuitBreaker: cbResp, + } + if resp := server.ReleaseCircuitBreaker(defaultCtx, release); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("发布不存在的熔断规则,返回错误", func(t *testing.T) { + _, cbVersionResp := createCommonCircuitBreakerVersion(t, cbResp, 1) + cleanCircuitBreaker(cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + + release := &api.ConfigRelease{ + Service: serviceResp, + CircuitBreaker: cbVersionResp, + } + if resp := server.ReleaseCircuitBreaker(defaultCtx, release); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("并发发布同一个服务的熔断规则", func(t *testing.T) { + var wg sync.WaitGroup + for i := 1; i <= 500; i++ { + wg.Add(1) + go func(index int) { + defer wg.Done() + + _, cbVersionResp := createCommonCircuitBreakerVersion(t, cbResp, index) + defer cleanCircuitBreaker(cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + + releaseCircuitBreaker(t, cbVersionResp, serviceResp) + defer cleanCircuitBreakerRelation(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue(), + cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + }(i) + } + wg.Wait() + t.Log("pass") + }) +} + +/** + * @brief 测试解绑熔断规则 + */ +func TestUnBindCircuitBreaker(t *testing.T) { + // 创建服务 + _, serviceResp := createCommonService(t, 0) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + // 创建熔断规则 + _, cbResp := createCommonCircuitBreaker(t, 0) + defer cleanCircuitBreaker(cbResp.GetId().GetValue(), cbResp.GetVersion().GetValue()) + + // 创建熔断规则的版本 + _, cbVersionResp := createCommonCircuitBreakerVersion(t, cbResp, 0) + defer cleanCircuitBreaker(cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + + t.Run("正常解绑熔断规则", func(t *testing.T) { + _ = server.Cache().Clear() + + // 发布熔断规则 + releaseCircuitBreaker(t, cbVersionResp, serviceResp) + defer cleanCircuitBreakerRelation(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue(), + cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + + unBindCircuitBreaker(t, cbVersionResp, serviceResp) + + // 等待缓存更新 + time.Sleep(updateCacheInterval) + + resp := server.GetCircuitBreakerWithCache(defaultCtx, serviceResp) + if resp != nil && resp.GetCircuitBreaker() == nil { + t.Log("pass") + } else { + t.Fatalf("err is %+v", resp) + } + }) + + t.Run("解绑关系不存在的熔断规则, 返回成功", func(t *testing.T) { + _ = server.Cache().Clear() + + // 发布熔断规则 + releaseCircuitBreaker(t, cbVersionResp, serviceResp) + defer cleanCircuitBreakerRelation(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue(), + cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + + // 创建熔断规则的版本 + _, newCbVersionResp := createCommonCircuitBreakerVersion(t, cbResp, 1) + defer cleanCircuitBreaker(newCbVersionResp.GetId().GetValue(), newCbVersionResp.GetVersion().GetValue()) + + unBindCircuitBreaker(t, newCbVersionResp, serviceResp) + + // 等待缓存更新 + time.Sleep(updateCacheInterval) + + resp := server.GetCircuitBreakerWithCache(defaultCtx, serviceResp) + checkCircuitBreaker(t, cbVersionResp, cbResp, resp.GetCircuitBreaker()) + }) + + t.Run("解绑规则时没有传递token,返回错误", func(t *testing.T) { + unbind := &api.ConfigRelease{ + Service: &api.Service{ + Name: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + }, + CircuitBreaker: cbVersionResp, + } + + if resp := server.UnBindCircuitBreaker(defaultCtx, unbind); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("解绑服务不存在的熔断规则,返回错误", func(t *testing.T) { + _, serviceResp := createCommonService(t, 1) + cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + unbind := &api.ConfigRelease{ + Service: serviceResp, + CircuitBreaker: cbVersionResp, + } + + if resp := server.UnBindCircuitBreaker(defaultCtx, unbind); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("解绑规则不存在的熔断规则,返回错误", func(t *testing.T) { + // 创建熔断规则的版本 + _, cbVersionResp := createCommonCircuitBreakerVersion(t, cbResp, 1) + cleanCircuitBreaker(cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + + unbind := &api.ConfigRelease{ + Service: serviceResp, + CircuitBreaker: cbVersionResp, + } + + if resp := server.UnBindCircuitBreaker(defaultCtx, unbind); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("解绑master版本的熔断规则,返回错误", func(t *testing.T) { + unbind := &api.ConfigRelease{ + Service: serviceResp, + CircuitBreaker: cbResp, + } + + if resp := server.UnBindCircuitBreaker(defaultCtx, unbind); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("解绑熔断规则时没有传递name,返回错误", func(t *testing.T) { + unbind := &api.ConfigRelease{ + Service: serviceResp, + CircuitBreaker: &api.CircuitBreaker{ + Version: cbVersionResp.GetVersion(), + Namespace: cbVersionResp.GetNamespace(), + }, + } + + if resp := server.UnBindCircuitBreaker(defaultCtx, unbind); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("并发解绑熔断规则", func(t *testing.T) { + var wg sync.WaitGroup + for i := 1; i <= 500; i++ { + wg.Add(1) + go func(index int) { + defer wg.Done() + + // 创建服务 + _, serviceResp := createCommonService(t, index) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + // 发布熔断规则 + releaseCircuitBreaker(t, cbVersionResp, serviceResp) + defer cleanCircuitBreakerRelation(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue(), + cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + + unBindCircuitBreaker(t, cbVersionResp, serviceResp) + }(i) + } + wg.Wait() + t.Log("pass") + }) +} + +/** + * @brief 测试查询熔断规则 + */ +func TestGetCircuitBreaker(t *testing.T) { + versionNum := 10 + serviceNum := 2 + releaseVersion := &api.CircuitBreaker{} + deleteVersion := &api.CircuitBreaker{} + service := &api.Service{} + + // 创建熔断规则 + _, cbResp := createCommonCircuitBreaker(t, 0) + defer cleanCircuitBreaker(cbResp.GetId().GetValue(), cbResp.GetVersion().GetValue()) + + // 创建熔断规则版本 + for i := 1; i <= versionNum; i++ { + // 创建熔断规则的版本 + _, cbVersionResp := createCommonCircuitBreakerVersion(t, cbResp, i) + defer cleanCircuitBreaker(cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + + if i == 5 { + releaseVersion = cbVersionResp + } + + if i == versionNum { + deleteVersion = cbVersionResp + } + } + + // 删除一个版本的熔断规则 + deleteCircuitBreaker(t, deleteVersion) + + // 发布熔断规则 + for i := 1; i <= serviceNum; i++ { + _, serviceResp := createCommonService(t, i) + if i == 1 { + service = serviceResp + } + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + releaseCircuitBreaker(t, releaseVersion, serviceResp) + defer cleanCircuitBreakerRelation(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue(), + releaseVersion.GetId().GetValue(), releaseVersion.GetVersion().GetValue()) + } + + t.Run("测试获取熔断规则的所有版本", func(t *testing.T) { + filters := map[string]string{ + "id": cbResp.GetId().GetValue(), + } + + resp := server.GetCircuitBreakerVersions(filters) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if resp.GetAmount().GetValue() != resp.GetSize().GetValue() || + resp.GetSize().GetValue() != uint32(versionNum) || len(resp.GetConfigWithServices()) != versionNum { + t.Fatalf("amount is %d, size is %d, num is %d, expect num is %d", resp.GetAmount().GetValue(), + resp.GetSize().GetValue(), len(resp.GetConfigWithServices()), versionNum) + } + t.Logf("pass: num is %d", resp.GetSize().GetValue()) + }) + + t.Run("测试获取熔断规则创建过的版本", func(t *testing.T) { + filters := map[string]string{ + "id": cbResp.GetId().GetValue(), + } + + resp := server.GetReleaseCircuitBreakers(filters) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if resp.GetAmount().GetValue() != resp.GetSize().GetValue() || + resp.GetSize().GetValue() != uint32(serviceNum) { + t.Fatalf("amount is %d, size is %d, expect num is %d", resp.GetAmount().GetValue(), + resp.GetSize().GetValue(), versionNum) + } + t.Logf("pass: num is %d", resp.GetSize().GetValue()) + }) + + t.Run("测试获取指定版本的熔断规则", func(t *testing.T) { + filters := map[string]string{ + "id": releaseVersion.GetId().GetValue(), + "version": releaseVersion.GetVersion().GetValue(), + } + + resp := server.GetCircuitBreaker(filters) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + checkCircuitBreaker(t, releaseVersion, cbResp, resp.GetConfigWithServices()[0].GetCircuitBreaker()) + }) + + t.Run("根据服务获取绑定的熔断规则", func(t *testing.T) { + filters := map[string]string{ + "service": service.GetName().GetValue(), + "namespace": service.GetNamespace().GetValue(), + } + + resp := server.GetCircuitBreakerByService(filters) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + checkCircuitBreaker(t, releaseVersion, cbResp, resp.GetConfigWithServices()[0].GetCircuitBreaker()) + }) +} + +/** + * @brief 测试查询熔断规则 + */ +func TestGetCircuitBreaker2(t *testing.T) { + // 创建服务 + _, serviceResp := createCommonService(t, 0) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + // 创建熔断规则 + _, cbResp := createCommonCircuitBreaker(t, 0) + cleanCircuitBreaker(cbResp.GetId().GetValue(), cbResp.GetVersion().GetValue()) + + t.Run("熔断规则不存在,测试获取所有版本", func(t *testing.T) { + filters := map[string]string{ + "id": cbResp.GetId().GetValue(), + } + + resp := server.GetCircuitBreakerVersions(filters) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if resp.GetAmount().GetValue() != 0 || resp.GetSize().GetValue() != 0 || + len(resp.GetConfigWithServices()) != 0 { + t.Fatalf("amount is %d, size is %d, num is %d", resp.GetAmount().GetValue(), + resp.GetSize().GetValue(), len(resp.GetConfigWithServices())) + } + t.Logf("pass: resp is %+v, configServices is %+v", resp, resp.GetConfigWithServices()) + }) + + t.Run("熔断规则不存在,测试获取所有创建过的版本", func(t *testing.T) { + filters := map[string]string{ + "id": cbResp.GetId().GetValue(), + } + + resp := server.GetReleaseCircuitBreakers(filters) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if resp.GetAmount().GetValue() != 0 || resp.GetSize().GetValue() != 0 || + len(resp.GetConfigWithServices()) != 0 { + t.Fatalf("amount is %d, size is %d, num is %d", resp.GetAmount().GetValue(), + resp.GetSize().GetValue(), len(resp.GetConfigWithServices())) + } + t.Logf("pass: resp is %+v, configServices is %+v", resp, resp.GetConfigWithServices()) + }) + + t.Run("熔断规则不存在,测试获取指定版本的熔断规则", func(t *testing.T) { + filters := map[string]string{ + "id": cbResp.GetId().GetValue(), + "version": cbResp.GetVersion().GetValue(), + } + + resp := server.GetCircuitBreaker(filters) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if resp.GetAmount().GetValue() != 0 || resp.GetSize().GetValue() != 0 || + len(resp.GetConfigWithServices()) != 0 { + t.Fatalf("amount is %d, size is %d, num is %d", resp.GetAmount().GetValue(), + resp.GetSize().GetValue(), len(resp.GetConfigWithServices())) + } + t.Logf("pass: resp is %+v, configServices is %+v", resp, resp.GetConfigWithServices()) + }) + + t.Run("服务未绑定熔断规则,获取熔断规则", func(t *testing.T) { + filters := map[string]string{ + "service": serviceResp.GetName().GetValue(), + "namespace": serviceResp.GetNamespace().GetValue(), + } + + resp := server.GetCircuitBreakerByService(filters) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if resp.GetAmount().GetValue() != 0 || resp.GetSize().GetValue() != 0 || + len(resp.GetConfigWithServices()) != 0 { + t.Fatalf("amount is %d, size is %d, num is %d", resp.GetAmount().GetValue(), + resp.GetSize().GetValue(), len(resp.GetConfigWithServices())) + } + t.Logf("pass: resp is %+v, configServices is %+v", resp, resp.GetConfigWithServices()) + }) +} + +// test对CircuitBreaker字段进行校验 +func TestCheckCircuitBreakerFieldLen(t *testing.T) { + circuitBreaker := &api.CircuitBreaker{ + Name: utils.NewStringValue("name-test-123"), + Namespace: utils.NewStringValue(naming.DefaultNamespace), + Owners: utils.NewStringValue("owner-test"), + Comment: utils.NewStringValue("comment-test"), + Department: utils.NewStringValue("department-test"), + Business: utils.NewStringValue("business-test"), + } + t.Run("熔断名超长", func(t *testing.T) { + str := genSpecialStr(33) + oldName := circuitBreaker.Name + circuitBreaker.Name = utils.NewStringValue(str) + resp := server.CreateCircuitBreaker(defaultCtx, circuitBreaker) + circuitBreaker.Name = oldName + if resp.Code.Value != api.InvalidCircuitBreakerName { + t.Fatalf("%+v", resp) + } + }) + t.Run("熔断命名空间超长", func(t *testing.T) { + str := genSpecialStr(65) + oldNamespace := circuitBreaker.Namespace + circuitBreaker.Namespace = utils.NewStringValue(str) + resp := server.CreateCircuitBreaker(defaultCtx, circuitBreaker) + circuitBreaker.Namespace = oldNamespace + if resp.Code.Value != api.InvalidCircuitBreakerNamespace { + t.Fatalf("%+v", resp) + } + }) + t.Run("熔断business超长", func(t *testing.T) { + str := genSpecialStr(65) + oldBusiness := circuitBreaker.Business + circuitBreaker.Business = utils.NewStringValue(str) + resp := server.CreateCircuitBreaker(defaultCtx, circuitBreaker) + circuitBreaker.Business = oldBusiness + if resp.Code.Value != api.InvalidCircuitBreakerBusiness { + t.Fatalf("%+v", resp) + } + }) + t.Run("熔断部门超长", func(t *testing.T) { + str := genSpecialStr(1025) + oldDepartment := circuitBreaker.Department + circuitBreaker.Department = utils.NewStringValue(str) + resp := server.CreateCircuitBreaker(defaultCtx, circuitBreaker) + circuitBreaker.Department = oldDepartment + if resp.Code.Value != api.InvalidCircuitBreakerDepartment { + t.Fatalf("%+v", resp) + } + }) + t.Run("熔断comment超长", func(t *testing.T) { + str := genSpecialStr(1025) + oldComment := circuitBreaker.Comment + circuitBreaker.Comment = utils.NewStringValue(str) + resp := server.CreateCircuitBreaker(defaultCtx, circuitBreaker) + circuitBreaker.Comment = oldComment + if resp.Code.Value != api.InvalidCircuitBreakerComment { + t.Fatalf("%+v", resp) + } + }) + t.Run("熔断owner超长", func(t *testing.T) { + str := genSpecialStr(1025) + oldOwners := circuitBreaker.Owners + circuitBreaker.Owners = utils.NewStringValue(str) + resp := server.CreateCircuitBreaker(defaultCtx, circuitBreaker) + circuitBreaker.Owners = oldOwners + if resp.Code.Value != api.InvalidCircuitBreakerOwners { + t.Fatalf("%+v", resp) + } + }) + t.Run("发布熔断规则超长", func(t *testing.T) { + release := &api.ConfigRelease{ + Service: &api.Service{ + Name: utils.NewStringValue("test"), + Namespace: utils.NewStringValue("default"), + Token: utils.NewStringValue("test"), + }, + CircuitBreaker: &api.CircuitBreaker{ + Name: utils.NewStringValue("test"), + Namespace: utils.NewStringValue("default"), + Version: utils.NewStringValue("1.0"), + }, + } + t.Run("发布熔断规则服务名超长", func(t *testing.T) { + str := genSpecialStr(1025) + oldName := release.Service.Name + release.Service.Name = utils.NewStringValue(str) + resp := server.ReleaseCircuitBreaker(defaultCtx, release) + release.Service.Name = oldName + if resp.Code.Value != api.InvalidServiceName { + t.Fatalf("%+v", resp) + } + }) + t.Run("发布熔断规则服务命名空间超长", func(t *testing.T) { + str := genSpecialStr(1025) + oldNamespace := release.Service.Namespace + release.Service.Namespace = utils.NewStringValue(str) + resp := server.ReleaseCircuitBreaker(defaultCtx, release) + release.Service.Namespace = oldNamespace + if resp.Code.Value != api.InvalidNamespaceName { + t.Fatalf("%+v", resp) + } + }) + t.Run("发布熔断规则服务token超长", func(t *testing.T) { + str := genSpecialStr(2049) + oldToken := release.Service.Token + release.Service.Token = utils.NewStringValue(str) + resp := server.ReleaseCircuitBreaker(defaultCtx, release) + release.Service.Token = oldToken + if resp.Code.Value != api.InvalidServiceToken { + t.Fatalf("%+v", resp) + } + }) + t.Run("发布熔断规则熔断名超长", func(t *testing.T) { + str := genSpecialStr(1025) + oldName := release.CircuitBreaker.Name + release.CircuitBreaker.Name = utils.NewStringValue(str) + resp := server.ReleaseCircuitBreaker(defaultCtx, release) + release.CircuitBreaker.Name = oldName + if resp.Code.Value != api.InvalidCircuitBreakerName { + t.Fatalf("%+v", resp) + } + }) + t.Run("发布熔断规则熔断命名空间超长", func(t *testing.T) { + str := genSpecialStr(1025) + oldNamespace := release.CircuitBreaker.Namespace + release.CircuitBreaker.Namespace = utils.NewStringValue(str) + resp := server.ReleaseCircuitBreaker(defaultCtx, release) + release.CircuitBreaker.Namespace = oldNamespace + if resp.Code.Value != api.InvalidCircuitBreakerNamespace { + t.Fatalf("%+v", resp) + } + }) + t.Run("发布熔断规则熔断version超长", func(t *testing.T) { + str := genSpecialStr(1025) + oldVersion := release.CircuitBreaker.Version + release.CircuitBreaker.Version = utils.NewStringValue(str) + resp := server.ReleaseCircuitBreaker(defaultCtx, release) + release.CircuitBreaker.Version = oldVersion + if resp.Code.Value != api.InvalidCircuitBreakerVersion { + t.Fatalf("%+v", resp) + } + }) + }) + +} diff --git a/naming/test/client_test.go b/naming/test/client_test.go new file mode 100644 index 000000000..50b32e12b --- /dev/null +++ b/naming/test/client_test.go @@ -0,0 +1,302 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package test + +import ( + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/utils" + . "github.com/smartystreets/goconvey/convey" + "testing" + "time" +) + +// 测试client版本上报 +func TestReportClient(t *testing.T) { + Convey("可以进行正常的client上报", t, func() { + req := &api.Client{ + Host: utils.NewStringValue("127.0.0.1"), + Type: api.Client_SDK, + Version: utils.NewStringValue("v1.0.0"), + } + resp := server.ReportClient(defaultCtx, req) + So(respSuccess(resp), ShouldEqual, true) + }) +} + +// 测试discover instances +func TestDiscoverInstances(t *testing.T) { + Convey("服务发现测试", t, func() { + _, service := createCommonService(t, 5) + defer cleanServiceName(service.GetName().GetValue(), service.GetNamespace().GetValue()) + count := 5 + var instances []*api.Instance + var reqInstances []*api.Instance + defer func() { + for _, entry := range instances { + cleanInstance(entry.GetId().GetValue()) + } + }() + for i := 0; i < count; i++ { + req, instance := createCommonInstance(t, service, i) + instances = append(instances, instance) + reqInstances = append(reqInstances, req) + } + Convey("正常服务发现,返回的数据齐全", func() { + time.Sleep(updateCacheInterval) + out := server.ServiceInstancesCache(defaultCtx, service) + So(respSuccess(out), ShouldEqual, true) + So(len(out.GetInstances()), ShouldEqual, count) + for _, resp := range out.GetInstances() { + found := false + for _, req := range reqInstances { + if resp.GetHost().GetValue() == req.GetHost().GetValue() { + instanceCheck(t, req, resp) // expect actual + // 检查resp中必须包含额外的metadata + So(resp.Metadata["version"], ShouldEqual, req.GetVersion().GetValue()) + So(resp.Metadata["protocol"], ShouldEqual, req.GetProtocol().GetValue()) + found = true + t.Logf("%+v", resp) + break + } + } + So(found, ShouldEqual, true) + } + }) + Convey("service-metadata修改,revision会修改", func() { + out := server.ServiceInstancesCache(defaultCtx, service) + So(respSuccess(out), ShouldEqual, true) + oldRevision := out.GetService().GetRevision().GetValue() + + service.Metadata = make(map[string]string) + service.Metadata["new-metadata1"] = "1233" + service.Metadata["new-metadata2"] = "2342" + resp := server.UpdateService(defaultCtx, service) + time.Sleep(updateCacheInterval) + So(respSuccess(resp), ShouldEqual, true) + So(resp.GetService().GetRevision().GetValue(), ShouldNotEqual, oldRevision) + So(resp.GetService().GetMetadata()["new-metadata1"], ShouldEqual, "1233") + So(resp.GetService().GetMetadata()["new-metadata2"], ShouldEqual, "2342") + serviceCheck(t, service, resp.GetService()) + }) + }) +} + +// 测试discover ratelimit +func TestDiscoverRateLimits(t *testing.T) { + Convey("限流规则测试", t, func() { + _, service := createCommonService(t, 1) + defer cleanServiceName(service.GetName().GetValue(), service.GetNamespace().GetValue()) + _, rateLimitResp := createCommonRateLimit(t, service, 1) + defer cleanRateLimit(rateLimitResp.GetId().GetValue()) + defer cleanRateLimitRevision(service.GetName().GetValue(), service.GetNamespace().GetValue()) + Convey("正常获取限流规则", func() { + time.Sleep(updateCacheInterval) + out := server.GetRateLimitWithCache(defaultCtx, service) + So(respSuccess(out), ShouldEqual, true) + So(len(out.GetRateLimit().GetRules()), ShouldEqual, 1) + checkRateLimit(t, rateLimitResp, out.GetRateLimit().GetRules()[0]) + t.Logf("pass: out is %+v", out) + // 再次请求 + out = server.GetRateLimitWithCache(defaultCtx, out.GetService()) + So(respSuccess(out), ShouldEqual, true) + So(out.GetCode().GetValue(), ShouldEqual, api.DataNoChange) + t.Logf("pass: out is %+v", out) + }) + Convey("限流规则已删除", func() { + deleteRateLimit(t, rateLimitResp) + time.Sleep(updateCacheInterval) + out := server.GetRateLimitWithCache(defaultCtx, service) + So(respSuccess(out), ShouldEqual, true) + So(len(out.GetRateLimit().GetRules()), ShouldEqual, 0) + t.Logf("pass: out is %+v", out) + }) + }) +} + +// 测试discover ratelimit +func TestDiscoverRateLimits2(t *testing.T) { + Convey("限流规则异常测试", t, func() { + _, service := createCommonService(t, 1) + defer cleanServiceName(service.GetName().GetValue(), service.GetNamespace().GetValue()) + Convey("限流规则不存在", func() { + time.Sleep(updateCacheInterval) + out := server.GetRateLimitWithCache(defaultCtx, service) + So(respSuccess(out), ShouldEqual, true) + So(out.GetRateLimit(), ShouldBeNil) + t.Logf("pass: out is %+v", out) + }) + Convey("服务不存在", func() { + services := []*api.Service{service} + removeCommonServices(t, services) + time.Sleep(updateCacheInterval) + out := server.GetRateLimitWithCache(defaultCtx, service) + So(respSuccess(out), ShouldEqual, false) + t.Logf("pass: out is %+v", out) + }) + }) +} + +// 测试discover circuitbreaker +func TestDiscoverCircuitBreaker(t *testing.T) { + Convey("熔断规则测试", t, func() { + // 创建服务 + _, service := createCommonService(t, 1) + defer cleanServiceName(service.GetName().GetValue(), service.GetNamespace().GetValue()) + // 创建熔断规则 + _, cbResp := createCommonCircuitBreaker(t, 1) + defer cleanCircuitBreaker(cbResp.GetId().GetValue(), cbResp.GetVersion().GetValue()) + // 创建熔断规则版本 + _, cbVersionResp := createCommonCircuitBreakerVersion(t, cbResp, 1) + defer cleanCircuitBreaker(cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + // 发布熔断规则 + releaseCircuitBreaker(t, cbVersionResp, service) + defer cleanCircuitBreakerRelation(service.GetName().GetValue(), service.GetNamespace().GetValue(), + cbVersionResp.GetId().GetValue(), cbVersionResp.GetVersion().GetValue()) + + Convey("正常获取熔断规则", func() { + time.Sleep(updateCacheInterval) + out := server.GetCircuitBreakerWithCache(defaultCtx, service) + So(respSuccess(out), ShouldEqual, true) + checkCircuitBreaker(t, cbVersionResp, cbResp, out.GetCircuitBreaker()) + t.Logf("pass: out is %+v", out) + + // 再次请求 + out = server.GetCircuitBreakerWithCache(defaultCtx, out.GetService()) + So(respSuccess(out), ShouldEqual, true) + So(out.GetCode().GetValue(), ShouldEqual, api.DataNoChange) + t.Logf("pass: out is %+v", out) + }) + + Convey("解绑熔断规则", func() { + unBindCircuitBreaker(t, cbVersionResp, service) + time.Sleep(updateCacheInterval) + out := server.GetCircuitBreakerWithCache(defaultCtx, service) + So(respSuccess(out), ShouldEqual, true) + t.Logf("pass: out is %+v", out) + }) + }) +} + +// 测试discover circuitbreaker +func TestDiscoverCircuitBreaker2(t *testing.T) { + Convey("熔断规则异常测试", t, func() { + _, service := createCommonService(t, 1) + defer cleanServiceName(service.GetName().GetValue(), service.GetNamespace().GetValue()) + Convey("熔断规则不存在", func() { + time.Sleep(updateCacheInterval) + out := server.GetCircuitBreakerWithCache(defaultCtx, service) + So(respSuccess(out), ShouldEqual, true) + So(out.GetCircuitBreaker(), ShouldBeNil) + t.Logf("pass: out is %+v", out) + }) + Convey("服务不存在", func() { + services := []*api.Service{service} + removeCommonServices(t, services) + time.Sleep(updateCacheInterval) + out := server.GetCircuitBreakerWithCache(defaultCtx, service) + So(respSuccess(out), ShouldEqual, false) + t.Logf("pass: out is %+v", out) + }) + }) +} + +// 测试discover service +func TestDiscoverService(t *testing.T) { + Convey("服务测试", t, func() { + expectService1 := &api.Service{} + expectService2 := &api.Service{} + for id := 0; id < 5; id++ { + _, service := createCommonService(t, id) + if id == 3 { + expectService1 = service + } + if id == 4 { + expectService2 = service + } + defer cleanServiceName(service.GetName().GetValue(), service.GetNamespace().GetValue()) + } + + meta := make(map[string]string) + requestMeta := make(map[string]string) + for i := 0; i < 5; i++ { + k := fmt.Sprintf("key-%d", i) + v := fmt.Sprintf("value-%d", i) + if i == 0 || i == 1 { + requestMeta[k] = v + } + meta[k] = v + } + + expectService1.Metadata = meta + expectService2.Metadata = meta + _ = server.UpdateService(defaultCtx, expectService1) + _ = server.UpdateService(defaultCtx, expectService2) + time.Sleep(updateCacheInterval) + + Convey("正常获取服务", func() { + requestService := &api.Service{ + Metadata: requestMeta, + } + out := server.GetServiceWithCache(defaultCtx, requestService) + So(respSuccess(out), ShouldEqual, true) + if len(out.GetServices()) == 2 { + t.Logf("pass: out service is %+v", out.GetServices()) + } else { + t.Logf("error: out is %+v", out) + } + }) + + Convey("元数据匹配到的服务为空", func() { + requestMeta := make(map[string]string) + requestMeta["test"] = "test" + requestService := &api.Service{ + Metadata: requestMeta, + } + out := server.GetServiceWithCache(defaultCtx, requestService) + So(respSuccess(out), ShouldEqual, true) + if len(out.GetServices()) == 0 { + t.Logf("pass: out service is %+v", out.GetServices()) + } else { + t.Logf("error: out is %+v", out) + } + }) + }) +} + +// 测试discover service +func TestDiscoverService2(t *testing.T) { + Convey("服务异常测试", t, func() { + Convey("元数据不存在", func() { + service := &api.Service{} + out := server.GetServiceWithCache(defaultCtx, service) + So(respSuccess(out), ShouldEqual, false) + t.Logf("pass: out is %+v", out) + }) + Convey("元数据为空", func() { + service := &api.Service{ + Metadata: make(map[string]string), + } + out := server.GetServiceWithCache(defaultCtx, service) + So(respSuccess(out), ShouldEqual, false) + t.Logf("pass: out is %+v", out) + }) + }) +} + + diff --git a/naming/test/healthcheck_test.go b/naming/test/healthcheck_test.go new file mode 100644 index 000000000..ec4fc2856 --- /dev/null +++ b/naming/test/healthcheck_test.go @@ -0,0 +1,636 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package test + +import ( + "context" + "fmt" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/naming" + "math/rand" + "strconv" + "sync" + "testing" + "time" + + "github.com/polarismesh/polaris-server/common/utils" + + "github.com/polarismesh/polaris-server/naming/cache" + + api "github.com/polarismesh/polaris-server/common/api/v1" + + // 使用mysql库 + + _ "github.com/polarismesh/polaris-server/store/defaultStore" + _ "github.com/go-sql-driver/mysql" + "github.com/golang/protobuf/ptypes/wrappers" + "github.com/stretchr/testify/assert" +) + +// 测试样例结构体 +type Case struct { + field string + req *api.Instance + expect uint32 +} + +const ( + timeoutTimes = 2 +) + +// 测试健康检查功能未开启的情况 +func TestHealthCheckNotOpen(t *testing.T) { + _, serviceResp := createCommonService(t, 131) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + _, req := createInstanceNotOpenHealthCheck(t, serviceResp, 131) + req.ServiceToken = &wrappers.StringValue{Value: serviceResp.GetToken().GetValue()} + defer cleanInstance(req.GetId().GetValue()) + + var heartbeatOnDisabledIns uint32 = 400141 + wait4Cache() + rsp := server.Heartbeat(context.Background(), req) + fmt.Printf("actiual:%v", rsp.GetCode().GetValue()) + assert.EqualValues(t, heartbeatOnDisabledIns, rsp.GetCode().GetValue()) +} + +// 测试错误输入的情况 +func TestWrongInput(t *testing.T) { + // 输入样例 + cases := []Case{ + {"req", nil, api.EmptyRequest}, + {"service_token", &api.Instance{}, api.InvalidServiceToken}, + {"service", &api.Instance{ + Namespace: &wrappers.StringValue{Value: "n"}, + Host: &wrappers.StringValue{Value: "h"}, + Port: &wrappers.UInt32Value{Value: 1}, + HealthCheck: &api.HealthCheck{ + Heartbeat: &api.HeartbeatHealthCheck{Ttl: &wrappers.UInt32Value{Value: 1}}, + }, + ServiceToken: &wrappers.StringValue{Value: "t"}, + }, api.InvalidServiceName}, + {"namespace", &api.Instance{ + Service: &wrappers.StringValue{Value: "s"}, + Host: &wrappers.StringValue{Value: "h"}, + Port: &wrappers.UInt32Value{Value: 1}, + HealthCheck: &api.HealthCheck{ + Heartbeat: &api.HeartbeatHealthCheck{Ttl: &wrappers.UInt32Value{Value: 1}}, + }, + ServiceToken: &wrappers.StringValue{Value: "t"}, + }, api.InvalidNamespaceName}, + {"host", &api.Instance{ + Service: &wrappers.StringValue{Value: "s"}, + Namespace: &wrappers.StringValue{Value: "n"}, + Port: &wrappers.UInt32Value{Value: 1}, + HealthCheck: &api.HealthCheck{ + Heartbeat: &api.HeartbeatHealthCheck{Ttl: &wrappers.UInt32Value{Value: 1}}, + }, + ServiceToken: &wrappers.StringValue{Value: "t"}, + }, api.InvalidInstanceHost}, + {"port", &api.Instance{ + Service: &wrappers.StringValue{Value: "s"}, + Namespace: &wrappers.StringValue{Value: "n"}, + Host: &wrappers.StringValue{Value: "h"}, + HealthCheck: &api.HealthCheck{ + Heartbeat: &api.HeartbeatHealthCheck{Ttl: &wrappers.UInt32Value{Value: 1}}, + }, + ServiceToken: &wrappers.StringValue{Value: "t"}, + }, api.InvalidInstancePort}, + } + + for _, c := range cases { + func(c Case) { + t.Run(fmt.Sprintf("测试输入缺少%v的情况", c.field), func(t *testing.T) { + t.Parallel() + rsp := server.Heartbeat(context.Background(), c.req) + assert.EqualValues(t, c.expect, rsp.GetCode().GetValue()) + }) + }(c) + } + + t.Run("测试传入非法token的情况", func(t *testing.T) { + t.Parallel() + var ( + req *api.Instance + rsp *api.Response + index = 1006 + ) + _, serviceResp := createCommonService(t, index) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + _, req = createCommonInstance(t, serviceResp, index) + defer cleanInstance(req.GetId().GetValue()) + req.ServiceToken = &wrappers.StringValue{Value: serviceResp.GetToken().GetValue()} + wait4Cache() + req.ServiceToken = &wrappers.StringValue{Value: "err token"} + rsp = server.Heartbeat(context.Background(), req) + assert.EqualValues(t, api.Unauthorized, rsp.GetCode().GetValue()) + }) +} + +// 测试输入正确的情况 +func TestHealthCheckInputRight(t *testing.T) { + t.Run("测试输入正确,使用id为key的情况", func(t *testing.T) { + var ( + req *api.Instance + rsp *api.Response + index = 15 + ) + _, serviceResp := createCommonService(t, index) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + _, req = createCommonInstance(t, serviceResp, index) + defer cleanInstance(req.GetId().GetValue()) + wait4Cache() + req.Service = nil + req.ServiceToken = &wrappers.StringValue{Value: serviceResp.GetToken().GetValue()} + rsp = server.Heartbeat(context.Background(), req) + assert.EqualValues(t, api.ExecuteSuccess, rsp.GetCode().GetValue()) + assert.True(t, getHealthStatus(req.GetHost().GetValue(), int(req.GetPort().GetValue()))) + }) + + t.Run("测试输入正确,使用四元组为key的情况", func(t *testing.T) { + var ( + req *api.Instance + rsp *api.Response + index = 16 + ) + _, serviceResp := createCommonService(t, index) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + _, req = createCommonInstance(t, serviceResp, index) + defer cleanInstance(req.GetId().GetValue()) + wait4Cache() + req.Id = nil + req.ServiceToken = &wrappers.StringValue{Value: serviceResp.GetToken().GetValue()} + rsp = server.Heartbeat(context.Background(), req) + assert.EqualValues(t, api.ExecuteSuccess, rsp.GetCode().GetValue()) + assert.True(t, getHealthStatus(req.GetHost().GetValue(), int(req.GetPort().GetValue()))) + }) +} + +// 测试健康状态的变化 +func TestTurnUnhealthy(t *testing.T) { + t.Run("测试从健康变为不健康", turnUnhealthy) +} + +// 测试ttl增加 +func TestTtlIncrease(t *testing.T) { + t.Run("测试ttl增加", ttlIncrease) +} + +// 测试ttl减少 +func TestTtlDecrease(t *testing.T) { + t.Run("测试ttl减少", ttlDecrease) +} + +// 从健康转变为不健康 +func turnUnhealthy(t *testing.T) { + //t.Parallel() + var ( + req *api.Instance + rsp *api.Response + ttl uint32 = 1 + index = 6001 + ) + _, serviceResp := createCommonService(t, index) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + _, req = createCommonInstance(t, serviceResp, index) + req.ServiceToken = &wrappers.StringValue{Value: serviceResp.GetToken().GetValue()} + defer cleanInstance(req.GetId().GetValue()) + updateTTL(t, req, ttl) + wait4Cache() + rsp = server.Heartbeat(context.Background(), req) + assert.EqualValues(t, api.ExecuteSuccess, rsp.GetCode().GetValue()) + time.Sleep(time.Duration(ttl) * time.Second) + assert.True(t, getHealthStatus(req.GetHost().GetValue(), int(req.GetPort().GetValue()))) + + rsp = server.Heartbeat(context.Background(), req) + assert.EqualValues(t, api.ExecuteSuccess, rsp.GetCode().GetValue()) + time.Sleep(time.Duration(ttl) * time.Second) + assert.True(t, getHealthStatus(req.GetHost().GetValue(), int(req.GetPort().GetValue()))) + + // 停止发送心跳,等到超时时间到了后,该实例变为不健康 + time.Sleep(time.Duration((timeoutTimes+1)*ttl+3) * time.Second) + assert.False(t, getHealthStatus(req.GetHost().GetValue(), int(req.GetPort().GetValue()))) +} + +// 多次心跳,ttl增加 +func ttlIncrease(t *testing.T) { + //t.Parallel() + var ( + req *api.Instance + rsp *api.Response + ttl uint32 = 1 + anotherTTL uint32 = 3 + index = 50002 + ) + + _, serviceResp := createCommonService(t, index) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + _, req = createCommonInstance(t, serviceResp, index) + req.ServiceToken = &wrappers.StringValue{Value: serviceResp.GetToken().GetValue()} + defer cleanInstance(req.GetId().GetValue()) + updateTTL(t, req, ttl) + wait4Cache() + + rsp = server.Heartbeat(context.Background(), req) + assert.EqualValues(t, api.ExecuteSuccess, rsp.GetCode().GetValue()) + // 更新ttl + updateTTL(t, req, anotherTTL) + + time.Sleep(time.Second) + rsp = server.Heartbeat(context.Background(), req) + if rsp.GetCode().GetValue() != api.ExecuteSuccess { + t.Errorf("heartBeat err:%s", rsp.GetInfo().GetValue()) + } + assert.EqualValues(t, api.ExecuteSuccess, rsp.GetCode().GetValue()) + + timeoutSec := timeoutTimes*anotherTTL + 1 + oldTimeoutSec := timeoutTimes*ttl + 1 + // 确保旧超时时间后,按照新的ttl来计算还未超时 + assert.Greater(t, timeoutSec, oldTimeoutSec) + + // 等待旧超时时间过去,此时实例应该还未超时 + time.Sleep(time.Duration(oldTimeoutSec) * time.Second) + assert.True(t, getHealthStatus(req.GetHost().GetValue(), int(req.GetPort().GetValue()))) + + // 再等待达到新超时时间,此时实例应该超时 + time.Sleep(time.Duration(timeoutSec+5) * time.Second) + assert.False(t, getHealthStatus(req.GetHost().GetValue(), int(req.GetPort().GetValue()))) +} + +// 多次心跳,ttl减少 +func ttlDecrease(t *testing.T) { + //t.Parallel() + var ( + req *api.Instance + // instance *api.Instance + rsp *api.Response + ttl uint32 = 3 + anotherTTL uint32 = 1 + index = 50003 + ) + _, serviceResp := createCommonService(t, index) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + _, req = createCommonInstance(t, serviceResp, index) + defer cleanInstance(req.GetId().GetValue()) + req.ServiceToken = &wrappers.StringValue{Value: serviceResp.GetToken().GetValue()} + updateTTL(t, req, ttl) + wait4Cache() + + rsp = server.Heartbeat(context.Background(), req) + assert.EqualValues(t, api.ExecuteSuccess, rsp.GetCode().GetValue()) + + // 更新ttl + updateTTL(t, req, anotherTTL) + wait4Cache() + + rsp = server.Heartbeat(context.Background(), req) + assert.EqualValues(t, api.ExecuteSuccess, rsp.GetCode().GetValue()) + + timeoutSec := timeoutTimes*anotherTTL + 2 + oldTimeoutSec := timeoutTimes * ttl + // 确保超时时间后,按照旧的ttl来计算还未超时 + assert.Less(t, timeoutSec, oldTimeoutSec) + + // 等待旧超时时间过去,此时实例应该就已经超时 + time.Sleep(time.Duration(oldTimeoutSec) * time.Second) + assert.False(t, getHealthStatus(req.GetHost().GetValue(), int(req.GetPort().GetValue()))) +} + +// 获取实例健康状态 +func getHealthStatus(host string, port int) bool { + query := map[string]string{"limit": "20", "host": host, "port": strconv.Itoa(port)} + rsp := server.GetInstances(query) + if !respSuccess(rsp) { + panic("寻找实例失败") + } + instances := rsp.GetInstances() + if len(instances) != 1 { + panic(fmt.Sprintf("找到的实例不唯一,已找到%v个实例", len(instances))) + } + return instances[0].GetHealthy().GetValue() +} + +// 获取实例健康状态 +func getHealthStatusByID(id string) bool { + ins := server.Cache().Instance().GetInstance(id) + if ins == nil { + panic("寻找实例失败") + } + + return ins.Proto.Healthy.Value +} + +// 等待cache加载数据,在创建实例后需要使用 +func wait4Cache() { + time.Sleep(2 * cache.UpdateCacheInterval) +} + +// 更新实例的ttl +func updateTTL(t *testing.T, instance *api.Instance, ttl uint32) { + instance.HealthCheck = &api.HealthCheck{Heartbeat: &api.HeartbeatHealthCheck{Ttl: utils.NewUInt32Value(ttl)}} + if resp := server.UpdateInstance(defaultCtx, instance); !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } +} + +// 获取实例的ttl +func getTTL(t *testing.T, id string) uint32 { + insCache := server.Cache().Instance().GetInstance(id) + if insCache == nil { + return 0 + } + return insCache.HealthCheck().Heartbeat.Ttl.Value +} + +// 测试存在不合法的实例的情况 +func TestInvalidHealthInstance(t *testing.T) { + t.Run("测试不存在实例的情况", func(t *testing.T) { + t.Parallel() + var ( + req *api.Instance + rsp *api.Response + index = 1004 + ) + _, serviceResp := createCommonService(t, index) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + _, req = createCommonInstance(t, serviceResp, index) + // 创建一个实例,然后将其删除 + cleanInstance(req.GetId().GetValue()) + wait4Cache() + req.ServiceToken = &wrappers.StringValue{Value: serviceResp.GetToken().GetValue()} + rsp = server.Heartbeat(context.Background(), req) + assert.EqualValues(t, api.NotFoundResource, rsp.GetCode().GetValue()) + }) + + t.Run("测试不存在service的情况", func(t *testing.T) { + t.Parallel() + var ( + req *api.Instance + rsp *api.Response + index = 1007 + ) + _, serviceResp := createCommonService(t, index) + _, req = createCommonInstance(t, serviceResp, index) + defer cleanInstance(req.GetId().GetValue()) + // 删除服务 + cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + wait4Cache() + req.ServiceToken = &wrappers.StringValue{Value: serviceResp.GetToken().GetValue()} + rsp = server.Heartbeat(context.Background(), req) + assert.EqualValues(t, api.NotFoundResource, rsp.GetCode().GetValue()) + }) + + t.Run("测试存在ttl非法的实例的情况", func(t *testing.T) { + t.Parallel() + var ( + req *api.Instance + rsp *api.Response + index = 1005 + ) + _, serviceResp := createCommonService(t, index) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + _, req = createCommonInstance(t, serviceResp, index) + defer cleanInstance(req.GetId().GetValue()) + req.ServiceToken = &wrappers.StringValue{Value: serviceResp.GetToken().GetValue()} + updateTTL(t, req, 123123) + wait4Cache() + + ttl := getTTL(t, req.GetId().GetValue()) + assert.EqualValues(t, ttl, 5) + + rsp = server.Heartbeat(context.Background(), req) + assert.EqualValues(t, api.ExecuteSuccess, rsp.GetCode().GetValue()) + }) +} + +// 测试大量heartbeat用时 +func TestHeartBeatUseTime(t *testing.T) { + t.Run("测试大量heartbeat用时", heartBeatUseTime) +} + +// 测试大量heartbeat用时 +func heartBeatUseTime(t *testing.T) { + heartBeatBatch(t, 10, 80) +} + +// 模拟正常多实例心跳上报情境 +func heartBeatBatch(t *testing.T, serviceNum, insNum int) { + var ( + req *api.Instance + index = 10000 + insArray = make([]*api.Instance, 0) + wg sync.WaitGroup + mu sync.Mutex + ) + + start := time.Now() + // 创建服务和实例 + for i := 0; i < serviceNum; i++ { + var wgt sync.WaitGroup + _, serviceResp := createCommonService(t, index+i) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + for j := 0; j < insNum; j++ { + go func(index int, serviceResp *api.Service) { + wgt.Add(1) + defer wgt.Done() + _, req = createCommonInstance(t, serviceResp, index) + req.ServiceToken = &wrappers.StringValue{Value: serviceResp.GetToken().GetValue()} + + mu.Lock() + insArray = append(insArray, req) + mu.Unlock() + }(index+i+j, serviceResp) + //睡眠0.1毫秒,削峰 + time.Sleep(time.Microsecond * 100) + } + wgt.Wait() + time.Sleep(100 * time.Millisecond) + } + t.Logf("create use time:%+v", time.Now().Sub(start)) + time.Sleep(time.Second * 2) + + wg.Add(len(insArray)) + exceedNum := 0 + now := time.Now() + for _, ins := range insArray { + go func(ins *api.Instance) { + resp := server.Heartbeat(context.Background(), ins) + if resp.GetCode().GetValue() != api.ExecuteSuccess { + exceedNum++ + } + wg.Done() + }(ins) + time.Sleep(time.Microsecond) + } + wg.Wait() + t.Logf("first, use time:%v, exceedNum:%d", time.Now().Sub(now), exceedNum) + + time.Sleep(time.Second * 3) + wg.Add(len(insArray)) + exceedNum = 0 + now = time.Now() + for _, ins := range insArray { + go func(ins *api.Instance) { + resp := server.Heartbeat(context.Background(), ins) + if resp.GetCode().GetValue() != api.ExecuteSuccess { + exceedNum++ + } + wg.Done() + }(ins) + time.Sleep(time.Microsecond) + } + wg.Wait() + t.Logf("third, use time:%v, exceedNum:%d", time.Now().Sub(now), exceedNum) + t.Logf("len:%d", len(insArray)) + + for _, ins := range insArray { + assert.True(t, getHealthStatusByID(ins.GetId().GetValue())) + } + ttl := 5 + time.Sleep(time.Duration((timeoutTimes+2)*ttl+4) * time.Second) + wait4Cache() + time.Sleep(20 * time.Second) + for _, ins := range insArray { + assert.False(t, getHealthStatusByID(ins.GetId().GetValue())) + } + for _, ins := range insArray { + cleanInstance(ins.GetId().GetValue()) + } +} + +// 测试ckv+节点变更 +func TestCkvNodeChange(t *testing.T) { + t.Logf("第一次测试心跳") + heartBeatBatch(t, 8, 20) + + name := cfg.Naming.HealthCheck.KvServiceName + namespace := cfg.Naming.HealthCheck.KvNamespace + service := server.Cache().Service().GetServiceByName(name, namespace) + if service == nil { + t.Fatalf("cannot get service, name:%s, namespace:%s", name, namespace) + } + instances := server.Cache().Instance().GetInstancesByServiceID(service.ID) + if len(instances) == 0 { + t.Fatalf("cannot get instance, name:%s, namespace:%s", name, namespace) + } + t.Logf("len:%d, instaces:%+v", len(instances), instances[0]) + + _ = server.Cache().Clear() // 为了防止影响,每个函数需要把缓存的内容清空 + creq := &api.Instance{ + ServiceToken: utils.NewStringValue(service.Token), + Id: utils.NewStringValue(instances[0].ID()), + } + // 节点增加 + t.Logf("ckv节点增加") + addInstance(t, ins2Api(instances[0], service.Token, service.Name, service.Namespace)) + time.Sleep(30 * time.Second) + t.Logf("再次测试心跳") + heartBeatBatch(t, 10, 20) + + // 节点减少 + t.Logf("ckv节点减少") + resp := server.DeleteInstance(defaultCtx, creq) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + time.Sleep(30 * time.Second) + t.Logf("再次测试心跳") + heartBeatBatch(t, 10, 20) + + t.Logf("ok") +} + +func ins2Api(ins *model.Instance, token, name, namespace string) *api.Instance { + rand.Seed(time.Now().UnixNano()) + return &api.Instance{ + ServiceToken: utils.NewStringValue(token), + Service: utils.NewStringValue(name), + Namespace: utils.NewStringValue(namespace), + VpcId: utils.NewStringValue(strconv.Itoa(rand.Intn(10000))), + Host: utils.NewStringValue(ins.Host()), + Port: utils.NewUInt32Value(ins.Port()), + Protocol: utils.NewStringValue(ins.Protocol()), + Version: utils.NewStringValue(ins.Version()), + Priority: utils.NewUInt32Value(ins.Priority()), + Weight: utils.NewUInt32Value(ins.Weight()), + HealthCheck: ins.HealthCheck(), + Healthy: utils.NewBoolValue(ins.Healthy()), + Isolate: utils.NewBoolValue(ins.Isolate()), + LogicSet: utils.NewStringValue(ins.LogicSet()), + Metadata: ins.Metadata(), + } +} + +// 新增一个不开启健康检查的实例 +func createInstanceNotOpenHealthCheck(t *testing.T, service *api.Service, id int) ( + *api.Instance, *api.Instance) { + instanceReq := &api.Instance{ + ServiceToken: utils.NewStringValue(service.GetToken().GetValue()), + Service: utils.NewStringValue(service.GetName().GetValue()), + Namespace: utils.NewStringValue(service.GetNamespace().GetValue()), + VpcId: utils.NewStringValue(fmt.Sprintf("vpcid-%d", id)), + Host: utils.NewStringValue(fmt.Sprintf("10.10.10.%d", id)), + Port: utils.NewUInt32Value(8000 + uint32(id)), + Protocol: utils.NewStringValue(fmt.Sprintf("protocol-%d", id)), + Version: utils.NewStringValue(fmt.Sprintf("version-%d", id)), + Priority: utils.NewUInt32Value(1 + uint32(id)%10), + Weight: utils.NewUInt32Value(1 + uint32(id)%1000), + HealthCheck: nil, + Healthy: utils.NewBoolValue(false), // 默认是非健康,因为打开了healthCheck + Isolate: utils.NewBoolValue(false), + LogicSet: utils.NewStringValue(fmt.Sprintf("logic-set-%d", id)), + Metadata: map[string]string{ + "internal-personal-xxx": fmt.Sprintf("internal-personal-xxx_%d", id), + "2my-meta": fmt.Sprintf("my-meta-%d", id), + "my-meta-a1": "1111", + "smy-xmeta-h2": "2222", + "my-1meta-o3": "2222", + "my-2meta-4c": "2222", + "my-3meta-d5": "2222", + "dmy-meta-6p": "2222", + "1my-pmeta-d7": "2222", + "my-dmeta-8c": "2222", + "my-xmeta-9p": "2222", + "other-meta-x": "xxx", + "other-meta-1": "xx11", + "amy-instance": "my-instance", + "very-long-key-data-xxxxxxxxx": "Y", + "very-long-key-data-uuuuuuuuu": "P", + }, + } + + resp := server.CreateInstance(defaultCtx, instanceReq) + if respSuccess(resp) { + return instanceReq, resp.GetInstance() + } + + if resp.GetCode().GetValue() != api.ExistedResource { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + + // repeated + InstanceID, _ := naming.CalculateInstanceID(instanceReq.GetNamespace().GetValue(), instanceReq.GetService().GetValue(), + instanceReq.GetVpcId().GetValue(), instanceReq.GetHost().GetValue(), instanceReq.GetPort().GetValue()) + cleanInstance(InstanceID) + t.Logf("repeatd create instance(%s)", InstanceID) + resp = server.CreateInstance(defaultCtx, instanceReq) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + + return instanceReq, resp.GetInstance() +} diff --git a/naming/test/instance_test.go b/naming/test/instance_test.go new file mode 100644 index 000000000..0dc9f6c18 --- /dev/null +++ b/naming/test/instance_test.go @@ -0,0 +1,1696 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package test + +import ( + "context" + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/utils" + "github.com/polarismesh/polaris-server/naming" + . "github.com/smartystreets/goconvey/convey" + "math/rand" + "strconv" + "sync" + "sync/atomic" + "testing" + "time" +) + +// 测试新建实例 +func TestCreateInstance(t *testing.T) { + _, serviceResp := createCommonService(t, 100) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + t.Run("正常创建实例", func(t *testing.T) { + instanceReq, instanceResp := createCommonInstance(t, serviceResp, 1000) + defer cleanInstance(instanceResp.GetId().GetValue()) + + if instanceResp.GetId().GetValue() != "" { + t.Logf("pass: %s", instanceResp.GetId().GetValue()) + } else { + t.Fatalf("error") + } + + if instanceResp.GetNamespace().GetValue() == instanceReq.GetNamespace().GetValue() && + instanceResp.GetService().GetValue() == instanceReq.GetService().GetValue() { + t.Logf("pass") + } else { + t.Fatalf("error: %+v", instanceResp) + } + }) + + t.Run("重复注册,会返回资源已经存在", func(t *testing.T) { + req, instanceResp := createCommonInstance(t, serviceResp, 1000) + defer cleanInstance(instanceResp.GetId().GetValue()) + + time.Sleep(time.Second) + resp := server.CreateInstance(defaultCtx, req) + if !respSuccess(resp) && resp.GetCode().GetValue() == api.ExistedResource { + t.Logf("pass: %+v", resp) + } else { + t.Fatalf("error: %+v", resp) + } + if resp.Instance.GetId().GetValue() == "" { + t.Fatalf("error: %+v", resp) + } + }) + + t.Run("instance有metadata个数和字符要求的限制", func(t *testing.T) { + instanceReq := &api.Instance{ + ServiceToken: utils.NewStringValue(serviceResp.GetToken().GetValue()), + Service: utils.NewStringValue(serviceResp.GetName().GetValue()), + Namespace: utils.NewStringValue(serviceResp.GetNamespace().GetValue()), + Host: utils.NewStringValue("123"), + Port: utils.NewUInt32Value(456), + Metadata: make(map[string]string), + } + for i := 0; i < naming.MaxMetadataLength+1; i++ { + instanceReq.Metadata[fmt.Sprintf("%d", i)] = fmt.Sprintf("%d", i) + } + if resp := server.CreateInstance(defaultCtx, instanceReq); respSuccess(resp) { + t.Fatalf("error") + } else { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } + }) + t.Run("healthcheck为空测试", func(t *testing.T) { + instanceReq := &api.Instance{ + ServiceToken: utils.NewStringValue(serviceResp.GetToken().GetValue()), + Service: utils.NewStringValue(serviceResp.GetName().GetValue()), + Namespace: utils.NewStringValue(serviceResp.GetNamespace().GetValue()), + Host: utils.NewStringValue("aaaaaaaaaaaaaa"), + Port: utils.NewUInt32Value(456), + HealthCheck: &api.HealthCheck{}, + } + resp := server.CreateInstance(defaultCtx, instanceReq) + if !respSuccess(resp) { + t.Fatalf("error: %+v", resp) + } + defer cleanInstance(resp.GetInstance().GetId().GetValue()) + + time.Sleep(time.Second) + cleanInstance(resp.GetInstance().GetId().GetValue()) + instanceReq.HealthCheck = &api.HealthCheck{ + Heartbeat: &api.HeartbeatHealthCheck{}, + } + resp = server.CreateInstance(defaultCtx, instanceReq) + if !respSuccess(resp) { + t.Fatalf("error: %+v", resp) + } + getResp := server.GetInstances(map[string]string{"host": instanceReq.GetHost().GetValue()}) + t.Logf("%+v", getResp) + if getResp.GetInstances()[0].HealthCheck.Type != api.HealthCheck_HEARTBEAT { + t.Fatalf("error") + } + if getResp.GetInstances()[0].HealthCheck.Heartbeat.Ttl.Value != naming.DefaultTLL { + t.Fatalf("error") + } + }) +} + +// 测试异常场景 +func TestCreateInstanceWithNoService(t *testing.T) { + t.Run("无权限注册,可以捕获正常的错误", func(t *testing.T) { + _, serviceResp := createCommonService(t, 900) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + var reqs []*api.Instance + reqs = append(reqs, &api.Instance{ + Service: serviceResp.Name, + Namespace: serviceResp.Namespace, + ServiceToken: serviceResp.Token, + Host: utils.NewStringValue("1111"), + Port: utils.NewUInt32Value(0), + }) + reqs = append(reqs, &api.Instance{ + Service: serviceResp.Name, + Namespace: serviceResp.Namespace, + ServiceToken: utils.NewStringValue("error token"), + Host: utils.NewStringValue("1111"), + Port: utils.NewUInt32Value(1), + }) + resps := server.CreateInstances(defaultCtx, reqs) + if respSuccess(resps) { + t.Fatalf("error") + } + if resps.Responses[0].GetCode().GetValue() != api.ExecuteSuccess { + t.Fatalf("error: %d", resps.Responses[0].GetCode().GetValue()) + } + defer cleanInstance(resps.Responses[0].GetInstance().GetId().GetValue()) + if resps.Responses[1].GetCode().GetValue() != api.Unauthorized { + t.Fatalf("error: %d", resps.Responses[0].GetCode().GetValue()) + } + }) +} + +// 并发注册 +func TestCreateInstance2(t *testing.T) { + t.Run("并发注册,可以正常注册", func(t *testing.T) { + var serviceResps []*api.Service + for i := 0; i < 10; i++ { + _, serviceResp := createCommonService(t, i) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + serviceResps = append(serviceResps, serviceResp) + } + + time.Sleep(updateCacheInterval) + total := 1024 + var wg sync.WaitGroup + start := time.Now() + for i := 0; i < total; i++ { + wg.Add(1) + go func(index int) { + defer wg.Done() + var req *api.Instance + var resp *api.Instance + req, resp = createCommonInstance(t, serviceResps[index%10], index) + for c := 0; c < 10; c++ { + if updateResp := server.UpdateInstance(defaultCtx, req); !respSuccess(updateResp) { + t.Fatalf("error: %+v", updateResp) + } + } + removeCommonInstance(t, serviceResps[index%10], resp.GetId().GetValue()) + cleanInstance(resp.GetId().GetValue()) + }(i) + } + + wg.Wait() + t.Logf("consume: %v", time.Now().Sub(start)) + }) +} + +// 并发更新同一个实例 +func TestUpdateInstanceManyTimes(t *testing.T) { + _, serviceResp := createCommonService(t, 100) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + instanceReq, instanceResp := createCommonInstance(t, serviceResp, 10) + defer cleanInstance(instanceResp.GetId().GetValue()) + + var wg sync.WaitGroup + for i := 0; i < 64; i++ { + wg.Add(1) + go func(index int) { + defer wg.Done() + for c := 0; c < 16; c++ { + instanceReq.Weight.Value = uint32(rand.Int() % 32767) + if updateResp := server.UpdateInstance(defaultCtx, instanceReq); !respSuccess(updateResp) { + t.Fatalf("error: %+v", updateResp) + } + } + }(i) + } + wg.Wait() +} + +// 测试获取实例 +func TestGetInstances(t *testing.T) { + t.Run("可以正常获取到实例信息", func(t *testing.T) { + _ = server.Cache().Clear() // 为了防止影响,每个函数需要把缓存的内容清空 + _, serviceResp := createCommonService(t, 320) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + time.Sleep(updateCacheInterval) + instanceReq, instanceResp := createCommonInstance(t, serviceResp, 30) + defer cleanInstance(instanceResp.GetId().GetValue()) + + // 需要等待一会,等本地缓存更新 + time.Sleep(updateCacheInterval) + + req := &api.Service{ + Name: utils.NewStringValue(instanceResp.GetService().GetValue()), + Namespace: utils.NewStringValue(instanceResp.GetNamespace().GetValue()), + } + resp := server.ServiceInstancesCache(defaultCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + + discoveryCheck(t, req, resp) + + if len(resp.Instances) != 1 { + t.Fatalf("error : %d", len(resp.Instances)) + } + + instanceCheck(t, instanceReq, resp.GetInstances()[0]) + t.Logf("pass: %+v", resp.GetInstances()[0]) + }) + t.Run("注册实例,查询实例列表,实例反注册,revision会改变", func(t *testing.T) { + _ = server.Cache().Clear() // 为了防止影响,每个函数需要把缓存的内容清空 + _, serviceResp := createCommonService(t, 100) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + _, instanceResp := createCommonInstance(t, serviceResp, 90) + defer cleanInstance(instanceResp.GetId().GetValue()) + + time.Sleep(updateCacheInterval) + resp := server.ServiceInstancesCache(defaultCtx, serviceResp) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + revision := resp.GetService().GetRevision() + + // 再注册一个实例,revision会改变 + _, instanceResp = createCommonInstance(t, serviceResp, 100) + defer cleanInstance(instanceResp.GetId().GetValue()) + + time.Sleep(updateCacheInterval) + resp = server.ServiceInstancesCache(defaultCtx, serviceResp) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + + if revision == resp.GetService().GetRevision() { + t.Fatalf("error") + } + t.Logf("%s, %s", revision, resp.GetService().GetRevision()) + }) +} + +// 测试获取多个实例 +func TestGetInstances1(t *testing.T) { + discover := func(t *testing.T, service *api.Service, expectCount int) *api.DiscoverResponse { + time.Sleep(updateCacheInterval) + resp := server.ServiceInstancesCache(defaultCtx, service) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + discoveryCheck(t, service, resp) + if len(resp.Instances) != expectCount { + t.Fatalf("error : %d", len(resp.Instances)) + } + return resp + } + t.Run("注册并反注册多个实例,可以正常获取", func(t *testing.T) { + _ = server.Cache().Clear() // 为了防止影响,每个函数需要把缓存的内容清空 + _, serviceResp := createCommonService(t, 320) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + var ids []string + for i := 0; i < 10; i++ { + _, instanceResp := createCommonInstance(t, serviceResp, i) + ids = append(ids, instanceResp.GetId().GetValue()) + defer cleanInstance(instanceResp.GetId().GetValue()) + } + discover(t, serviceResp, 10) + + // 反注册一部分 + for i := 1; i < 6; i++ { + removeCommonInstance(t, serviceResp, ids[i]) + } + discover(t, serviceResp, 5) + }) + t.Run("传递revision, revision有变化则有数据,否则无数据返回", func(t *testing.T) { + _ = server.Cache().Clear() // 为了防止影响,每个函数需要把缓存的内容清空 + _, serviceResp := createCommonService(t, 100) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + for i := 0; i < 5; i++ { + _, instanceResp := createCommonInstance(t, serviceResp, i) + defer cleanInstance(instanceResp.GetId().GetValue()) + } + firstResp := discover(t, serviceResp, 5) + + serviceResp.Revision = firstResp.Service.GetRevision() + if resp := server.ServiceInstancesCache(defaultCtx, serviceResp); !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } else { + if len(resp.Instances) != 0 { + t.Fatalf("error: %d", len(resp.Instances)) + } + t.Logf("%+v", resp) + } + + // 多注册一个实例,revision发生改变 + _, instanceResp := createCommonInstance(t, serviceResp, 20) + defer cleanInstance(instanceResp.GetId().GetValue()) + discover(t, serviceResp, 6) + + }) +} + +// 反注册测试 +func TestRemoveInstance(t *testing.T) { + _, serviceResp := createCommonService(t, 15) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + t.Run("实例创建完马上反注册,可以成功", func(t *testing.T) { + _, instanceResp := createCommonInstance(t, serviceResp, 88) + defer cleanInstance(instanceResp.GetId().GetValue()) + + removeCommonInstance(t, serviceResp, instanceResp.GetId().GetValue()) + t.Logf("pass") + }) + + t.Run("注册完实例,反注册,再注册,可以成功", func(t *testing.T) { + _, instanceResp := createCommonInstance(t, serviceResp, 888) + defer cleanInstance(instanceResp.GetId().GetValue()) + + removeCommonInstance(t, serviceResp, instanceResp.GetId().GetValue()) + + time.Sleep(time.Second) + _, instanceResp = createCommonInstance(t, serviceResp, 888) + defer cleanInstance(instanceResp.GetId().GetValue()) + t.Logf("pass") + }) + t.Run("重复反注册,返回成功", func(t *testing.T) { + _, instanceResp := createCommonInstance(t, serviceResp, 999) + defer cleanInstance(instanceResp.GetId().GetValue()) + + removeCommonInstance(t, serviceResp, instanceResp.GetId().GetValue()) + time.Sleep(time.Second) + removeCommonInstance(t, serviceResp, instanceResp.GetId().GetValue()) + }) +} + +// 测试从数据库拉取实例信息 +func TestListInstances(t *testing.T) { + t.Run("list实例列表,返回的数据字段都存在", func(t *testing.T) { + _, serviceResp := createCommonService(t, 1156) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + instanceReq, instanceResp := createCommonInstance(t, serviceResp, 200) + defer cleanInstance(instanceResp.GetId().GetValue()) + + query := map[string]string{"offset": "0", "limit": "100"} + query["host"] = instanceReq.GetHost().GetValue() + query["port"] = strconv.FormatUint(uint64(instanceReq.GetPort().GetValue()), 10) + resp := server.GetInstances(query) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if len(resp.Instances) != 1 { + t.Fatalf("error: %d", len(resp.Instances)) + } + + instanceCheck(t, instanceReq, resp.Instances[0]) + }) + t.Run("list实例列表,offset和limit能正常工作", func(t *testing.T) { + _, serviceResp := createCommonService(t, 115) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + time.Sleep(updateCacheInterval) + total := 50 + for i := 0; i < total; i++ { + _, instanceResp := createCommonInstance(t, serviceResp, i+1) + defer cleanInstance(instanceResp.GetId().GetValue()) + } + + query := map[string]string{"offset": "10", "limit": "20", "host": "127.0.0.1"} + resp := server.GetInstances(query) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + + if len(resp.Instances) == 20 { + t.Logf("pass") + } + }) + + t.Run("list实例列表,可以进行正常字段过滤", func(t *testing.T) { + // 先任意找几个实例字段过滤 + _, serviceResp := createCommonService(t, 200) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + time.Sleep(updateCacheInterval) + total := 10 + instance := new(api.Instance) + for i := 0; i < total; i++ { + _, instanceResp := createCommonInstance(t, serviceResp, i+1) + defer cleanInstance(instanceResp.GetId().GetValue()) + instance = instanceResp + } + + host := instance.GetHost().GetValue() + port := strconv.FormatUint(uint64(instance.GetPort().GetValue()), 10) + query := map[string]string{"limit": "20", "host": host, "port": port} + resp := server.GetInstances(query) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if len(resp.Instances) == 1 { + t.Logf("pass") + } + }) +} + +// 测试list实例列表 +func TestListInstances1(t *testing.T) { + // 先任意找几个实例字段过滤 + _, serviceResp := createCommonService(t, 800) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + checkAmountAndSize := func(t *testing.T, resp *api.BatchQueryResponse, expect int, size int) { + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if resp.GetAmount().GetValue() != uint32(expect) { + t.Fatalf("error: %d", resp.GetAmount().GetValue()) + } + if len(resp.Instances) != size { + t.Fatalf("error: %d", len(resp.Instances)) + } + } + + t.Run("list实例,使用service和namespace过滤", func(t *testing.T) { + total := 102 + for i := 0; i < total; i++ { + _, instanceResp := createCommonInstance(t, serviceResp, i+2) + defer cleanInstance(instanceResp.GetId().GetValue()) + } + query := map[string]string{ + "offset": "0", + "limit": "100", + "service": serviceResp.GetName().GetValue(), + "namespace": serviceResp.GetNamespace().GetValue(), + } + + resp := server.GetInstances(query) + checkAmountAndSize(t, resp, total, 100) + }) + + t.Run("list实例,先删除实例,再查询会过滤删除的", func(t *testing.T) { + total := 50 + for i := 0; i < total; i++ { + _, instanceResp := createCommonInstance(t, serviceResp, i+2) + defer cleanInstance(instanceResp.GetId().GetValue()) + if i%2 == 0 { + removeCommonInstance(t, serviceResp, instanceResp.GetId().GetValue()) + } + } + + query := map[string]string{ + "service": serviceResp.GetName().GetValue(), + "namespace": serviceResp.GetNamespace().GetValue(), + } + resp := server.GetInstances(query) + checkAmountAndSize(t, resp, total/2, total/2) + + }) + t.Run("true和false测试", func(t *testing.T) { + _, instanceResp := createCommonInstance(t, serviceResp, 10) + defer cleanInstance(instanceResp.GetId().GetValue()) + + query := map[string]string{ + "service": serviceResp.GetName().GetValue(), + "namespace": serviceResp.GetNamespace().GetValue(), + "isolate": "false", + "healthy": "false", + } + checkAmountAndSize(t, server.GetInstances(query), 1, 1) + + query["isolate"] = "true" + checkAmountAndSize(t, server.GetInstances(query), 0, 0) + + query["isolate"] = "false" + query["healthy"] = "true" + checkAmountAndSize(t, server.GetInstances(query), 0, 0) + + query["isolate"] = "0" + query["healthy"] = "0" + checkAmountAndSize(t, server.GetInstances(query), 1, 1) + + query["health_status"] = "1" + checkAmountAndSize(t, server.GetInstances(query), 1, 1) + + query["health_status"] = "0" + delete(query, "healthy") + checkAmountAndSize(t, server.GetInstances(query), 1, 1) + + query["health_status"] = "1" + checkAmountAndSize(t, server.GetInstances(query), 0, 0) + }) + t.Run("metadata条件测试", func(t *testing.T) { + _, instanceResp1 := createCommonInstance(t, serviceResp, 10) + defer cleanInstance(instanceResp1.GetId().GetValue()) + _, instanceResp2 := createCommonInstance(t, serviceResp, 20) + defer cleanInstance(instanceResp2.GetId().GetValue()) + //只返回第一个实例的查询 + query := map[string]string{ + "service": serviceResp.GetName().GetValue(), + "namespace": serviceResp.GetNamespace().GetValue(), + "keys": "internal-personal-xxx", + "values": "internal-personal-xxx_10", + } + checkAmountAndSize(t, server.GetInstances(query), 1, 1) + //使用共同的元数据查询,返回两个实例 + query = map[string]string{ + "service": serviceResp.GetName().GetValue(), + "namespace": serviceResp.GetNamespace().GetValue(), + "keys": "my-meta-a1", + "values": "1111", + } + checkAmountAndSize(t, server.GetInstances(query), 2, 2) + //使用不存在的元数据查询,返回零个实例 + query = map[string]string{ + "service": serviceResp.GetName().GetValue(), + "namespace": serviceResp.GetNamespace().GetValue(), + "keys": "nokey", + "values": "novalue", + } + checkAmountAndSize(t, server.GetInstances(query), 0, 0) + }) + t.Run("metadata只有key或者value,返回错误", func(t *testing.T) { + query := map[string]string{ + "service": serviceResp.GetName().GetValue(), + "namespace": serviceResp.GetNamespace().GetValue(), + "keys": "internal-personal-xxx", + } + resp := server.GetInstances(query) + if resp.GetCode().GetValue() != api.InvalidQueryInsParameter { + t.Fatalf("resp is %v, not InvalidQueryInsParameter", resp) + } + query = map[string]string{ + "service": serviceResp.GetName().GetValue(), + "namespace": serviceResp.GetNamespace().GetValue(), + "values": "internal-personal-xxx", + } + resp = server.GetInstances(query) + if resp.GetCode().GetValue() != api.InvalidQueryInsParameter { + t.Fatalf("resp is %v, not InvalidQueryInsParameter", resp) + } + }) +} + +// 测试地域获取 +func TestInstancesContainLocation(t *testing.T) { + locationCheck := func(lhs *api.Location, rhs *api.Location) { + if lhs.GetRegion().GetValue() != rhs.GetRegion().GetValue() { + t.Fatalf("error: %v, %v", lhs, rhs) + } + if lhs.GetZone().GetValue() != rhs.GetZone().GetValue() { + t.Fatalf("error: %v, %v", lhs, rhs) + } + if lhs.GetCampus().GetValue() != rhs.GetCampus().GetValue() { + t.Fatalf("error: %v, %v", lhs, rhs) + } + } + + _, service := createCommonService(t, 123) + defer cleanServiceName(service.GetName().GetValue(), service.GetNamespace().GetValue()) + + instance := &api.Instance{ + Service: service.GetName(), + Namespace: service.GetNamespace(), + ServiceToken: service.GetToken(), + Host: utils.NewStringValue("123456"), + Port: utils.NewUInt32Value(9090), + Location: &api.Location{ + Region: utils.NewStringValue("region1"), + Zone: utils.NewStringValue("zone1"), + Campus: utils.NewStringValue("campus1"), + }, + } + resp := server.CreateInstance(defaultCtx, instance) + if !respSuccess(resp) { + t.Fatalf("error: %+v", resp) + } + defer cleanInstance(resp.GetInstance().GetId().GetValue()) + + getResp := server.GetInstances(map[string]string{ + "service": instance.GetService().GetValue(), "namespace": instance.GetNamespace().GetValue(), + }) + if !respSuccess(getResp) { + t.Fatalf("error: %+v", getResp) + } + getInstances := getResp.GetInstances() + if len(getInstances) != 1 { + t.Fatalf("error: %d", len(getInstances)) + } + t.Logf("%v", getInstances[0]) + locationCheck(instance.GetLocation(), getInstances[0].GetLocation()) + + time.Sleep(updateCacheInterval) + discoverResp := server.ServiceInstancesCache(defaultCtx, service) + if len(discoverResp.GetInstances()) != 1 { + t.Fatalf("error: %d", len(discoverResp.GetInstances())) + } + t.Logf("%v", discoverResp.GetInstances()[0]) + locationCheck(instance.GetLocation(), discoverResp.GetInstances()[0].GetLocation()) +} + +// 测试实例更新 +func TestUpdateInstance(t *testing.T) { + _, serviceResp := createCommonService(t, 123) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + instanceReq, instanceResp := createCommonInstance(t, serviceResp, 22) + defer cleanInstance(instanceResp.GetId().GetValue()) + t.Run("更新实例,所有属性都可以生效", func(t *testing.T) { + // update + instanceReq.Protocol = utils.NewStringValue("update-protocol") + instanceReq.Version = utils.NewStringValue("update-version") + instanceReq.Priority = utils.NewUInt32Value(30) + instanceReq.Weight = utils.NewUInt32Value(500) + instanceReq.Healthy = utils.NewBoolValue(false) + instanceReq.Isolate = utils.NewBoolValue(true) + instanceReq.LogicSet = utils.NewStringValue("update-logic-set") + instanceReq.HealthCheck = &api.HealthCheck{ + Type: api.HealthCheck_HEARTBEAT, + Heartbeat: &api.HeartbeatHealthCheck{ + Ttl: utils.NewUInt32Value(6), + }, + } + instanceReq.Metadata = map[string]string{ + "internal-personal-xxx": fmt.Sprintf("internal-personal-xxx_2412323"), + "tencent": "1111", + "yyyy": "2222", + } + instanceReq.ServiceToken = serviceResp.Token + + if resp := server.UpdateInstance(defaultCtx, instanceReq); !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + + // 查询数据 + query := map[string]string{ + "host": instanceReq.GetHost().GetValue(), + "port": strconv.FormatUint(uint64(instanceReq.GetPort().GetValue()), 10), + } + resp := server.GetInstances(query) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if len(resp.GetInstances()) != 1 { + t.Fatalf("error: %d", len(resp.GetInstances())) + } + + instanceReq.Service = instanceResp.Service + instanceReq.Namespace = instanceResp.Namespace + instanceCheck(t, instanceReq, resp.Instances[0]) + }) + t.Run("实例只更新metadata,revision也会发生改变", func(t *testing.T) { + instanceReq.Metadata = map[string]string{ + "new-metadata": "new-value", + } + + serviceName := serviceResp.GetName().GetValue() + namespaceName := serviceResp.GetNamespace().GetValue() + firstInstances := getInstancesWithService(t, serviceName, namespaceName, 1) + + if resp := server.UpdateInstance(defaultCtx, instanceReq); !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + + secondInstances := getInstancesWithService(t, serviceName, namespaceName, 1) + if firstInstances[0].GetRevision().GetValue() != secondInstances[0].GetRevision().GetValue() { + t.Logf("pass %s, %s", + firstInstances[0].GetRevision().GetValue(), secondInstances[0].GetRevision().GetValue()) + } else { + t.Fatalf("error") + } + + instanceCheck(t, instanceReq, secondInstances[0]) + }) + t.Run("metadata太长,update会报错", func(t *testing.T) { + instanceReq.Metadata = make(map[string]string) + for i := 0; i < naming.MaxMetadataLength+1; i++ { + instanceReq.Metadata[fmt.Sprintf("%d", i)] = "a" + } + if resp := server.UpdateInstance(defaultCtx, instanceReq); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error") + } + }) +} + +/** + * @brief 根据ip修改隔离状态 + */ +func TestUpdateIsolate(t *testing.T) { + _, serviceResp := createCommonService(t, 111) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + t.Run("修改超过100个实例的隔离状态", func(t *testing.T) { + instancesReq := make([]*api.Instance, 0, 210) + for i := 0; i < 210; i++ { + instanceReq := &api.Instance{ + ServiceToken: utils.NewStringValue(serviceResp.GetToken().GetValue()), + Service: utils.NewStringValue(serviceResp.GetName().GetValue()), + Namespace: utils.NewStringValue(serviceResp.GetNamespace().GetValue()), + Host: utils.NewStringValue("127.0.0.1"), + Port: utils.NewUInt32Value(uint32(i)), + } + resp := server.CreateInstance(defaultCtx, instanceReq) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + instancesReq = append(instancesReq, instanceReq) + defer cleanInstance(resp.GetInstance().GetId().GetValue()) + } + req := &api.Instance{ + ServiceToken: utils.NewStringValue(serviceResp.GetToken().GetValue()), + Service: utils.NewStringValue(serviceResp.GetName().GetValue()), + Namespace: utils.NewStringValue(serviceResp.GetNamespace().GetValue()), + Host: utils.NewStringValue("127.0.0.1"), + Isolate: utils.NewBoolValue(true), + } + if resp := server.UpdateInstanceIsolate(defaultCtx, req); !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("根据ip修改隔离状态", func(t *testing.T) { + instanceNum := 20 + portNum := 2 + revisions := make(map[string]string, instanceNum) + instancesReq := make([]*api.Instance, 0, instanceNum) + for i := 0; i < instanceNum/portNum; i++ { + for j := 1; j <= portNum; j++ { + instanceReq := &api.Instance{ + ServiceToken: utils.NewStringValue(serviceResp.GetToken().GetValue()), + Service: utils.NewStringValue(serviceResp.GetName().GetValue()), + Namespace: utils.NewStringValue(serviceResp.GetNamespace().GetValue()), + Host: utils.NewStringValue(fmt.Sprintf("%d.%d.%d.%d", i, i, i, i)), + Port: utils.NewUInt32Value(uint32(j)), + Isolate: utils.NewBoolValue(false), + Healthy: utils.NewBoolValue(true), + Metadata: map[string]string{ + "internal-personal-xxx": fmt.Sprintf("internal-personal-xxx_%d", i), + }, + } + resp := server.CreateInstance(defaultCtx, instanceReq) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + instanceReq.Isolate = utils.NewBoolValue(true) + instancesReq = append(instancesReq, instanceReq) + revisions[resp.GetInstance().GetId().GetValue()] = resp.GetInstance().GetRevision().GetValue() + defer cleanInstance(resp.GetInstance().GetId().GetValue()) + } + } + + if resp := server.UpdateInstancesIsolate(defaultCtx, instancesReq); !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + + // 检查隔离状态和revision是否改变 + for i := 0; i < instanceNum/portNum; i++ { + filter := map[string]string{ + "service": serviceResp.GetName().GetValue(), + "namespace": serviceResp.GetNamespace().GetValue(), + "host": fmt.Sprintf("%d.%d.%d.%d", i, i, i, i), + } + + resp := server.GetInstances(filter) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + + if len(resp.GetInstances()) != portNum { + t.Fatalf("error: %d", len(resp.GetInstances())) + } + + actualInstances := resp.GetInstances() + for _, instance := range actualInstances { + if !instance.GetIsolate().GetValue() || + instance.GetRevision().GetValue() == revisions[instance.GetId().GetValue()] { + t.Fatalf("error instance is %+v", instance) + } + } + } + t.Log("pass") + }) + + t.Run("并发更新", func(t *testing.T) { + instanceReq, instanceResp := createCommonInstance(t, serviceResp, 123) + defer cleanInstance(instanceResp.GetId().GetValue()) + + var wg sync.WaitGroup + for i := 0; i < 64; i++ { + wg.Add(1) + go func(index int) { + defer wg.Done() + for c := 0; c < 16; c++ { + instanceReq.Isolate = utils.NewBoolValue(true) + if resp := server.UpdateInstanceIsolate(defaultCtx, instanceReq); !respSuccess(resp) { + t.Fatalf("error: %+v", resp) + } + } + }(i) + } + wg.Wait() + t.Log("pass") + }) + + t.Run("若隔离状态相同,则不需要更新", func(t *testing.T) { + instanceReq, instanceResp := createCommonInstance(t, serviceResp, 456) + defer cleanInstance(instanceResp.GetId().GetValue()) + + resp := server.UpdateInstanceIsolate(defaultCtx, instanceReq) + if resp.GetCode().GetValue() == api.NoNeedUpdate { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) +} + +/** + * @brief 根据ip删除服务实例 + */ +func TestDeleteInstanceByHost(t *testing.T) { + _, serviceResp := createCommonService(t, 222) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + t.Run("根据ip删除服务实例", func(t *testing.T) { + instanceNum := 20 + portNum := 2 + instancesReq := make([]*api.Instance, 0, instanceNum) + for i := 0; i < instanceNum/portNum; i++ { + for j := 1; j <= portNum; j++ { + instanceReq := &api.Instance{ + ServiceToken: utils.NewStringValue(serviceResp.GetToken().GetValue()), + Service: utils.NewStringValue(serviceResp.GetName().GetValue()), + Namespace: utils.NewStringValue(serviceResp.GetNamespace().GetValue()), + Host: utils.NewStringValue(fmt.Sprintf("%d.%d.%d.%d", i, i, i, i)), + Port: utils.NewUInt32Value(uint32(j)), + } + resp := server.CreateInstance(defaultCtx, instanceReq) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + instancesReq = append(instancesReq, instanceReq) + defer cleanInstance(resp.GetInstance().GetId().GetValue()) + } + } + + if resp := server.DeleteInstancesByHost(defaultCtx, instancesReq); !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + + // 检查隔离状态和revision是否改变 + getInstancesWithService(t, + serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue(), 0) + t.Log("pass") + }) + + t.Run("删除超过100个实例", func(t *testing.T) { + instancesReq := make([]*api.Instance, 0, 210) + for i := 0; i < 210; i++ { + instanceReq := &api.Instance{ + ServiceToken: utils.NewStringValue(serviceResp.GetToken().GetValue()), + Service: utils.NewStringValue(serviceResp.GetName().GetValue()), + Namespace: utils.NewStringValue(serviceResp.GetNamespace().GetValue()), + Host: utils.NewStringValue("127.0.0.2"), + Port: utils.NewUInt32Value(uint32(i)), + } + resp := server.CreateInstance(defaultCtx, instanceReq) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + instancesReq = append(instancesReq, instanceReq) + defer cleanInstance(resp.GetInstance().GetId().GetValue()) + } + req := &api.Instance{ + ServiceToken: utils.NewStringValue(serviceResp.GetToken().GetValue()), + Service: utils.NewStringValue(serviceResp.GetName().GetValue()), + Namespace: utils.NewStringValue(serviceResp.GetNamespace().GetValue()), + Host: utils.NewStringValue("127.0.0.1"), + Isolate: utils.NewBoolValue(true), + } + if resp := server.DeleteInstanceByHost(defaultCtx, req); !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) +} + +// 测试enable_health_check +func TestUpdateHealthCheck(t *testing.T) { + getAndCheck := func(t *testing.T, req *api.Instance) { + query := map[string]string{ + "host": req.GetHost().GetValue(), + "port": strconv.FormatUint(uint64(req.GetPort().GetValue()), 10), + } + resp := server.GetInstances(query) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if len(resp.GetInstances()) != 1 { + t.Fatalf("error: %d", len(resp.GetInstances())) + } + t.Logf("%+v", resp.Instances[0]) + + instanceCheck(t, req, resp.Instances[0]) + } + _, serviceResp := createCommonService(t, 321) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + instanceReq, instanceResp := createCommonInstance(t, serviceResp, 10) + defer cleanInstance(instanceResp.GetId().GetValue()) + instanceReq.ServiceToken = serviceResp.Token + t.Run("health_check可以随意关闭", func(t *testing.T) { + // 打开 -> 打开 + instanceReq.Weight = utils.NewUInt32Value(300) + if resp := server.UpdateInstance(defaultCtx, instanceReq); !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + getAndCheck(t, instanceReq) + + // 打开-> 关闭 + instanceReq.EnableHealthCheck = utils.NewBoolValue(false) + if resp := server.UpdateInstance(defaultCtx, instanceReq); !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + instanceReq.HealthCheck = nil + getAndCheck(t, instanceReq) + + // 关闭 -> 关闭 + instanceReq.Weight = utils.NewUInt32Value(200) + if resp := server.UpdateInstance(defaultCtx, instanceReq); !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + getAndCheck(t, instanceReq) + + // 关闭 -> 打开 + instanceReq.EnableHealthCheck = utils.NewBoolValue(true) + instanceReq.HealthCheck = &api.HealthCheck{ + Type: api.HealthCheck_HEARTBEAT, + Heartbeat: &api.HeartbeatHealthCheck{ + Ttl: utils.NewUInt32Value(8), + }, + } + if resp := server.UpdateInstance(defaultCtx, instanceReq); !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + getAndCheck(t, instanceReq) + }) + t.Run("healthcheck为空的异常测试", func(t *testing.T) { + instanceReq.HealthCheck = &api.HealthCheck{ + Heartbeat: &api.HeartbeatHealthCheck{ + Ttl: utils.NewUInt32Value(0), + }, + } + if resp := server.UpdateInstance(defaultCtx, instanceReq); !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + instanceReq.HealthCheck = &api.HealthCheck{ + Type: api.HealthCheck_HEARTBEAT, + Heartbeat: &api.HeartbeatHealthCheck{ + Ttl: utils.NewUInt32Value(naming.DefaultTLL), + }, + } + getAndCheck(t, instanceReq) + }) +} + +// 测试删除实例 +func TestDeleteInstance(t *testing.T) { + _, serviceResp := createCommonService(t, 123) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + getInstance := func(t *testing.T, s *api.Service, expect int) []*api.Instance { + filters := map[string]string{"service": s.GetName().GetValue(), "namespace": s.GetNamespace().GetValue()} + getResp := server.GetInstances(filters) + if !respSuccess(getResp) { + t.Fatalf("error") + } + if len(getResp.GetInstances()) != expect { + t.Fatalf("error") + } + return getResp.GetInstances() + } + + t.Run("可以通过ID删除实例", func(t *testing.T) { + _, instanceResp := createCommonInstance(t, serviceResp, 10) + defer cleanInstance(instanceResp.GetId().GetValue()) + removeCommonInstance(t, serviceResp, instanceResp.GetId().GetValue()) + + getInstance(t, serviceResp, 0) + }) + t.Run("可以通过四元组删除实例", func(t *testing.T) { + req := &api.Instance{ + ServiceToken: serviceResp.GetToken(), + Service: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + Host: utils.NewStringValue("abc"), + Port: utils.NewUInt32Value(8080), + } + resp := server.CreateInstance(defaultCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + instanceResp := resp.GetInstance() + t.Logf("%+v", getInstance(t, serviceResp, 1)) + defer cleanInstance(instanceResp.GetId().GetValue()) + + removeInstanceWithAttrs(t, serviceResp, instanceResp) + getInstance(t, serviceResp, 0) + }) + t.Run("可以通过五元组删除实例", func(t *testing.T) { + _, instanceResp := createCommonInstance(t, serviceResp, 55) + defer cleanInstance(instanceResp.GetId().GetValue()) + + removeInstanceWithAttrs(t, serviceResp, instanceResp) + getInstance(t, serviceResp, 0) + }) +} + +// 批量创建服务实例 +// 步骤: +// 1. n个服务,每个服务m个服务实例 +// 2. n个协程同时发请求 +func TestBatchCreateInstances(t *testing.T) { + Convey("批量创建服务", t, func() { + n := 32 + m := 128 + var services []*api.Service + for i := 0; i < n; i++ { + _, service := createCommonService(t, i) + services = append(services, service) + } + defer cleanServices(services) + + var wg sync.WaitGroup + idCh := make(chan string, n*m) + for i := 0; i < n; i++ { + wg.Add(1) + go func(index int) { + defer wg.Done() + for j := 0; j < m; j++ { + _, instance := createCommonInstance(t, services[index], j) + idCh <- instance.GetId().GetValue() + } + }(i) + } + + var deleteCount int32 + for i := 0; i < n; i++ { + go func() { + for { + select { + case id := <-idCh: + cleanInstance(id) + atomic.AddInt32(&deleteCount, 1) + } + } + }() + } + + wg.Wait() + for { + count := atomic.LoadInt32(&deleteCount) + if count == int32(n*m) { + return + } + t.Logf("%d", count) + time.Sleep(time.Second * 1) + } + + }) +} + +// 测试批量接口返回的顺序 +func TestCreateInstancesOrder(t *testing.T) { + t.Run("测试批量接口返回的顺序与发送的数据一致", func(t *testing.T) { + _, service := createCommonService(t, 123) + defer cleanServiceName(service.GetName().GetValue(), service.GetNamespace().GetValue()) + var instances []*api.Instance + for j := 0; j < 10; j++ { + instances = append(instances, &api.Instance{ + Service: service.GetName(), + Namespace: service.GetNamespace(), + ServiceToken: service.GetToken(), + Host: utils.NewStringValue("a.b.c.d"), + Port: utils.NewUInt32Value(uint32(j)), + }) + } + + resps := server.CreateInstances(defaultCtx, instances) + if !respSuccess(resps) { + t.Fatalf("error: %+v", resps) + } + for i, resp := range resps.GetResponses() { + if resp.GetInstance().GetPort().GetValue() != instances[i].GetPort().GetValue() { + t.Fatalf("error") + } + cleanInstance(resp.GetInstance().GetId().GetValue()) + } + }) +} + +// 测试批量删除实例 +func TestBatchDeleteInstances(t *testing.T) { + _, service := createCommonService(t, 234) + defer cleanServiceName(service.GetName().GetValue(), service.GetNamespace().GetValue()) + createInstances := func(t *testing.T) ([]*api.Instance, *api.BatchWriteResponse) { + var instances []*api.Instance + for j := 0; j < 100; j++ { + instances = append(instances, &api.Instance{ + Service: service.GetName(), + Namespace: service.GetNamespace(), + ServiceToken: service.GetToken(), + Host: utils.NewStringValue("a.b.c.d"), + Port: utils.NewUInt32Value(uint32(j)), + }) + } + resps := server.CreateInstances(defaultCtx, instances) + if !respSuccess(resps) { + t.Fatalf("error: %+v", resps) + } + return instances, resps + } + t.Run("测试batch删除实例,单个接口", func(t *testing.T) { + _, resps := createInstances(t) + var wg sync.WaitGroup + for _, resp := range resps.GetResponses() { + wg.Add(1) + go func(instance *api.Instance) { + defer func() { + cleanInstance(instance.GetId().GetValue()) + wg.Done() + }() + req := &api.Instance{Id: instance.Id, ServiceToken: service.Token} + if out := server.DeleteInstance(defaultCtx, req); !respSuccess(out) { + t.Fatalf("error: %+v", out) + } + }(resp.GetInstance()) + } + wg.Wait() + }) + t.Run("测试batch删除实例,批量接口", func(t *testing.T) { + instances, instancesResp := createInstances(t) + // 删除body的token,测试header的token是否可行 + for _, instance := range instances { + instance.ServiceToken = nil + instance.Id = nil + } + ctx := context.WithValue(defaultCtx, utils.StringContext("polaris-token"), service.GetToken().GetValue()) + if out := server.DeleteInstances(ctx, instances); !respSuccess(out) { + t.Fatalf("error: %+v", out) + } else { + t.Logf("%+v", out) + } + resps := server.GetInstances(map[string]string{ + "service": service.GetName().GetValue(), + "namespace": service.GetNamespace().GetValue(), + }) + if !respSuccess(resps) { + t.Fatalf("error: %+v", resps) + } + if len(resps.GetInstances()) != 0 { + t.Fatalf("error : %d", len(resps.GetInstances())) + } + for _, entry := range instancesResp.GetResponses() { + cleanInstance(entry.GetInstance().GetId().GetValue()) + } + }) +} + +// 验证成功创建和删除实例的response +func TestInstanceResponse(t *testing.T) { + _, service := createCommonService(t, 234) + defer cleanServiceName(service.GetName().GetValue(), service.GetNamespace().GetValue()) + create := func() (*api.Instance, *api.Instance) { + ins := &api.Instance{ + Service: service.GetName(), + Namespace: service.GetNamespace(), + ServiceToken: service.GetToken(), + Host: utils.NewStringValue("a.b.c.d"), + Port: utils.NewUInt32Value(uint32(100)), + } + resps := server.CreateInstances(defaultCtx, []*api.Instance{ins}) + if !respSuccess(resps) { + t.Fatalf("error: %+v", resps) + } + return ins, resps.Responses[0].GetInstance() + } + t.Run("创建实例,返回的信息不能包括token,包括id", func(t *testing.T) { + ins, respIns := create() + defer cleanInstance(respIns.GetId().GetValue()) + t.Logf("%+v", respIns) + if respIns.GetService().GetValue() != ins.GetService().GetValue() || + respIns.GetNamespace().GetValue() != ins.GetNamespace().GetValue() || + respIns.GetHost().GetValue() != ins.GetHost().GetValue() || + respIns.GetPort().GetValue() != ins.GetPort().GetValue() || + respIns.GetId().GetValue() == "" || respIns.GetServiceToken().GetValue() != "" { + t.Fatalf("error") + } + }) + t.Run("删除实例,返回的信息包括req,不增加信息", func(t *testing.T) { + req, resp := create() + defer cleanInstance(resp.GetId().GetValue()) + time.Sleep(time.Second) + resps := server.DeleteInstances(defaultCtx, []*api.Instance{req}) + if !respSuccess(resps) { + t.Fatalf("error: %+v", resps) + } + respIns := resps.GetResponses()[0].GetInstance() + if respIns.GetId().GetValue() != "" || respIns.GetService() != req.GetService() || + respIns.GetNamespace() != req.GetNamespace() || respIns.GetHost() != req.GetHost() || + respIns.GetPort() != req.GetPort() || respIns.GetServiceToken() != req.GetServiceToken() { + t.Fatalf("error") + } + t.Logf("pass") + }) +} + +// 测试实例创建与删除的异常场景2 +func TestCreateInstancesBadCase2(t *testing.T) { + _, service := createCommonService(t, 123) + defer cleanServiceName(service.GetName().GetValue(), service.GetNamespace().GetValue()) + t.Run("重复多个一样的实例注册,其中一个成功,其他的失败", func(t *testing.T) { + time.Sleep(time.Second) + var instances []*api.Instance + for j := 0; j < 3; j++ { + instances = append(instances, &api.Instance{ + Service: service.GetName(), + Namespace: service.GetNamespace(), + ServiceToken: service.GetToken(), + Host: utils.NewStringValue("a.b.c.d"), + Port: utils.NewUInt32Value(uint32(100)), + }) + } + + resps := server.CreateInstances(defaultCtx, instances) + t.Logf("%+v", resps) + if respSuccess(resps) { + t.Fatalf("error: %+v", resps) + } + for _, resp := range resps.GetResponses() { + if resp.GetInstance().GetId().GetValue() != "" { + cleanInstance(resp.GetInstance().GetId().GetValue()) + } + } + }) + t.Run("重复发送同样实例的反注册请求,可以正常返回,一个成功,其他的失败", func(t *testing.T) { + time.Sleep(time.Second) + instance := &api.Instance{ + Service: service.GetName(), + Namespace: service.GetNamespace(), + ServiceToken: service.GetToken(), + Host: utils.NewStringValue("a.b.c.d"), + Port: utils.NewUInt32Value(uint32(100)), + } + resps := server.CreateInstances(defaultCtx, []*api.Instance{instance}) + if !respSuccess(resps) { + t.Fatalf("error: %+v", resps) + } + defer cleanInstance(resps.Responses[0].Instance.GetId().GetValue()) + + delReqs := make([]*api.Instance, 0, 10) + for i := 0; i < 2; i++ { + delReqs = append(delReqs, &api.Instance{ + Id: resps.Responses[0].Instance.GetId(), + ServiceToken: service.GetToken(), + }) + } + time.Sleep(time.Second) + resps = server.DeleteInstances(defaultCtx, delReqs) + if respSuccess(resps) { + t.Fatalf("error: %s", resps) + } + for _, resp := range resps.GetResponses() { + if resp.GetCode().GetValue() != api.ExecuteSuccess && + resp.GetCode().GetValue() != api.SameInstanceRequest { + t.Fatalf("error: %+v", resp) + } + } + }) +} + +// 测试实例创建和删除的流量限制 +func TestInstanceRatelimit(t *testing.T) { + Convey("超过ratelimit,返回错误", t, func() { + _, serviceResp := createCommonService(t, 100) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + instanceReq, instanceResp := createCommonInstance(t, serviceResp, 110) + server.CreateInstance(defaultCtx, instanceReq) + defer cleanInstance(instanceResp.GetId().GetValue()) + for i := 0; i < 10; i++ { + resp := server.CreateInstance(defaultCtx, instanceReq) + So(resp.GetCode().GetValue(), ShouldEqual, api.InstanceTooManyRequests) + } + time.Sleep(time.Second) + resp := server.CreateInstance(defaultCtx, instanceReq) + So(resp.GetCode().GetValue(), ShouldEqual, api.ExistedResource) + }) +} + +// 测试instance,no need update +func TestInstanceNoNeedUpdate(t *testing.T) { + _, serviceResp := createCommonService(t, 222) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + instanceReq, instanceResp := createCommonInstance(t, serviceResp, 222) + defer cleanInstance(instanceResp.GetId().GetValue()) + Convey("instance没有变更,不需要更新", t, func() { + resp := server.UpdateInstance(defaultCtx, instanceReq) + So(resp.GetCode().GetValue(), ShouldEqual, api.NoNeedUpdate) + }) + Convey("metadata为空,不需要更新", t, func() { + oldMeta := instanceReq.GetMetadata() + instanceReq.Metadata = nil + defer func() { instanceReq.Metadata = oldMeta }() + resp := server.UpdateInstance(defaultCtx, instanceReq) + So(resp.GetCode().GetValue(), ShouldEqual, api.NoNeedUpdate) + }) + Convey("healthCheck为nil,不需要更新", t, func() { + oldHealthCheck := instanceReq.GetHealthCheck() + instanceReq.HealthCheck = nil + defer func() { instanceReq.HealthCheck = oldHealthCheck }() + So(server.UpdateInstance(defaultCtx, instanceReq).GetCode().GetValue(), ShouldEqual, api.NoNeedUpdate) + }) +} + +// 实例数据更新测试 +// 部分数据变更,触发更新 +func TestUpdateInstanceFiled(t *testing.T) { + _, serviceResp := createCommonService(t, 555) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + instanceReq, instanceResp := createCommonInstance(t, serviceResp, 555) + defer cleanInstance(instanceResp.GetId().GetValue()) + Convey("metadata变更", t, func() { + instanceReq.Metadata = map[string]string{} + So(server.UpdateInstance(defaultCtx, instanceReq).GetCode().GetValue(), ShouldEqual, api.ExecuteSuccess) + + instanceReq.Metadata = map[string]string{"123": "456", "789": "abc", "135": "246"} + So(server.UpdateInstance(defaultCtx, instanceReq).GetCode().GetValue(), ShouldEqual, api.ExecuteSuccess) + + instanceReq.Metadata["890"] = "678" + So(server.UpdateInstance(defaultCtx, instanceReq).GetCode().GetValue(), ShouldEqual, api.ExecuteSuccess) + + delete(instanceReq.Metadata, "135") + So(server.UpdateInstance(defaultCtx, instanceReq).GetCode().GetValue(), ShouldEqual, api.ExecuteSuccess) + }) + Convey("healthCheck变更", t, func() { + instanceReq.HealthCheck.Heartbeat.Ttl.Value = 33 + So(server.UpdateInstance(defaultCtx, instanceReq).GetCode().GetValue(), ShouldEqual, api.ExecuteSuccess) + + instanceReq.EnableHealthCheck = utils.NewBoolValue(false) + So(server.UpdateInstance(defaultCtx, instanceReq).GetCode().GetValue(), ShouldEqual, api.ExecuteSuccess) + newInstanceResp := server.GetInstances(map[string]string{ + "service": serviceResp.GetName().GetValue(), + "namespace": serviceResp.GetNamespace().GetValue(), + }) + So(newInstanceResp.GetInstances()[0].GetHealthCheck(), ShouldBeNil) + instanceReq.HealthCheck = nil + + instanceReq.EnableHealthCheck = utils.NewBoolValue(true) + So(server.UpdateInstance(defaultCtx, instanceReq).GetCode().GetValue(), ShouldEqual, api.NoNeedUpdate) + + instanceReq.HealthCheck = &api.HealthCheck{ + Type: api.HealthCheck_HEARTBEAT, + Heartbeat: &api.HeartbeatHealthCheck{Ttl: utils.NewUInt32Value(50)}, + } + So(server.UpdateInstance(defaultCtx, instanceReq).GetCode().GetValue(), ShouldEqual, api.ExecuteSuccess) + }) + Convey("其他字段变更", t, func() { + instanceReq.Protocol.Value = "new-protocol-1" + So(server.UpdateInstance(defaultCtx, instanceReq).GetCode().GetValue(), ShouldEqual, api.ExecuteSuccess) + + instanceReq.Version.Value = "new-version-1" + So(server.UpdateInstance(defaultCtx, instanceReq).GetCode().GetValue(), ShouldEqual, api.ExecuteSuccess) + + instanceReq.Priority.Value = 88 + So(server.UpdateInstance(defaultCtx, instanceReq).GetCode().GetValue(), ShouldEqual, api.ExecuteSuccess) + + instanceReq.Weight.Value = 500 + So(server.UpdateInstance(defaultCtx, instanceReq).GetCode().GetValue(), ShouldEqual, api.ExecuteSuccess) + + instanceReq.Healthy.Value = true + So(server.UpdateInstance(defaultCtx, instanceReq).GetCode().GetValue(), ShouldEqual, api.ExecuteSuccess) + + instanceReq.Isolate.Value = true + So(server.UpdateInstance(defaultCtx, instanceReq).GetCode().GetValue(), ShouldEqual, api.ExecuteSuccess) + + instanceReq.LogicSet.Value = "new-logic-set-1" + So(server.UpdateInstance(defaultCtx, instanceReq).GetCode().GetValue(), ShouldEqual, api.ExecuteSuccess) + + newInstanceResp := server.GetInstances(map[string]string{ + "service": serviceResp.GetName().GetValue(), + "namespace": serviceResp.GetNamespace().GetValue(), + }) + instanceCheck(t, newInstanceResp.GetInstances()[0], instanceReq) + }) +} + +// 根据服务名获取实例列表并且做基础的判断 +func getInstancesWithService(t *testing.T, name string, namespace string, expectCount int) []*api.Instance { + query := map[string]string{ + "service": name, + "namespace": namespace, + } + resp := server.GetInstances(query) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + + if len(resp.GetInstances()) != expectCount { + t.Fatalf("error: %d", len(resp.GetInstances())) + } + + return resp.GetInstances() +} + +// test对instance字段进行校验 +func TestCheckInstanceFieldLen(t *testing.T) { + _, serviceResp := createCommonService(t, 800) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + ins := &api.Instance{ + ServiceToken: serviceResp.GetToken(), + Service: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + Host: utils.NewStringValue("127.0.0.1"), + Protocol: utils.NewStringValue("grpc"), + Version: utils.NewStringValue("1.0.1"), + LogicSet: utils.NewStringValue("sz"), + Metadata: map[string]string{}, + } + + t.Run("服务名超长", func(t *testing.T) { + str := genSpecialStr(129) + oldName := ins.Service + ins.Service = utils.NewStringValue(str) + resp := server.CreateInstance(defaultCtx, ins) + ins.Service = oldName + if resp.Code.Value != api.InvalidServiceName { + t.Fatalf("%+v", resp) + } + }) + t.Run("host超长", func(t *testing.T) { + str := genSpecialStr(129) + oldHost := ins.Host + ins.Host = utils.NewStringValue(str) + resp := server.CreateInstance(defaultCtx, ins) + ins.Host = oldHost + if resp.Code.Value != api.InvalidInstanceHost { + t.Fatalf("%+v", resp) + } + }) + t.Run("protocol超长", func(t *testing.T) { + str := genSpecialStr(129) + oldProtocol := ins.Protocol + ins.Protocol = utils.NewStringValue(str) + resp := server.CreateInstance(defaultCtx, ins) + ins.Protocol = oldProtocol + if resp.Code.Value != api.InvalidInstanceProtocol { + t.Fatalf("%+v", resp) + } + }) + t.Run("version超长", func(t *testing.T) { + str := genSpecialStr(129) + oldVersion := ins.Version + ins.Version = utils.NewStringValue(str) + resp := server.CreateInstance(defaultCtx, ins) + ins.Version = oldVersion + if resp.Code.Value != api.InvalidInstanceVersion { + t.Fatalf("%+v", resp) + } + }) + t.Run("logicSet超长", func(t *testing.T) { + str := genSpecialStr(129) + oldLogicSet := ins.LogicSet + ins.LogicSet = utils.NewStringValue(str) + resp := server.CreateInstance(defaultCtx, ins) + ins.LogicSet = oldLogicSet + if resp.Code.Value != api.InvalidInstanceLogicSet { + t.Fatalf("%+v", resp) + } + }) + t.Run("metadata超长", func(t *testing.T) { + str := genSpecialStr(129) + oldMetadata := ins.Metadata + oldMetadata[str] = str + resp := server.CreateInstance(defaultCtx, ins) + ins.Metadata = make(map[string]string) + if resp.Code.Value != api.InvalidMetadata { + t.Fatalf("%+v", resp) + } + }) + t.Run("port超长", func(t *testing.T) { + oldPort := ins.Port + ins.Port = utils.NewUInt32Value(70000) + resp := server.CreateInstance(defaultCtx, ins) + ins.Port = oldPort + if resp.Code.Value != api.InvalidInstancePort { + t.Fatalf("%+v", resp) + } + }) + t.Run("weight超长", func(t *testing.T) { + oldWeight := ins.Weight + ins.Weight = utils.NewUInt32Value(70000) + resp := server.CreateInstance(defaultCtx, ins) + ins.Weight = oldWeight + if resp.Code.Value != api.InvalidParameter { + t.Fatalf("%+v", resp) + } + }) + t.Run("检测字段为空指针", func(t *testing.T) { + oldName := ins.Service + ins.Service = nil + resp := server.CreateInstance(defaultCtx, ins) + ins.Service = oldName + if resp.Code.Value != api.InvalidServiceName { + t.Fatalf("%+v", resp) + } + }) + t.Run("检测字段为空", func(t *testing.T) { + oldName := ins.Service + ins.Service = utils.NewStringValue("") + resp := server.CreateInstance(defaultCtx, ins) + ins.Service = oldName + if resp.Code.Value != api.InvalidServiceName { + t.Fatalf("%+v", resp) + } + }) +} + +// test对instance入参进行校验 +func TestCheckInstanceParam(t *testing.T) { + // get instances接口限制(service+namespace)或者host必传,其它传参均拒绝服务 + _, serviceResp := createCommonService(t, 1254) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + instanceReq, instanceResp := createCommonInstance(t, serviceResp, 153) + defer cleanInstance(instanceResp.GetId().GetValue()) + + t.Run("只传service", func(t *testing.T) { + query := map[string]string{} + query["service"] = "test" + resp := server.GetInstances(query) + if resp.Code.Value != api.InvalidQueryInsParameter { + t.Fatalf("%+v", resp) + } + }) + t.Run("只传namespace", func(t *testing.T) { + query := map[string]string{} + query["namespace"] = "test" + resp := server.GetInstances(query) + if resp.Code.Value != api.InvalidQueryInsParameter { + t.Fatalf("%+v", resp) + } + }) + t.Run("只传port", func(t *testing.T) { + query := map[string]string{} + query["port"] = "123" + resp := server.GetInstances(query) + if resp.Code.Value != api.InvalidQueryInsParameter { + t.Fatalf("%+v", resp) + } + }) + t.Run("只传version", func(t *testing.T) { + query := map[string]string{} + query["version"] = "123" + resp := server.GetInstances(query) + if resp.Code.Value != api.InvalidQueryInsParameter { + t.Fatalf("%+v", resp) + } + }) + t.Run("只传protocol", func(t *testing.T) { + query := map[string]string{} + query["protocol"] = "http" + resp := server.GetInstances(query) + if resp.Code.Value != api.InvalidQueryInsParameter { + t.Fatalf("%+v", resp) + } + }) + t.Run("传service+port", func(t *testing.T) { + query := map[string]string{} + query["service"] = "test" + query["port"] = "123" + resp := server.GetInstances(query) + if resp.Code.Value != api.InvalidQueryInsParameter { + t.Fatalf("%+v", resp) + } + }) + t.Run("传namespace+port", func(t *testing.T) { + query := map[string]string{} + query["namespace"] = "test" + query["port"] = "123" + resp := server.GetInstances(query) + if resp.Code.Value != api.InvalidQueryInsParameter { + t.Fatalf("%+v", resp) + } + }) + t.Run("传service+namespace", func(t *testing.T) { + query := map[string]string{} + query["service"] = instanceReq.GetService().Value + query["namespace"] = instanceReq.GetNamespace().Value + resp := server.GetInstances(query) + if resp.Code.Value != api.ExecuteSuccess { + t.Fatalf("%+v", resp) + } + }) + t.Run("传service+namespace+host", func(t *testing.T) { + query := map[string]string{} + query["service"] = instanceReq.GetService().Value + query["namespace"] = instanceReq.GetNamespace().Value + query["host"] = instanceReq.GetHost().Value + resp := server.GetInstances(query) + if resp.Code.Value != api.ExecuteSuccess { + t.Fatalf("%+v", resp) + } + }) + t.Run("传service+namespace+port", func(t *testing.T) { + query := map[string]string{} + query["service"] = instanceReq.GetService().Value + query["namespace"] = instanceReq.GetNamespace().Value + query["port"] = strconv.Itoa(int(instanceReq.GetPort().Value)) + resp := server.GetInstances(query) + if resp.Code.Value != api.ExecuteSuccess { + t.Fatalf("%+v", resp) + } + }) + t.Run("传host", func(t *testing.T) { + query := map[string]string{} + query["host"] = instanceReq.GetHost().Value + resp := server.GetInstances(query) + if resp.Code.Value != api.ExecuteSuccess { + t.Fatalf("%+v", resp) + } + }) + t.Run("传host+namespace", func(t *testing.T) { + query := map[string]string{} + query["host"] = instanceReq.GetHost().Value + query["namespace"] = instanceReq.GetNamespace().Value + resp := server.GetInstances(query) + if resp.Code.Value != api.ExecuteSuccess { + t.Fatalf("%+v", resp) + } + }) + t.Run("传host+port", func(t *testing.T) { + query := map[string]string{} + query["host"] = instanceReq.GetHost().Value + query["port"] = strconv.Itoa(int(instanceReq.GetPort().Value)) + resp := server.GetInstances(query) + if resp.Code.Value != api.ExecuteSuccess { + t.Fatalf("%+v", resp) + } + }) +} diff --git a/naming/test/l5service_test.go b/naming/test/l5service_test.go new file mode 100644 index 000000000..22d79bc37 --- /dev/null +++ b/naming/test/l5service_test.go @@ -0,0 +1,306 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package test + +import ( + "context" + "database/sql" + "fmt" + "github.com/polarismesh/polaris-server/common/api/l5" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/utils" + "github.com/gogo/protobuf/proto" + . "github.com/smartystreets/goconvey/convey" + "testing" + "time" +) + +// safe get cache data +func safeSyncByAgentCmd(ctx context.Context, sbac *l5.Cl5SyncByAgentCmd) (*l5.Cl5SyncByAgentAckCmd, error) { + time.Sleep(updateCacheInterval) + return server.SyncByAgentCmd(ctx, sbac) +} + +// get maxFlow from t_route +func getMaxRouteFlow(t *testing.T) int { + maxStr := "select IFNULL(max(fflow),0) from t_route" + var maxFlow int + err := db.QueryRow(maxStr).Scan(&maxFlow) + switch { + case err == sql.ErrNoRows: + maxFlow = 0 + case err != nil: + t.Fatalf("error: %s", err.Error()) + } + + return maxFlow + 1 +} + +// add l5 t_route +func addL5Route(t *testing.T, ip, modID, cmdID int32, setID string) { + maxFlow := getMaxRouteFlow(t) + str := "replace into t_route(fip, fmodid, fcmdid, fsetId, fflag, fstamp, fflow) values(?,?,?,?,0,now(),?)" + if _, err := db.Exec(str, ip, modID, cmdID, setID, maxFlow+1); err != nil { + t.Fatalf("error: %s", err.Error()) + } +} + +// 删除t_route +func deleteL5Route(t *testing.T, ip, modID, cmdID int32) { + maxFlow := getMaxRouteFlow(t) + str := "update t_route set fflag = 1, fflow = ? where fip = ? and fmodid = ? and fcmdid = ?" + if _, err := db.Exec(str, maxFlow, ip, modID, cmdID); err != nil { + t.Fatalf("error: %s", err.Error()) + } +} + +// 创建带SetID的实例列表 +// setID可以为空 +func createInstanceWithSetID(service *api.Service, index int, setIDs string, weights string) *api.Instance { + instance := &api.Instance{ + Service: service.GetName(), + Namespace: service.GetNamespace(), + Host: utils.NewStringValue(fmt.Sprintf("10.235.25.%d", index)), + Port: utils.NewUInt32Value(8080), + ServiceToken: service.GetToken(), + } + if setIDs != "" { + instance.Metadata = map[string]string{"internal-cl5-setId": setIDs} + } + if weights != "" { + if instance.Metadata == nil { + instance.Metadata = make(map[string]string) + } + instance.Metadata["internal-cl5-weight"] = weights + } + resp := server.CreateInstance(defaultCtx, instance) + So(respSuccess(resp), ShouldEqual, true) + return resp.GetInstance() +} + +// 测试兼容l5协议的流程 +func TestSyncByAgentCmd(t *testing.T) { + Convey("获取老Cl5的Sid数据", t, func() { + reqCmd := &l5.Cl5SyncByAgentCmd{AgentIp: proto.Int32(11111), SyncFlow: proto.Int32(22222)} + modID := int32(64850433) + cmdID := int32(65540) + service := &api.Service{ + Name: utils.NewStringValue(fmt.Sprintf("%d:%d", modID, cmdID)), + Namespace: utils.NewStringValue("default"), + Owners: utils.NewStringValue("aa"), + } + serviceResp := server.CreateService(defaultCtx, service) + So(respSuccess(serviceResp), ShouldEqual, true) + defer cleanServiceName(service.GetName().GetValue(), service.GetNamespace().GetValue()) + + Convey("正常数据获取", func() { + reqCmd.OptList = &l5.Cl5OptList{ + Opt: []*l5.Cl5OptObj{{ModId: proto.Int32(modID), CmdId: proto.Int32(cmdID)}}, + } + + ack, err := safeSyncByAgentCmd(defaultCtx, reqCmd) + So(err, ShouldBeNil) + So(ack.GetAgentIp(), ShouldEqual, reqCmd.GetAgentIp()) + So(ack.GetSyncFlow(), ShouldEqual, reqCmd.GetSyncFlow()+1) + So(len(ack.GetServList().GetServ()), ShouldEqual, 0) + + for i := 0; i < 5; i++ { + resp := createInstanceWithSetID(serviceResp.GetService(), i, "", "") + defer cleanInstance(resp.GetId().GetValue()) + } + + ack, _ = safeSyncByAgentCmd(defaultCtx, reqCmd) + So(len(ack.GetServList().GetServ()), ShouldEqual, 5) + }) + Convey("一个实例属于一个set功能验证", func() { + // 新建一些带set的被调 + for i := 0; i < 10; i++ { + resp := createInstanceWithSetID(serviceResp.GetService(), i, fmt.Sprintf("SET_%d", i%2), "") + defer cleanInstance(resp.GetId().GetValue()) + } + + ack, _ := safeSyncByAgentCmd(defaultCtx, reqCmd) + So(len(ack.GetServList().GetServ()), ShouldEqual, 0) + + addL5Route(t, reqCmd.GetAgentIp(), modID, cmdID, "SET_1") + defer deleteL5Route(t, reqCmd.GetAgentIp(), modID, cmdID) + ack, _ = safeSyncByAgentCmd(defaultCtx, reqCmd) + So(len(ack.GetServList().GetServ()), ShouldEqual, 5) // SET_0 SET_1 各一半 + }) + Convey("一个实例多个set功能验证", func() { + // 新建一些带set的被调 + setIDs := "SET_X" + weights := "0" + for i := 0; i < 10; i++ { + setIDs = setIDs + fmt.Sprintf(",SET_%d", i) + weights = weights + fmt.Sprintf(",%d", (i+1)*100) + resp := createInstanceWithSetID(serviceResp.GetService(), i, setIDs, weights) + defer cleanInstance(resp.GetId().GetValue()) + } + // SET_X,SET_0, 0,100 + // SET_X,SET_0,SET_1 0,100,200 + // SET_X,SET_0,SET_1,SET_2 0,100,200,300 + // ... + ack, _ := safeSyncByAgentCmd(defaultCtx, reqCmd) + So(len(ack.GetServList().GetServ()), ShouldEqual, 0) + for i := 0; i < 10; i++ { + addL5Route(t, reqCmd.GetAgentIp(), modID, cmdID, fmt.Sprintf("SET_%d", i)) + ack, _ = safeSyncByAgentCmd(defaultCtx, reqCmd) + So(len(ack.GetServList().GetServ()), ShouldEqual, 10-i) + for _, callee := range ack.GetServList().GetServ() { + So(callee.GetWeight(), ShouldEqual, (i+1)*100) + } + } + + // SET_X weight=0 + addL5Route(t, reqCmd.GetAgentIp(), modID, cmdID, fmt.Sprintf("SET_X")) + ack, _ = safeSyncByAgentCmd(defaultCtx, reqCmd) + So(len(ack.GetServList().GetServ()), ShouldEqual, 0) + + addL5Route(t, reqCmd.GetAgentIp(), modID, cmdID, fmt.Sprintf("SET_%d", 20)) + defer deleteL5Route(t, reqCmd.GetAgentIp(), modID, cmdID) + ack, _ = safeSyncByAgentCmd(defaultCtx, reqCmd) + So(len(ack.GetServList().GetServ()), ShouldEqual, 0) + }) + }) +} + +// 测试根据埋点server获取到后端serverList的功能 +func TestCl5DiscoverTest(t *testing.T) { + createDiscoverServer := func(name string) *api.Service { + service := &api.Service{ + Name: utils.NewStringValue(name), + Namespace: utils.NewStringValue("Polaris"), + Owners: utils.NewStringValue("my"), + } + resp := server.CreateService(defaultCtx, service) + So(respSuccess(resp), ShouldEqual, true) + So(resp.GetService(), ShouldNotBeNil) + return resp.GetService() + } + createDiscoverInstance := func(service *api.Service, index int) *api.Instance { + instance := &api.Instance{ + Service: service.GetName(), + Namespace: service.GetNamespace(), + ServiceToken: service.GetToken(), + Host: utils.NewStringValue(fmt.Sprintf("10.0.0.%d", index)), + Port: utils.NewUInt32Value(7779), + Protocol: utils.NewStringValue("l5pb"), + Healthy: utils.NewBoolValue(true), + } + resp := server.CreateInstance(defaultCtx, instance) + So(respSuccess(resp), ShouldEqual, true) + So(resp.GetInstance(), ShouldNotBeNil) + return resp.GetInstance() + } + + Convey("测试根据埋点server获取到后端serverList的功能", t, func() { + reqCmd := &l5.Cl5SyncByAgentCmd{ + AgentIp: proto.Int32(123), + OptList: &l5.Cl5OptList{Opt: []*l5.Cl5OptObj{{ModId: proto.Int32(111), CmdId: proto.Int32(222)}}}, + } + name := "test-api.cl5.discover" + discover := createDiscoverServer(name) + defer cleanServiceName(discover.GetName().GetValue(), discover.GetNamespace().GetValue()) + instance := createDiscoverInstance(discover, 0) + defer cleanInstance(instance.GetId().GetValue()) + + discover1 := createDiscoverServer(name + ".1") + defer cleanServiceName(discover1.GetName().GetValue(), discover1.GetNamespace().GetValue()) + discover2 := createDiscoverServer(name + ".2") + defer cleanServiceName(discover2.GetName().GetValue(), discover2.GetNamespace().GetValue()) + + ctx := context.WithValue(defaultCtx, utils.Cl5ServerCluster{}, name) + ctx = context.WithValue(ctx, utils.Cl5ServerProtocol{}, "l5pb") + Convey("只有默认集群,则返回默认集群的数据", func() { + ack, _ := safeSyncByAgentCmd(ctx, reqCmd) + So(len(ack.GetL5SvrList().GetIp()), ShouldEqual, 1) + t.Logf("%+v", ack) + }) + Convey("不同请求IP获取到不同的集群", func() { + discover.Metadata = map[string]string{"internal-cluster-count": "2"} + So(respSuccess(server.UpdateService(defaultCtx, discover)), ShouldEqual, true) + instance1 := createDiscoverInstance(discover1, 1) + defer cleanInstance(instance1.GetId().GetValue()) + instance2 := createDiscoverInstance(discover1, 2) + defer cleanInstance(instance2.GetId().GetValue()) + + instance3 := createDiscoverInstance(discover2, 3) + defer cleanInstance(instance3.GetId().GetValue()) + instance4 := createDiscoverInstance(discover2, 4) + defer cleanInstance(instance4.GetId().GetValue()) + instance5 := createDiscoverInstance(discover2, 5) + defer cleanInstance(instance5.GetId().GetValue()) + + reqCmd.AgentIp = proto.Int32(56352420) // clusterIndex := ip %count + 1 + ack, _ := safeSyncByAgentCmd(ctx, reqCmd) + So(len(ack.GetL5SvrList().GetIp()), ShouldEqual, 2) // cluster1 + + reqCmd.AgentIp = proto.Int32(56352421) + ack, _ = safeSyncByAgentCmd(ctx, reqCmd) + So(len(ack.GetL5SvrList().GetIp()), ShouldEqual, 3) // cluster2 + }) + }) +} + +// 测试别名sid可以正常获取数据 +func TestCl5AliasSyncCmd(t *testing.T) { + reqCmd := &l5.Cl5SyncByAgentCmd{ + AgentIp: proto.Int32(11111), + SyncFlow: proto.Int32(22222), + } + testFunc := func(namespace string) { + Convey(fmt.Sprintf("%s, alias sid, discover ok", namespace), t, func() { + service := &api.Service{ + Name: utils.NewStringValue("my-name-for-alias"), + Namespace: utils.NewStringValue(namespace), + Owners: utils.NewStringValue("aa"), + } + resp := server.CreateService(defaultCtx, service) + So(respSuccess(resp), ShouldEqual, true) + serviceResp := resp.Service + defer cleanServiceName(serviceResp.Name.Value, serviceResp.Namespace.Value) + + resp = createCommonAlias(serviceResp, "", api.AliasType_CL5SID) + So(respSuccess(resp), ShouldEqual, true) + defer cleanServiceName(resp.Alias.Alias.Value, serviceResp.Namespace.Value) + modID, cmdID := parseStr2Sid(resp.Alias.Alias.Value) + reqCmd.OptList = &l5.Cl5OptList{ + Opt: []*l5.Cl5OptObj{{ModId: proto.Int32(int32(modID)), CmdId: proto.Int32(int32(cmdID))}}, + } + + count := 5 + for i := 0; i < count; i++ { + _, instanceResp := createCommonInstance(t, serviceResp, i) + defer cleanInstance(instanceResp.GetId().GetValue()) + } + time.Sleep(updateCacheInterval) + + ack, _ := server.SyncByAgentCmd(defaultCtx, reqCmd) + So(ack.GetAgentIp(), ShouldEqual, reqCmd.GetAgentIp()) + So(ack.GetSyncFlow(), ShouldEqual, reqCmd.GetSyncFlow()+1) + So(len(ack.GetServList().GetServ()), ShouldEqual, count) + + }) + } + + namespaces := []string{"default", "Polaris"} + for _, entry := range namespaces { + testFunc(entry) + } +} diff --git a/naming/test/main_test.go b/naming/test/main_test.go new file mode 100644 index 000000000..944e6f7a8 --- /dev/null +++ b/naming/test/main_test.go @@ -0,0 +1,1406 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package test + +import ( + "context" + "database/sql" + "encoding/json" + "fmt" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/plugin" + "github.com/golang/protobuf/ptypes/duration" + "strconv" + "strings" + "testing" + "time" + + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/utils" + "github.com/polarismesh/polaris-server/config" + "github.com/polarismesh/polaris-server/naming" + "github.com/polarismesh/polaris-server/store" + + // 使用mysql库 + "os" + "sync" + + _ "github.com/polarismesh/polaris-server/plugin/history/logger" + _ "github.com/polarismesh/polaris-server/plugin/ratelimit/tokenBucket" + _ "github.com/polarismesh/polaris-server/store/defaultStore" + _ "github.com/go-sql-driver/mysql" + "gopkg.in/yaml.v2" +) + +var ( + cfg = config.Config{} + once = sync.Once{} + server = &naming.Server{} + db = &sql.DB{} + cancelFlag = false + updateCacheInterval = time.Second * 2 + defaultCtx = context.Background() +) + +// 加载配置 +func loadConfig() error { + file, err := os.Open("test.yaml") + if err != nil { + fmt.Printf("[ERROR] %v\n", err) + return err + } + + err = yaml.NewDecoder(file).Decode(&cfg) + if err != nil { + fmt.Printf("[ERROR] %v\n", err) + return err + } + + return err +} + +// 判断一个resp是否执行成功 +func respSuccess(resp api.ResponseMessage) bool { + if api.CalcCode(resp) != 200 { + return false + } + + return true +} + +// 内部初始化函数 +func initialize() error { + options := log.DefaultOptions() + options.SetLogCallers(log.DefaultScopeName, true) + _ = log.Configure(options) + var err error + once.Do(func() { + err = loadConfig() + if err != nil { + return + } + // 初始化defaultCtx + defaultCtx = context.WithValue(defaultCtx, utils.StringContext("request-id"), "test-1") + + // 初始化存储层 + store.SetStoreConfig(&cfg.Store) + _, _ = store.GetStore() + + plugin.SetPluginConfig(&cfg.Plugin) + + // 初始化Naming Server + ctx, cancel := context.WithCancel(context.Background()) + defer func() { + if cancelFlag { + cancel() + } + }() + + naming.SetHealthCheckConfig(&cfg.Naming.HealthCheck) + if err := naming.Initialize(ctx, &cfg.Naming, &cfg.Cache); err != nil { + panic(err) + } + + server, err = naming.GetServer() + if err != nil { + panic(err) + } + + masterEntry := cfg.Store.Option["master"] + masterConfig, ok := masterEntry.(map[interface{}]interface{}) + if !ok { + panic("database cfg is invalid") + } + + dbType := masterConfig["dbType"].(string) + dbUser := masterConfig["dbUser"].(string) + dbPwd := masterConfig["dbPwd"].(string) + dbAddr := masterConfig["dbAddr"].(string) + dbName := masterConfig["dbName"].(string) + + dbSource := fmt.Sprintf("%s:%s@tcp(%s)/%s", dbUser, dbPwd, dbAddr, dbName) + db, err = sql.Open(dbType, dbSource) + if err != nil { + panic(err) + } + + // 多等待一会 + updateCacheInterval = server.Cache().GetUpdateCacheInterval() + time.Millisecond*500 + }) + + return err +} + +// 从数据库彻底删除命名空间 +func cleanNamespace(name string) { + if name == "" { + panic("name is empty") + } + + log.Infof("clean namespace: %s", name) + str := "delete from namespace where name = ?" + if _, err := db.Exec(str, name); err != nil { + panic(err) + } +} + +// 从数据库彻底删除服务 +func cleanService(id, name, namespace string) { + if id == "" { + panic("id is empty") + } + + log.Infof("clean service: %s", id) + str := "delete from service_metadata where id = ?" + if _, err := db.Exec(str, id); err != nil { + panic(err) + } + + str = "delete from service where id = ?" + if _, err := db.Exec(str, id); err != nil { + panic(err) + } + + str = "delete from owner_service_map where service=? and namespace=?" + if _, err := db.Exec(str, name, namespace); err != nil { + panic(err) + } +} + +// 从数据库彻底删除服务名对应的服务 +func cleanServiceName(name string, namespace string) { + log.Infof("clean service %s, %s", name, namespace) + str := "select id from service where name = ? and namespace = ?" + var id string + err := db.QueryRow(str, name, namespace).Scan(&id) + switch { + case err == sql.ErrNoRows: + return + case err != nil: + panic(err) + } + + cleanService(id, name, namespace) +} + +// clean services +func cleanServices(services []*api.Service) { + str := "delete from service where name = ? and namespace = ?" + cleanOwnerSql := "delete from owner_service_map where service=? and namespace=?" + for _, service := range services { + if _, err := db.Exec(str, service.GetName().GetValue(), service.GetNamespace().GetValue()); err != nil { + panic(err) + } + if _, err := db.Exec(cleanOwnerSql, service.GetName().GetValue(), service.GetNamespace().GetValue()); err != nil { + panic(err) + } + } +} + +// 从数据库彻底删除实例 +func cleanInstance(instanceID string) { + if instanceID == "" { + panic("instanceID is empty") + } + + /*str := "delete from health_check where id = ?" + if _, err := db.Exec(str, instanceID); err != nil { + panic(err) + } + + str = "delete from instance_metadata where id = ?" + if _, err := db.Exec(str, instanceID); err != nil { + panic(err) + }*/ + log.Infof("clean instance: %s", instanceID) + str := "delete from instance where id = ?" + if _, err := db.Exec(str, instanceID); err != nil { + panic(err) + } +} + +// 增加一个服务 +func createCommonService(t *testing.T, id int) (*api.Service, *api.Service) { + serviceReq := genMainService(id) + for i := 0; i < 10; i++ { + k := fmt.Sprintf("key-%d-%d", id, i) + v := fmt.Sprintf("value-%d-%d", id, i) + serviceReq.Metadata[k] = v + } + + cleanServiceName(serviceReq.GetName().GetValue(), serviceReq.GetNamespace().GetValue()) + + resp := server.CreateService(defaultCtx, serviceReq) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + + return serviceReq, resp.GetService() +} + +// 生成服务的主要数据 +func genMainService(id int) *api.Service { + return &api.Service{ + Name: utils.NewStringValue(fmt.Sprintf("test-service-%d", id)), + Namespace: utils.NewStringValue(naming.DefaultNamespace), + Metadata: make(map[string]string), + Ports: utils.NewStringValue(fmt.Sprintf("ports-%d", id)), + Business: utils.NewStringValue(fmt.Sprintf("business-%d", id)), + Department: utils.NewStringValue(fmt.Sprintf("department-%d", id)), + CmdbMod1: utils.NewStringValue(fmt.Sprintf("cmdb-mod1-%d", id)), + CmdbMod2: utils.NewStringValue(fmt.Sprintf("cmdb-mod2-%d", id)), + CmdbMod3: utils.NewStringValue(fmt.Sprintf("cmdb-mod2-%d", id)), + Comment: utils.NewStringValue(fmt.Sprintf("service-comment-%d", id)), + Owners: utils.NewStringValue(fmt.Sprintf("service-owner-%d", id)), + } +} + +// removeCommonService +func removeCommonServices(t *testing.T, req []*api.Service) { + if resp := server.DeleteServices(defaultCtx, req); !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } +} + +// 新增一个实例 +func createCommonInstance(t *testing.T, service *api.Service, id int) ( + *api.Instance, *api.Instance) { + instanceReq := &api.Instance{ + ServiceToken: utils.NewStringValue(service.GetToken().GetValue()), + Service: utils.NewStringValue(service.GetName().GetValue()), + Namespace: utils.NewStringValue(service.GetNamespace().GetValue()), + VpcId: utils.NewStringValue(fmt.Sprintf("vpcid-%d", id)), + Host: utils.NewStringValue(fmt.Sprintf("9.9.9.%d", id)), + Port: utils.NewUInt32Value(8000 + uint32(id)), + Protocol: utils.NewStringValue(fmt.Sprintf("protocol-%d", id)), + Version: utils.NewStringValue(fmt.Sprintf("version-%d", id)), + Priority: utils.NewUInt32Value(1 + uint32(id)%10), + Weight: utils.NewUInt32Value(1 + uint32(id)%1000), + HealthCheck: &api.HealthCheck{ + Type: api.HealthCheck_HEARTBEAT, + Heartbeat: &api.HeartbeatHealthCheck{ + Ttl: utils.NewUInt32Value(3), + }, + }, + Healthy: utils.NewBoolValue(false), // 默认是非健康,因为打开了healthCheck + Isolate: utils.NewBoolValue(false), + LogicSet: utils.NewStringValue(fmt.Sprintf("logic-set-%d", id)), + Metadata: map[string]string{ + "internal-personal-xxx": fmt.Sprintf("internal-personal-xxx_%d", id), + "2my-meta": fmt.Sprintf("my-meta-%d", id), + "my-meta-a1": "1111", + "smy-xmeta-h2": "2222", + "my-1meta-o3": "2222", + "my-2meta-4c": "2222", + "my-3meta-d5": "2222", + "dmy-meta-6p": "2222", + "1my-pmeta-d7": "2222", + "my-dmeta-8c": "2222", + "my-xmeta-9p": "2222", + "other-meta-x": "xxx", + "other-meta-1": "xx11", + "amy-instance": "my-instance", + "very-long-key-data-xxxxxxxxx": "Y", + "very-long-key-data-uuuuuuuuu": "P", + }, + } + + resp := server.CreateInstance(defaultCtx, instanceReq) + if respSuccess(resp) { + return instanceReq, resp.GetInstance() + } + + if resp.GetCode().GetValue() != api.ExistedResource { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + + // repeated + InstanceID, _ := naming.CalculateInstanceID(instanceReq.GetNamespace().GetValue(), instanceReq.GetService().GetValue(), + instanceReq.GetVpcId().GetValue(), instanceReq.GetHost().GetValue(), instanceReq.GetPort().GetValue()) + cleanInstance(InstanceID) + t.Logf("repeatd create instance(%s)", InstanceID) + resp = server.CreateInstance(defaultCtx, instanceReq) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + + return instanceReq, resp.GetInstance() +} + +//指定 IP 和端口为一个服务创建实例 +func addHostPortInstance(t *testing.T, service *api.Service, host string, port uint32) ( + *api.Instance, *api.Instance) { + instanceReq := &api.Instance{ + ServiceToken: utils.NewStringValue(service.GetToken().GetValue()), + Service: utils.NewStringValue(service.GetName().GetValue()), + Namespace: utils.NewStringValue(service.GetNamespace().GetValue()), + Host: utils.NewStringValue(host), + Port: utils.NewUInt32Value(port), + Healthy: utils.NewBoolValue(true), + Isolate: utils.NewBoolValue(false), + } + resp := server.CreateInstance(defaultCtx, instanceReq) + if respSuccess(resp) { + return instanceReq, resp.GetInstance() + } + + if resp.GetCode().GetValue() != api.ExistedResource { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + return instanceReq, resp.GetInstance() +} + +// 添加一个实例 +func addInstance(t *testing.T, ins *api.Instance) ( + *api.Instance, *api.Instance) { + resp := server.CreateInstance(defaultCtx, ins) + if !respSuccess(resp) { + if resp.GetCode().GetValue() == api.ExistedResource { + id, _ := naming.CalculateInstanceID(ins.GetNamespace().GetValue(), ins.GetService().GetValue(), + ins.GetHost().GetValue(), ins.GetHost().GetValue(), ins.GetPort().GetValue()) + cleanInstance(id) + } + } else { + return ins, resp.GetInstance() + } + + resp = server.CreateInstance(defaultCtx, ins) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + + return ins, resp.GetInstance() +} + +// 删除一个实例 +func removeCommonInstance(t *testing.T, service *api.Service, instanceID string) { + req := &api.Instance{ + ServiceToken: utils.NewStringValue(service.GetToken().GetValue()), + Id: utils.NewStringValue(instanceID), + } + + resp := server.DeleteInstance(defaultCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + + return +} + +// 通过四元组或者五元组删除实例 +func removeInstanceWithAttrs(t *testing.T, service *api.Service, instance *api.Instance) { + req := &api.Instance{ + ServiceToken: utils.NewStringValue(service.GetToken().GetValue()), + Service: utils.NewStringValue(service.GetName().GetValue()), + Namespace: utils.NewStringValue(service.GetNamespace().GetValue()), + VpcId: utils.NewStringValue(instance.GetVpcId().GetValue()), + Host: utils.NewStringValue(instance.GetHost().GetValue()), + Port: utils.NewUInt32Value(instance.GetPort().GetValue()), + } + if resp := server.DeleteInstance(defaultCtx, req); !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + return +} + +// 创建一个路由配置 +func createCommonRoutingConfig(t *testing.T, service *api.Service, inCount int, outCount int) (*api.Routing, *api.Routing) { + inBounds := make([]*api.Route, 0, inCount) + for i := 0; i < inCount; i++ { + matchString := &api.MatchString{ + Type: api.MatchString_EXACT, + Value: utils.NewStringValue(fmt.Sprintf("in-meta-value-%d", i)), + } + source := &api.Source{ + Service: utils.NewStringValue(fmt.Sprintf("in-source-service-%d", i)), + Namespace: utils.NewStringValue(fmt.Sprintf("in-source-service-%d", i)), + Metadata: map[string]*api.MatchString{ + fmt.Sprintf("in-metadata-%d", i): matchString, + }, + } + destination := &api.Destination{ + Service: utils.NewStringValue(fmt.Sprintf("in-destination-service-%d", i)), + Namespace: utils.NewStringValue(fmt.Sprintf("in-destination-service-%d", i)), + Metadata: map[string]*api.MatchString{ + fmt.Sprintf("in-metadata-%d", i): matchString, + }, + Priority: utils.NewUInt32Value(120), + Weight: utils.NewUInt32Value(100), + Transfer: utils.NewStringValue("abcdefg"), + } + + entry := &api.Route{ + Sources: []*api.Source{source}, + Destinations: []*api.Destination{destination}, + } + inBounds = append(inBounds, entry) + } + + conf := &api.Routing{ + Service: utils.NewStringValue(service.GetName().GetValue()), + Namespace: utils.NewStringValue(service.GetNamespace().GetValue()), + Inbounds: inBounds, + ServiceToken: utils.NewStringValue(service.GetToken().GetValue()), + } + + // TODO 是否应该先删除routing + + resp := server.CreateRoutingConfig(defaultCtx, conf) + if !respSuccess(resp) { + t.Fatalf("error: %+v", resp) + } + + return conf, resp.GetRouting() +} + +// 删除一个路由配置 +func deleteCommonRoutingConfig(t *testing.T, req *api.Routing) { + resp := server.DeleteRoutingConfig(defaultCtx, req) + if !respSuccess(resp) { + t.Fatalf("%s", resp.GetInfo().GetValue()) + } +} + +// 更新一个路由配置 +func updateCommonRoutingConfig(t *testing.T, req *api.Routing) { + resp := server.UpdateRoutingConfig(defaultCtx, req) + if !respSuccess(resp) { + t.Fatalf("%s", resp.GetInfo().GetValue()) + } +} + +// 彻底删除一个路由配置 +func cleanCommonRoutingConfig(service string, namespace string) { + str := "delete from routing_config where id in (select id from service where name = ? and namespace = ?)" + //fmt.Printf("%s %s %s\n", str, service, namespace) + if _, err := db.Exec(str, service, namespace); err != nil { + panic(err) + } + return +} + +// +func CheckGetService(t *testing.T, expectReqs []*api.Service, actualReqs []*api.Service) { + if len(expectReqs) != len(actualReqs) { + t.Fatalf("error: %d %d", len(expectReqs), len(actualReqs)) + } + + for _, expect := range expectReqs { + found := false + for _, actual := range actualReqs { + if expect.GetName().GetValue() != actual.GetName().GetValue() || + expect.GetNamespace().GetValue() != actual.GetNamespace().GetValue() { + continue + } + + found = true + + if expect.GetPorts().GetValue() != actual.GetPorts().GetValue() || + expect.GetOwners().GetValue() != actual.GetOwners().GetValue() || + expect.GetComment().GetValue() != actual.GetComment().GetValue() || + actual.GetToken().GetValue() != "" || actual.GetRevision().GetValue() == "" { + t.Fatalf("error: %+v, %+v", expect, actual) + } + + if len(expect.Metadata) != len(actual.Metadata) { + t.Fatalf("error: %d, %d", len(expect.Metadata), len(actual.Metadata)) + } + for key, value := range expect.Metadata { + match, ok := actual.Metadata[key] + if !ok { + t.Fatalf("error") + } + if value != match { + t.Fatalf("error") + } + } + } + if !found { + t.Fatalf("error: %s, %s", expect.GetName().GetValue(), expect.GetNamespace().GetValue()) + } + + } +} + +// 检查服务发现的字段是否一致 +func discoveryCheck(t *testing.T, req *api.Service, resp *api.DiscoverResponse) { + if resp == nil { + t.Fatalf("error") + } + + if resp.GetService().GetName().GetValue() != req.GetName().GetValue() || + resp.GetService().GetNamespace().GetValue() != req.GetNamespace().GetValue() || + resp.GetService().GetRevision().GetValue() == "" { + t.Fatalf("error: %+v", resp) + } + + if resp.Service == nil { + t.Fatalf("error") + } + //t.Logf("%+v", resp.Service) + + if resp.Service.GetName().GetValue() != req.GetName().GetValue() || + resp.Service.GetNamespace().GetValue() != req.GetNamespace().GetValue() { + t.Fatalf("error: %+v", resp.Service) + } +} + +// 实例校验 +func instanceCheck(t *testing.T, expect *api.Instance, actual *api.Instance) { + // #lizard forgives + switch { + case expect.GetService().GetValue() != actual.GetService().GetValue(): + t.Fatalf("error %s---%s", expect.GetService().GetValue(), actual.GetService().GetValue()) + case expect.GetNamespace().GetValue() != actual.GetNamespace().GetValue(): + t.Fatalf("error") + case expect.GetPort().GetValue() != actual.GetPort().GetValue(): + t.Fatalf("error") + case expect.GetHost().GetValue() != actual.GetHost().GetValue(): + t.Fatalf("error") + case expect.GetVpcId().GetValue() != actual.GetVpcId().GetValue(): + t.Fatalf("error") + case expect.GetProtocol().GetValue() != actual.GetProtocol().GetValue(): + t.Fatalf("error") + case expect.GetVersion().GetValue() != actual.GetVersion().GetValue(): + t.Fatalf("error") + case expect.GetWeight().GetValue() != actual.GetWeight().GetValue(): + t.Fatalf("error") + case expect.GetHealthy().GetValue() != actual.GetHealthy().GetValue(): + t.Fatalf("error") + case expect.GetIsolate().GetValue() != actual.GetIsolate().GetValue(): + t.Fatalf("error") + case expect.GetLogicSet().GetValue() != actual.GetLogicSet().GetValue(): + t.Fatalf("error") + default: + break + + // 实例创建,无法指定cmdb信息 + /*case expect.GetCmdbRegion().GetValue() != actual.GetCmdbRegion().GetValue(): + t.Fatalf("error") + case expect.GetCmdbCampus().GetValue() != actual.GetCmdbRegion().GetValue(): + t.Fatalf("error") + case expect.GetCmdbZone().GetValue() != actual.GetCmdbZone().GetValue(): + t.Fatalf("error")*/ + + } + for key, value := range expect.GetMetadata() { + actualValue := actual.GetMetadata()[key] + if value != actualValue { + t.Fatalf("error %+v, %+v", expect.Metadata, actual.Metadata) + } + } + + if expect.GetHealthCheck().GetType() != actual.GetHealthCheck().GetType() { + t.Fatalf("error") + } + if expect.GetHealthCheck().GetHeartbeat().GetTtl().GetValue() != + actual.GetHealthCheck().GetHeartbeat().GetTtl().GetValue() { + t.Fatalf("error") + } +} + +// 完整对比service的各个属性 +func serviceCheck(t *testing.T, expect *api.Service, actual *api.Service) { + switch { + case expect.GetName().GetValue() != actual.GetName().GetValue(): + t.Fatalf("error") + case expect.GetNamespace().GetValue() != actual.GetNamespace().GetValue(): + t.Fatalf("error") + case expect.GetPorts().GetValue() != actual.GetPorts().GetValue(): + t.Fatalf("error") + case expect.GetBusiness().GetValue() != actual.GetBusiness().GetValue(): + t.Fatalf("error") + case expect.GetDepartment().GetValue() != actual.GetDepartment().GetValue(): + t.Fatalf("error") + case expect.GetCmdbMod1().GetValue() != actual.GetCmdbMod1().GetValue(): + t.Fatalf("error") + case expect.GetCmdbMod2().GetValue() != actual.GetCmdbMod2().GetValue(): + t.Fatalf("error") + case expect.GetCmdbMod3().GetValue() != actual.GetCmdbMod3().GetValue(): + t.Fatalf("error") + case expect.GetComment().GetValue() != actual.GetComment().GetValue(): + t.Fatalf("error") + case expect.GetOwners().GetValue() != actual.GetOwners().GetValue(): + t.Fatalf("error") + default: + break + } + + for key, value := range expect.GetMetadata() { + actualValue := actual.GetMetadata()[key] + if actualValue != value { + t.Fatalf("error") + } + } +} + +// 创建限流规则 +func createCommonRateLimit(t *testing.T, service *api.Service, index int) (*api.Rule, *api.Rule) { + // 先不考虑Cluster + rateLimit := &api.Rule{ + Service: service.GetName(), + Namespace: service.GetNamespace(), + Priority: utils.NewUInt32Value(uint32(index)), + Resource: api.Rule_QPS, + Type: api.Rule_GLOBAL, + Labels: map[string]*api.MatchString{ + fmt.Sprintf("name-%d", index): { + Type: api.MatchString_EXACT, + Value: utils.NewStringValue(fmt.Sprintf("value-%d", index)), + }, + fmt.Sprintf("name-%d", index+1): { + Type: api.MatchString_REGEX, + Value: utils.NewStringValue(fmt.Sprintf("value-%d", index+1)), + }, + }, + Amounts: []*api.Amount{ + { + MaxAmount: utils.NewUInt32Value(uint32(10 * index)), + ValidDuration: &duration.Duration{ + Seconds: int64(index), + Nanos: int32(index), + }, + }, + }, + Action: utils.NewStringValue(fmt.Sprintf("behavior-%d", index)), + Disable: utils.NewBoolValue(false), + Report: &api.Report{ + Interval: &duration.Duration{ + Seconds: int64(index), + }, + AmountPercent: utils.NewUInt32Value(uint32(index)), + }, + ServiceToken: utils.NewStringValue(service.GetToken().GetValue()), + } + + resp := server.CreateRateLimit(defaultCtx, rateLimit) + if !respSuccess(resp) { + t.Fatalf("error: %+v", resp) + } + return rateLimit, resp.GetRateLimit() +} + +// 删除限流规则 +func deleteRateLimit(t *testing.T, rateLimit *api.Rule) { + if resp := server.DeleteRateLimit(defaultCtx, rateLimit); !respSuccess(resp) { + t.Fatalf("%s", resp.GetInfo().GetValue()) + } +} + +// 更新单个限流规则 +func updateRateLimit(t *testing.T, rateLimit *api.Rule) { + if resp := server.UpdateRateLimit(defaultCtx, rateLimit); !respSuccess(resp) { + t.Fatalf("%s", resp.GetInfo().GetValue()) + } +} + +// 彻底删除限流规则 +func cleanRateLimit(id string) { + str := `delete from ratelimit_config where id = ?` + if _, err := db.Exec(str, id); err != nil { + panic(err) + } +} + +// 彻底删除限流规则版本号 +func cleanRateLimitRevision(service, namespace string) { + str := `delete from ratelimit_revision using ratelimit_revision, service + where service_id = service.id and name = ? and namespace = ?` + if _, err := db.Exec(str, service, namespace); err != nil { + panic(err) + } +} + +// 更新限流规则内容 +func updateRateLimitContent(rateLimit *api.Rule, index int) { + rateLimit.Priority = utils.NewUInt32Value(uint32(index)) + rateLimit.Resource = api.Rule_CONCURRENCY + rateLimit.Type = api.Rule_LOCAL + rateLimit.Labels = map[string]*api.MatchString{ + fmt.Sprintf("name-%d", index): { + Type: api.MatchString_EXACT, + Value: utils.NewStringValue(fmt.Sprintf("value-%d", index)), + }, + fmt.Sprintf("name-%d", index+1): { + Type: api.MatchString_REGEX, + Value: utils.NewStringValue(fmt.Sprintf("value-%d", index+1)), + }, + } + rateLimit.Amounts = []*api.Amount{ + { + MaxAmount: utils.NewUInt32Value(uint32(index)), + ValidDuration: &duration.Duration{ + Seconds: int64(index), + }, + }, + } + rateLimit.Action = utils.NewStringValue(fmt.Sprintf("value-%d", index)) + rateLimit.Disable = utils.NewBoolValue(true) + rateLimit.Report = &api.Report{ + Interval: &duration.Duration{ + Seconds: int64(index), + }, + AmountPercent: utils.NewUInt32Value(uint32(index)), + } +} + +/* + * @brief 对比限流规则的各个属性 + */ +func checkRateLimit(t *testing.T, expect *api.Rule, actual *api.Rule) { + switch { + case expect.GetId().GetValue() != actual.GetId().GetValue(): + t.Fatal("error id") + case expect.GetService().GetValue() != actual.GetService().GetValue(): + t.Fatal("error service") + case expect.GetNamespace().GetValue() != actual.GetNamespace().GetValue(): + t.Fatal("error namespace") + case expect.GetPriority().GetValue() != actual.GetPriority().GetValue(): + t.Fatal("error priority") + case expect.GetResource() != actual.GetResource(): + t.Fatal("error resource") + case expect.GetType() != actual.GetType(): + t.Fatal("error type") + case expect.GetDisable().GetValue() != actual.GetDisable().GetValue(): + t.Fatal("error disable") + case expect.GetAction().GetValue() != actual.GetAction().GetValue(): + t.Fatal("error action") + default: + break + } + + expectSubset, err := json.Marshal(expect.GetSubset()) + if err != nil { + panic(err) + } + actualSubset, err := json.Marshal(actual.GetSubset()) + if err != nil { + panic(err) + } + if string(expectSubset) != string(actualSubset) { + t.Fatal("error subset") + } + + expectLabels, err := json.Marshal(expect.GetLabels()) + if err != nil { + panic(err) + } + actualLabels, err := json.Marshal(actual.GetLabels()) + if err != nil { + panic(err) + } + if string(expectLabels) != string(actualLabels) { + t.Fatal("error labels") + } + + expectAmounts, err := json.Marshal(expect.GetAmounts()) + if err != nil { + panic(err) + } + actualAmounts, err := json.Marshal(actual.GetAmounts()) + if err != nil { + panic(err) + } + if string(expectAmounts) != string(actualAmounts) { + t.Fatal("error amounts") + } + + expectReport, err := json.Marshal(expect.GetReport()) + if err != nil { + panic(err) + } + actualReport, err := json.Marshal(actual.GetReport()) + if err != nil { + panic(err) + } + if string(expectReport) != string(actualReport) { + t.Fatal("error report") + } + t.Log("check success") +} + +// 增加熔断规则 +func createCommonCircuitBreaker(t *testing.T, id int) (*api.CircuitBreaker, *api.CircuitBreaker) { + circuitBreaker := &api.CircuitBreaker{ + Name: utils.NewStringValue(fmt.Sprintf("name-test-%d", id)), + Namespace: utils.NewStringValue(naming.DefaultNamespace), + Owners: utils.NewStringValue("owner-test"), + Comment: utils.NewStringValue("comment-test"), + Department: utils.NewStringValue("department-test"), + Business: utils.NewStringValue("business-test"), + } + ruleNum := 1 + // 填充source规则 + sources := make([]*api.SourceMatcher, 0, ruleNum) + for i := 0; i < ruleNum; i++ { + source := &api.SourceMatcher{ + Service: utils.NewStringValue(fmt.Sprintf("service-test-%d", i)), + Namespace: utils.NewStringValue(fmt.Sprintf("namespace-test-%d", i)), + Labels: map[string]*api.MatchString{ + fmt.Sprintf("name-%d", i): { + Type: api.MatchString_EXACT, + Value: utils.NewStringValue(fmt.Sprintf("value-%d", i)), + }, + fmt.Sprintf("name-%d", i+1): { + Type: api.MatchString_REGEX, + Value: utils.NewStringValue(fmt.Sprintf("value-%d", i+1)), + }, + }, + } + sources = append(sources, source) + } + + // 填充destination规则 + destinations := make([]*api.DestinationSet, 0, ruleNum) + for i := 0; i < ruleNum; i++ { + destination := &api.DestinationSet{ + Service: utils.NewStringValue(fmt.Sprintf("service-test-%d", i)), + Namespace: utils.NewStringValue(fmt.Sprintf("namespace-test-%d", i)), + Metadata: map[string]*api.MatchString{ + fmt.Sprintf("name-%d", i): { + Type: api.MatchString_EXACT, + Value: utils.NewStringValue(fmt.Sprintf("value-%d", i)), + }, + fmt.Sprintf("name-%d", i+1): { + Type: api.MatchString_REGEX, + Value: utils.NewStringValue(fmt.Sprintf("value-%d", i+1)), + }, + }, + Resource: 0, + Type: 0, + Scope: 0, + MetricWindow: &duration.Duration{ + Seconds: int64(i), + }, + MetricPrecision: utils.NewUInt32Value(uint32(i)), + UpdateInterval: &duration.Duration{ + Seconds: int64(i), + }, + Recover: &api.RecoverConfig{}, + Policy: &api.CbPolicy{}, + } + destinations = append(destinations, destination) + } + + // 填充inbound规则 + inbounds := make([]*api.CbRule, 0, ruleNum) + for i := 0; i < ruleNum; i++ { + inbound := &api.CbRule{ + Sources: sources, + Destinations: destinations, + } + inbounds = append(inbounds, inbound) + } + // 填充outbound规则 + outbounds := make([]*api.CbRule, 0, ruleNum) + for i := 0; i < ruleNum; i++ { + outbound := &api.CbRule{ + Sources: sources, + Destinations: destinations, + } + outbounds = append(outbounds, outbound) + } + circuitBreaker.Inbounds = inbounds + circuitBreaker.Outbounds = outbounds + + resp := server.CreateCircuitBreaker(defaultCtx, circuitBreaker) + if !respSuccess(resp) { + t.Fatalf("error: %+v", resp) + } + return circuitBreaker, resp.GetCircuitBreaker() +} + +// 增加熔断规则版本 +func createCommonCircuitBreakerVersion(t *testing.T, cb *api.CircuitBreaker, index int) ( + *api.CircuitBreaker, *api.CircuitBreaker) { + cbVersion := &api.CircuitBreaker{ + Id: cb.GetId(), + Name: cb.GetName(), + Namespace: cb.GetNamespace(), + Version: utils.NewStringValue(fmt.Sprintf("test-version-%d", index)), + Inbounds: cb.GetInbounds(), + Outbounds: cb.GetOutbounds(), + Token: cb.GetToken(), + } + + resp := server.CreateCircuitBreakerVersion(defaultCtx, cbVersion) + if !respSuccess(resp) { + t.Fatalf("error: %+v", resp) + } + return cbVersion, resp.GetCircuitBreaker() +} + +// 删除熔断规则 +func deleteCircuitBreaker(t *testing.T, circuitBreaker *api.CircuitBreaker) { + if resp := server.DeleteCircuitBreaker(defaultCtx, circuitBreaker); !respSuccess(resp) { + t.Fatalf("%s", resp.GetInfo().GetValue()) + } +} + +// 更新熔断规则内容 +func updateCircuitBreaker(t *testing.T, circuitBreaker *api.CircuitBreaker) { + if resp := server.UpdateCircuitBreaker(defaultCtx, circuitBreaker); !respSuccess(resp) { + t.Fatalf("%s", resp.GetInfo().GetValue()) + } +} + +// 发布熔断规则 +func releaseCircuitBreaker(t *testing.T, cb *api.CircuitBreaker, service *api.Service) { + release := &api.ConfigRelease{ + Service: service, + CircuitBreaker: cb, + } + + resp := server.ReleaseCircuitBreaker(defaultCtx, release) + if !respSuccess(resp) { + t.Fatalf("error: %+v", resp) + } +} + +// 解绑熔断规则 +func unBindCircuitBreaker(t *testing.T, cb *api.CircuitBreaker, service *api.Service) { + unbind := &api.ConfigRelease{ + Service: service, + CircuitBreaker: cb, + } + + resp := server.UnBindCircuitBreaker(defaultCtx, unbind) + if !respSuccess(resp) { + t.Fatalf("error: %+v", resp) + } +} + +// 对比熔断规则的各个属性 +func checkCircuitBreaker(t *testing.T, expect, expectMaster *api.CircuitBreaker, actual *api.CircuitBreaker) { + switch { + case expectMaster.GetId().GetValue() != actual.GetId().GetValue(): + t.Fatal("error id") + case expect.GetVersion().GetValue() != actual.GetVersion().GetValue(): + t.Fatal("error version") + case expectMaster.GetName().GetValue() != actual.GetName().GetValue(): + t.Fatal("error name") + case expectMaster.GetNamespace().GetValue() != actual.GetNamespace().GetValue(): + t.Fatal("error namespace") + case expectMaster.GetOwners().GetValue() != actual.GetOwners().GetValue(): + t.Fatal("error owners") + case expectMaster.GetComment().GetValue() != actual.GetComment().GetValue(): + t.Fatal("error comment") + case expectMaster.GetBusiness().GetValue() != actual.GetBusiness().GetValue(): + t.Fatal("error business") + case expectMaster.GetDepartment().GetValue() != actual.GetDepartment().GetValue(): + t.Fatal("error department") + default: + break + } + + expectInbounds, err := json.Marshal(expect.GetInbounds()) + if err != nil { + panic(err) + } + inbounds, err := json.Marshal(actual.GetInbounds()) + if err != nil { + panic(err) + } + if string(expectInbounds) != string(inbounds) { + t.Fatal("error inbounds") + } + + expectOutbounds, err := json.Marshal(expect.GetOutbounds()) + if err != nil { + panic(err) + } + outbounds, err := json.Marshal(actual.GetOutbounds()) + if err != nil { + panic(err) + } + if string(expectOutbounds) != string(outbounds) { + t.Fatal("error inbounds") + } + t.Log("check success") +} + +// 彻底删除熔断规则 +func cleanCircuitBreaker(id, version string) { + log.Infof("clean circuit breaker, id: %s, version: %s", id, version) + str := `delete from circuitbreaker_rule where id = ? and version = ?` + if _, err := db.Exec(str, id, version); err != nil { + panic(err) + } +} + +// 彻底删除熔断规则发布记录 +func cleanCircuitBreakerRelation(name, namespace, ruleID, ruleVersion string) { + str := `delete from circuitbreaker_rule_relation using circuitbreaker_rule_relation, service where + service_id = service.id and name = ? and namespace = ? and rule_id = ? and rule_version = ?` + if _, err := db.Exec(str, name, namespace, ruleID, ruleVersion); err != nil { + panic(err) + } +} + +// 创建一个网格规则 +func createMeshResource(typeUrl, meshID, meshToken, rule string) (*api.MeshResource, *api.Response) { + resource := &api.MeshResource{ + MeshId: utils.NewStringValue(meshID), + MeshToken: utils.NewStringValue(meshToken), + TypeUrl: utils.NewStringValue(typeUrl), + Body: utils.NewStringValue(rule), + } + reqResource := &api.MeshResource{ + MeshId: utils.NewStringValue(meshID), + MeshToken: utils.NewStringValue(meshToken), + TypeUrl: utils.NewStringValue(typeUrl), + Body: utils.NewStringValue(rule), + } + return reqResource, server.CreateMeshResource(defaultCtx, resource) +} + +// 创建一个网格 +func createMesh(req *api.Mesh, withSystemToken bool) *api.Response { + ctx := defaultCtx + if withSystemToken { + ctx = context.Background() + ctx = context.WithValue(ctx, utils.StringContext("polaris-token"), "polaris@12345678") + } + return server.CreateMesh(ctx, req) +} + +func checkReqMesh(t *testing.T, expect *api.Mesh, actual *api.Mesh) { + switch { + case actual.GetName().GetValue() != expect.GetName().GetValue(): + t.Fatalf("mesh name not match") + case actual.GetBusiness().GetValue() != expect.GetBusiness().GetValue(): + t.Fatalf("mesh business not match") + case actual.GetDepartment().GetValue() != expect.GetDepartment().GetValue(): + t.Fatalf("mesh department not match") + case actual.GetOwners().GetValue() != expect.GetOwners().GetValue(): + t.Fatalf("mesh owners not match") + case actual.GetManaged().GetValue() != expect.GetManaged().GetValue(): + t.Fatalf("mesh managed not match") + case actual.GetIstioVersion().GetValue() != expect.GetIstioVersion().GetValue(): + t.Fatalf("mesh istio version not match") + case actual.GetComment().GetValue() != expect.GetComment().GetValue(): + t.Fatalf("mesh comment not match") + } +} + +func checkReqMeshComplete(t *testing.T, expect *api.Mesh, actual *api.Mesh) { + checkReqMesh(t, expect, actual) + switch { + case actual.GetId().GetValue() != expect.GetId().GetValue(): + t.Fatalf("mesh id not match") + case actual.GetRevision().GetValue() != expect.GetRevision().GetValue(): + t.Fatalf("mesh revision not match") + } +} + +// 比较两个网格规则是否一致 +func checkReqMeshResource(t *testing.T, expect *api.MeshResource, actual *api.MeshResource) { + switch { + case actual.GetName().GetValue() != expect.GetName().GetValue(): + t.Fatalf("mesh resource name not match") + case actual.GetTypeUrl().GetValue() != expect.GetTypeUrl().GetValue(): + t.Fatalf("mesh resource typeUrl not match") + case actual.GetMeshNamespace().GetValue() != expect.GetMeshNamespace().GetValue(): + t.Fatalf("mesh resource mesh namespace not match") + case actual.GetBody().GetValue() != expect.GetBody().GetValue(): + t.Fatalf("mesh resource body not match") + case actual.GetId().GetValue() == "": + t.Fatalf("mesh resource id empty") + default: + break + } +} + +// 比较从cache中获取的规则是否符合预期 +func checkCacheMeshResource(t *testing.T, expect *api.MeshResource, actual *api.MeshResource) { + switch { + case expect.GetName().GetValue() != actual.GetName().GetValue(): + t.Fatalf("mesh resource name not match") + case expect.GetTypeUrl().GetValue() != actual.GetTypeUrl().GetValue(): + t.Fatalf("mesh resource typeUrl not match") + case expect.GetRevision().GetValue() != actual.GetRevision().GetValue(): + t.Fatalf("mesh resource revision not match") + case expect.GetBody().GetValue() != actual.GetBody().GetValue(): + t.Fatalf("mesh resource body not match") + case expect.GetMeshId().GetValue() != actual.GetMeshId().GetValue(): + t.Fatalf("mesh id not match") + default: + break + } +} + +// 比较利用控制台接口获取的规则是否符合预期 +//func checkHttpMeshResource(t *testing.T, expect *api.MeshResource, actual *api.MeshResource) { +// switch { +// case actual.GetId().GetValue() != expect.GetId().GetValue(): +// t.Fatalf("mesh resource id not match") +// case actual.GetNamespace().GetValue() != expect.GetNamespace().GetValue(): +// t.Fatalf("mesh resource namespace not match") +// case actual.GetName().GetValue() != expect.GetName().GetValue(): +// t.Fatalf("mesh resource name not match") +// case actual.GetTypeUrl().GetValue() != expect.GetTypeUrl().GetValue(): +// t.Fatalf("mesh resource typeUrl not match") +// case actual.GetBusiness().GetValue() != expect.GetBusiness().GetValue(): +// t.Fatalf("mesh resource business not match") +// case actual.GetComment().GetValue() != expect.GetComment().GetValue(): +// t.Fatalf("mesh resource comment not match") +// case actual.GetDepartment().GetValue() != expect.GetDepartment().GetValue(): +// t.Fatalf("mesh resource department not match") +// case actual.GetBody().GetValue() != expect.GetBody().GetValue(): +// t.Fatalf("mesh resource body not match") +// case actual.GetOwners().GetValue() != expect.GetOwners().GetValue(): +// t.Fatalf("mesh resource owners not match") +// default: +// break +// } +//} + +// 获取一个更新规则请求 +//func updateMeshResource(baseResource *api.MeshResource) *api.MeshResource { +// return &api.MeshResource{ +// Namespace: utils.NewStringValue(baseResource.GetNamespace().GetValue()), +// Name: utils.NewStringValue(baseResource.GetName().GetValue()), +// Token: utils.NewStringValue(baseResource.GetToken().GetValue()), +// TypeUrl: utils.NewStringValue(baseResource.GetTypeUrl().GetValue()), +// Business: utils.NewStringValue(baseResource.GetBusiness().GetValue()), +// Id: utils.NewStringValue(baseResource.GetId().GetValue()), +// } +//} + +//// 清除网格规则 +//func cleanMeshResource(namespace, name string) { +// str := `delete from mesh_resource where name = ? and namespace = ?` +// if _, err := db.Exec(str, name, namespace); err != nil { +// panic(err) +// } +//} +// +//// 清除网格规则版本号 +//func cleanMeshResourceRevision(namespace, business, typeUrl string) { +// str := `delete from mesh_revision where namespace = ? and business = ? and type_url = ?` +// if _, err := db.Exec(str, namespace, business, typeUrl); err != nil { +// panic(err) +// } +//} + +func cleanMeshResourceByMeshID(meshID string) { + log.Infof("cleanMeshResourceByMeshID: %s", meshID) + str := `delete from mesh_resource where mesh_id = ?` + if _, err := db.Exec(str, meshID); err != nil { + panic(err) + } + str = `delete from mesh_resource_revision where mesh_id = ?` + if _, err := db.Exec(str, meshID); err != nil { + panic(err) + } +} + +// 清除网格 +func cleanMesh(id string) { + str := `delete from mesh where id = ?` + if _, err := db.Exec(str, id); err != nil { + panic(err) + } +} + +func cleanMeshService(meshID string) { + str := `delete from mesh_service where mesh_id = ?` + if _, err := db.Exec(str, meshID); err != nil { + panic(err) + } + str = `delete from mesh_service_revision where mesh_id = ?` + if _, err := db.Exec(str, meshID); err != nil { + panic(err) + } +} + +// 删除一个网格 +func deleteMesh(mesh *api.Mesh) *api.Response { + dMesh := &api.Mesh{ + Id: utils.NewStringValue(mesh.GetId().GetValue()), + Token: utils.NewStringValue(mesh.GetToken().GetValue()), + } + return server.DeleteMesh(defaultCtx, dMesh) +} + +// 更新一个网格 +func updateMesh(mesh *api.Mesh) *api.Response { + return server.UpdateMesh(defaultCtx, mesh) +} + +// 删除一个网格规则 +func deleteMeshResource(name, namespace, token string) *api.Response { + resource := &api.MeshResource{ + Name: utils.NewStringValue(name), + MeshToken: utils.NewStringValue(token), + } + return server.DeleteMeshResource(defaultCtx, resource) +} + +// 创建flux限流规则 +func createCommonFluxRateLimit(t *testing.T, service *api.Service, index int) (*api.FluxConsoleRateLimitRule, + *api.FluxConsoleRateLimitRule) { + rateLimit := &api.FluxConsoleRateLimitRule{ + Name: utils.NewStringValue(fmt.Sprintf("test-%d", index)), + Description: utils.NewStringValue("test"), + CalleeServiceName: service.GetName(), + CalleeServiceEnv: service.GetNamespace(), + CallerServiceBusiness: utils.NewStringValue(fmt.Sprintf("business-%d", index)), + SetKey: utils.NewStringValue(fmt.Sprintf("set-key-%d", index)), + SetAlertQps: utils.NewStringValue(fmt.Sprintf("%d", index*10)), + SetWarningQps: utils.NewStringValue(fmt.Sprintf("%d", index*8)), + SetRemark: utils.NewStringValue(fmt.Sprintf("set-remark-%d", index)), + DefaultKey: utils.NewStringValue(fmt.Sprintf("default-key-%d", index)), + DefaultAlertQps: utils.NewStringValue(fmt.Sprintf("%d", index*2)), + DefaultWarningQps: utils.NewStringValue(fmt.Sprintf("%d", index)), + DefaultRemark: utils.NewStringValue(fmt.Sprintf("default-remark-%d", index)), + Creator: utils.NewStringValue("test"), + Updater: utils.NewStringValue("test"), + Status: utils.NewUInt32Value(1), + Type: utils.NewUInt32Value(2), + ServiceToken: utils.NewStringValue(service.GetToken().GetValue()), + } + + resp := server.CreateFluxRateLimit(defaultCtx, rateLimit) + if !respSuccess(resp) { + t.Fatalf("error: %+v", resp) + } + return rateLimit, resp.GetFluxConsoleRateLimitRule() +} + +// 删除限流规则 +func deleteFluxRateLimit(t *testing.T, rateLimit *api.FluxConsoleRateLimitRule) { + if resp := server.DeleteFluxRateLimit(defaultCtx, rateLimit); !respSuccess(resp) { + t.Fatalf("%s", resp.GetInfo().GetValue()) + } +} + +// 更新单个限流规则 +func updateFluxRateLimit(t *testing.T, rateLimit *api.FluxConsoleRateLimitRule) { + if resp := server.UpdateFluxRateLimit(defaultCtx, rateLimit); !respSuccess(resp) { + t.Fatalf("%s", resp.GetInfo().GetValue()) + } +} + +// 彻底删除限流规则 +func cleanFluxRateLimit(id string) { + str := `delete from ratelimit_flux_rule_config where id = ?` + if _, err := db.Exec(str, id); err != nil { + panic(err) + } +} + +// 彻底删除限流规则版本号 +func cleanFluxRateLimitRevision(service, namespace string) { + str := `delete from ratelimit_flux_rule_revision using ratelimit_flux_rule_revision, service + where service_id = service.id and name = ? and namespace = ?` + if _, err := db.Exec(str, service, namespace); err != nil { + panic(err) + } +} + +// 更新限流规则内容 +func updateFluxRateLimitContent(rateLimit *api.FluxConsoleRateLimitRule, index int) { + rateLimit.SetAlertQps = utils.NewStringValue(fmt.Sprintf("%d", index*10)) + rateLimit.SetWarningQps = utils.NewStringValue(fmt.Sprintf("%d", index*5)) + rateLimit.SetKey = utils.NewStringValue(fmt.Sprintf("set-key-%d", index)) + rateLimit.SetRemark = utils.NewStringValue(fmt.Sprintf("remark-%d", index)) + rateLimit.DefaultAlertQps = utils.NewStringValue(fmt.Sprintf("%d", index*2)) + rateLimit.DefaultWarningQps = utils.NewStringValue(fmt.Sprintf("%d", index)) + rateLimit.DefaultKey = utils.NewStringValue(fmt.Sprintf("default-key-%d", index)) + rateLimit.DefaultRemark = utils.NewStringValue(fmt.Sprintf("default-remark-%d", index)) +} + +/* + * @brief 对比限流规则的各个属性 + */ +func checkFluxRateLimit(t *testing.T, expect *api.FluxConsoleRateLimitRule, actual *api.FluxConsoleRateLimitRule) { + switch { + case (expect.GetId().GetValue()) != "" && (expect.GetId().GetValue() != actual.GetId().GetValue()): + t.Fatal("invalid id") + case expect.GetName().GetValue() != actual.GetName().GetValue(): + t.Fatal("error name") + case expect.GetDescription().GetValue() != actual.GetDescription().GetValue(): + t.Fatal("error description") + case expect.GetStatus().GetValue() != actual.GetStatus().GetValue(): + t.Fatal("invalid status") + case expect.GetCalleeServiceName().GetValue() != actual.GetCalleeServiceName().GetValue(): + t.Fatal("invalid CalleeServiceName") + case expect.GetCalleeServiceEnv().GetValue() != actual.GetCalleeServiceEnv().GetValue(): + t.Fatal("invalid CalleeServiceEnv") + case expect.GetCallerServiceBusiness().GetValue() != actual.GetCallerServiceBusiness().GetValue(): + t.Fatal("invalid GetCallerServiceBusiness") + case expect.GetSetKey().GetValue() != actual.GetSetKey().GetValue(): + t.Fatal("error set key") + case expect.GetSetAlertQps().GetValue() != actual.GetSetAlertQps().GetValue(): + t.Fatal("error set alert qps") + case expect.GetSetWarningQps().GetValue() != actual.GetSetWarningQps().GetValue(): + t.Fatal("error set warning qps") + case expect.GetSetRemark().GetValue() != actual.GetSetRemark().GetValue(): + t.Fatal("error set remark") + case expect.GetDefaultKey().GetValue() != actual.GetDefaultKey().GetValue(): + t.Fatal("error default key") + case expect.GetDefaultAlertQps().GetValue() != actual.GetDefaultAlertQps().GetValue(): + t.Fatal("error default alert qps") + case expect.GetDefaultWarningQps().GetValue() != actual.GetDefaultWarningQps().GetValue(): + t.Fatal("error default warning qps") + case expect.GetDefaultRemark().GetValue() != actual.GetDefaultRemark().GetValue(): + t.Fatal("error default remark") + case expect.GetType().GetValue() != actual.GetType().GetValue(): + t.Fatal("error type") + case expect.GetStatus().GetValue() != actual.GetStatus().GetValue(): + t.Fatal("error status") + default: + break + } + t.Log("check success") +} + +// 时间转化为可读字符串 +func time2String(t time.Time) string { + return t.Format("2006-01-02 15:04:05") +} + +// 获取指定长度str +func genSpecialStr(n int) string { + str := "" + for i := 0; i < n; i++ { + str += "a" + } + return str +} + +// 解析字符串sid为modID和cmdID +func parseStr2Sid(sid string) (uint32, uint32) { + items := strings.Split(sid, ":") + if len(items) != 2 { + return 0, 0 + } + + mod, _ := strconv.ParseUint(items[0], 10, 32) + cmd, _ := strconv.ParseUint(items[1], 10, 32) + return uint32(mod), uint32(cmd) +} + +// 初始化函数 +func init() { + if err := initialize(); err != nil { + fmt.Printf("init err: %s", err.Error()) + panic(err) + } +} diff --git a/naming/test/namespace_test.go b/naming/test/namespace_test.go new file mode 100644 index 000000000..49cd4032c --- /dev/null +++ b/naming/test/namespace_test.go @@ -0,0 +1,237 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package test + +import ( + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/utils" + "testing" + "time" +) + +// create +func createCommonNamespace(t *testing.T, id int) (*api.Namespace, *api.Namespace) { + req := &api.Namespace{ + Name: utils.NewStringValue(fmt.Sprintf("namespace-%d", id)), + Comment: utils.NewStringValue(fmt.Sprintf("comment-%d", id)), + Owners: utils.NewStringValue(fmt.Sprintf("owner-%d", id)), + } + cleanNamespace(req.GetName().GetValue()) + + resp := server.CreateNamespace(defaultCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + + if resp.GetNamespace().GetToken().GetValue() == "" || + req.GetName().GetValue() != resp.GetNamespace().GetName().GetValue() { + t.Fatalf("erros: %+v", resp) + } + + return req, resp.GetNamespace() +} + +// remove +func removeCommonNamespaces(t *testing.T, req []*api.Namespace) { + resp := server.DeleteNamespaces(defaultCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } +} + +// update +func updateCommonNamespaces(t *testing.T, req []*api.Namespace) { + resp := server.UpdateNamespaces(defaultCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } +} + +// 测试新建命名空间 +func TestCreateNamespace(t *testing.T) { + t.Run("正常创建命名空间", func(t *testing.T) { + _, resp := createCommonNamespace(t, 100) + defer cleanNamespace(resp.GetName().GetValue()) + t.Logf("pass") + }) + + t.Run("新建命名空间,删除,再创建一个同样的,可以成功", func(t *testing.T) { + req, resp := createCommonNamespace(t, 10) + defer cleanNamespace(req.GetName().GetValue()) + + // remove + removeCommonNamespaces(t, []*api.Namespace{resp}) + apiResp := server.CreateNamespace(defaultCtx, req) + if !respSuccess(apiResp) { + t.Fatalf("error: %s", apiResp.GetInfo().GetValue()) + } + + t.Logf("pass") + }) + + t.Run("新建命名空间和服务,删除命名空间和服务,再创建命名空间", func(t *testing.T) { + _, namespaceResp := createCommonNamespace(t, 10) + defer cleanNamespace(namespaceResp.GetName().GetValue()) + + _, serviceResp := createCommonService(t, 100) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + removeCommonServices(t, []*api.Service{serviceResp}) + removeCommonNamespaces(t, []*api.Namespace{namespaceResp}) + + _, namespaceResp = createCommonNamespace(t, 10) + defer cleanNamespace(namespaceResp.GetName().GetValue()) + }) +} + +// 删除命名空间 +func TestRemoveNamespace(t *testing.T) { + t.Run("可以删除命名空间", func(t *testing.T) { + _, resp := createCommonNamespace(t, 99) + defer cleanNamespace(resp.GetName().GetValue()) + + removeCommonNamespaces(t, []*api.Namespace{resp}) + out := server.GetNamespaces(map[string][]string{"name": {resp.GetName().GetValue()}}) + if !respSuccess(out) { + t.Fatalf("error: %s", out.GetInfo().GetValue()) + } + if len(out.GetNamespaces()) != 0 { + t.Fatalf("error: %d", len(out.GetNamespaces())) + } + }) + + t.Run("批量删除命名空间", func(t *testing.T) { + var reqs []*api.Namespace + for i := 0; i < 20; i++ { + _, resp := createCommonNamespace(t, i) + defer cleanNamespace(resp.GetName().GetValue()) + reqs = append(reqs, resp) + } + + time.Sleep(updateCacheInterval) + removeCommonNamespaces(t, reqs) + t.Logf("pass") + }) + + t.Run("新建命名空间和服务,直接删除名空间,因为有服务,删除会失败", func(t *testing.T) { + _, namespaceResp := createCommonNamespace(t, 100) + defer cleanNamespace(namespaceResp.GetName().GetValue()) + + serviceReq := &api.Service{ + Name: utils.NewStringValue("abc"), + Namespace: namespaceResp.GetName(), + Owners: utils.NewStringValue("123"), + } + if resp := server.CreateService(defaultCtx, serviceReq); !respSuccess(resp) { + t.Fatalf("errror: %s", resp.GetInfo().GetValue()) + } + defer cleanServiceName(serviceReq.GetName().GetValue(), serviceReq.GetNamespace().GetValue()) + + resp := server.DeleteNamespace(defaultCtx, namespaceResp) + if resp.GetCode().GetValue() != api.NamespaceExistedServices { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Logf("%s", resp.GetInfo().GetValue()) + }) +} + +// 更新命名空间 +func TestUpdateNamespace(t *testing.T) { + t.Run("正常更新命名空间", func(t *testing.T) { + req, resp := createCommonNamespace(t, 200) + defer cleanNamespace(resp.GetName().GetValue()) + + time.Sleep(updateCacheInterval) + + req.Token = resp.Token + req.Comment = utils.NewStringValue("new-comment") + + updateCommonNamespaces(t, []*api.Namespace{req}) + t.Logf("pass") + }) +} + +// 获取命名空间列表 +func TestGetNamespaces(t *testing.T) { + t.Run("正常获取命名空间,可以正常获取", func(t *testing.T) { + total := 50 + for i := 0; i < total; i++ { + req, _ := createCommonNamespace(t, i+200) + defer cleanNamespace(req.GetName().GetValue()) + } + + resp := server.GetNamespaces(map[string][]string{}) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if resp.GetSize().GetValue() == uint32(total) { + t.Fatalf("error: %d", resp.GetSize().GetValue()) + } + }) + + t.Run("分页参数可以正常过滤", func(t *testing.T) { + total := 20 + for i := 0; i < total; i++ { + req, _ := createCommonNamespace(t, i+200) + defer cleanNamespace(req.GetName().GetValue()) + } + + query := map[string][]string{ + "offset": {"10"}, + "limit": {"10"}, + } + resp := server.GetNamespaces(query) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if resp.GetSize().GetValue() != 10 { + t.Fatalf("error: %d", resp.GetSize().GetValue()) + } + }) +} + +// 测试命名空间的token +func TestNamespaceToken(t *testing.T) { + t.Run("可以正常获取到namespaceToken", func(t *testing.T) { + _, namespaceResp := createCommonNamespace(t, 1) + defer cleanNamespace(namespaceResp.GetName().GetValue()) + + resp := server.GetNamespaceToken(defaultCtx, namespaceResp) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if resp.GetNamespace().GetToken().GetValue() != namespaceResp.GetToken().GetValue() { + t.Fatalf("error") + } + }) + t.Run("可以正常更新namespace的token", func(t *testing.T) { + _, namespaceResp := createCommonNamespace(t, 2) + defer cleanNamespace(namespaceResp.GetName().GetValue()) + + resp := server.UpdateNamespaceToken(defaultCtx, namespaceResp) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if resp.GetNamespace().GetToken().GetValue() == namespaceResp.GetToken().GetValue() { + t.Fatalf("error") + } + t.Logf("%s %s", resp.GetNamespace().GetToken().GetValue(), + namespaceResp.GetToken().GetValue()) + }) +} diff --git a/naming/test/platform_test.go b/naming/test/platform_test.go new file mode 100644 index 000000000..e5ccd010d --- /dev/null +++ b/naming/test/platform_test.go @@ -0,0 +1,457 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package test + +import ( + "context" + "errors" + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/utils" + "sync" + "testing" +) + +/** + * @brief 创建平台 + */ +func createCommonPlatform(t *testing.T, id int) (*api.Platform, *api.Platform) { + req := &api.Platform{ + Id: utils.NewStringValue(fmt.Sprintf("id-%d", id)), + Name: utils.NewStringValue("name"), + Domain: utils.NewStringValue(fmt.Sprintf("domain-%d", id)), + Qps: utils.NewUInt32Value(1), + Owner: utils.NewStringValue(fmt.Sprintf("owner-%d", id)), + Department: utils.NewStringValue(fmt.Sprintf("department-%d", id)), + Comment: utils.NewStringValue(fmt.Sprintf("comment-%d", id)), + } + cleanPlatform(req.GetId().GetValue()) + + resp := server.CreatePlatform(defaultCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if resp.GetPlatform().GetToken().GetValue() == "" { + t.Fatalf("error: %+v", resp) + } + if _, err := comparePlatform(req, resp.GetPlatform()); err != nil { + t.Fatalf("error: %s", err.Error()) + } + + return req, resp.GetPlatform() +} + +/** + * @brief 删除平台 + */ +func removeCommonPlatform(t *testing.T, req *api.Platform) { + resp := server.DeletePlatform(defaultCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } +} + +/** + * @brief 修改平台 + */ +func updateCommonPlatform(t *testing.T, req *api.Platform) { + resp := server.UpdatePlatform(defaultCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } +} + +/** + * @brief 修改平台内容 + */ +func updatePlatformContent(req *api.Platform) { + req.Name = utils.NewStringValue("update-name") + req.Domain = utils.NewStringValue("update-domain") + req.Qps = utils.NewUInt32Value(req.GetQps().GetValue() + 1) + req.Owner = utils.NewStringValue("update-owner") + req.Department = utils.NewStringValue("update-department") + req.Comment = utils.NewStringValue("update-comment") +} + +/** + * @brief 从数据库彻底删除平台 + */ +func cleanPlatform(id string) { + if id == "" { + panic("id is empty") + } + + log.Infof("clean platform: %s", id) + str := `delete from platform where id = ?` + if _, err := db.Exec(str, id); err != nil { + panic(err) + } +} + +func comparePlatform(correctItem *api.Platform, item *api.Platform) (bool, error) { + switch { + case correctItem.GetId().GetValue() != item.GetId().GetValue(): + return false, errors.New("error id") + case correctItem.GetName().GetValue() != item.GetName().GetValue(): + return false, errors.New("error name") + case correctItem.GetDomain().GetValue() != item.GetDomain().GetValue(): + return false, errors.New("error domain") + case correctItem.GetQps().GetValue() != item.GetQps().GetValue(): + return false, errors.New("error qps") + case correctItem.GetOwner().GetValue() != item.GetOwner().GetValue(): + return false, errors.New("error owner") + case correctItem.GetDepartment().GetValue() != item.GetDepartment().GetValue(): + return false, errors.New("error department") + case correctItem.GetComment().GetValue() != item.GetComment().GetValue(): + return false, errors.New("error comment") + } + return true, nil +} + +/** + * @brief 测试新建平台 + */ +func TestCreatePlatform(t *testing.T) { + t.Run("正常创建平台,返回成功", func(t *testing.T) { + _, resp := createCommonPlatform(t, 1) + defer cleanPlatform(resp.GetId().GetValue()) + t.Log("pass") + }) + + t.Run("创建平台,删除后,再创建同名平台,返回成功", func(t *testing.T) { + req, resp := createCommonPlatform(t, 1) + defer cleanPlatform(resp.GetId().GetValue()) + + // 删除平台 + removeCommonPlatform(t, resp) + apiResp := server.CreatePlatform(defaultCtx, req) + if !respSuccess(apiResp) { + t.Fatalf("error: %s", apiResp.GetInfo().GetValue()) + } else { + t.Log("pass") + } + }) + + t.Run("重复创建平台,返回失败", func(t *testing.T) { + req, _ := createCommonPlatform(t, 1) + defer cleanPlatform(req.GetId().GetValue()) + + resp := server.CreatePlatform(defaultCtx, req) + if !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("创建平台时没有传递id,返回失败", func(t *testing.T) { + req := &api.Platform{} + + resp := server.CreatePlatform(defaultCtx, req) + if !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("创建平台时没有传递负责人, 返回失败", func(t *testing.T) { + req := &api.Platform{ + Id: utils.NewStringValue(fmt.Sprintf("id-%d", 1)), + } + + resp := server.CreatePlatform(defaultCtx, req) + if !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("并发创建平台,返回成功", func(t *testing.T) { + var wg sync.WaitGroup + for i := 1; i <= 500; i++ { + wg.Add(1) + go func(index int) { + defer wg.Done() + _, resp := createCommonPlatform(t, index) + defer cleanPlatform(resp.GetId().GetValue()) + }(i) + } + wg.Wait() + t.Log("pass") + }) +} + +/** + * @brief 测试删除平台 + */ +func TestDeletePlatform(t *testing.T) { + getPlatforms := func(t *testing.T, id string, expectNum uint32) { + filter := map[string]string{ + "id": id, + } + resp := server.GetPlatforms(filter) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if resp.GetAmount().GetValue() != expectNum { + t.Fatalf("error, actual num is %d, expect num is %d", resp.GetAmount().GetValue(), expectNum) + } + } + + t.Run("删除存在的平台,返回成功", func(t *testing.T) { + _, resp := createCommonPlatform(t, 1) + defer cleanPlatform(resp.GetId().GetValue()) + removeCommonPlatform(t, resp) + getPlatforms(t, resp.GetId().GetValue(), 0) + t.Log("pass") + }) + + t.Run("删除不存在的平台,返回成功", func(t *testing.T) { + _, resp := createCommonPlatform(t, 1) + defer cleanPlatform(resp.GetId().GetValue()) + + removeCommonPlatform(t, resp) + + apiResp := server.DeletePlatform(defaultCtx, resp) + if respSuccess(apiResp) { + t.Log("pass") + } else { + t.Fatalf("error: %s", apiResp.GetInfo().GetValue()) + } + + getPlatforms(t, resp.GetId().GetValue(), 0) + }) + + t.Run("使用系统token删除,返回成功", func(t *testing.T) { + req, _ := createCommonPlatform(t, 1) + defer cleanPlatform(req.GetId().GetValue()) + + ctx := context.WithValue(defaultCtx, utils.StringContext("polaris-token"), "polaris@12345678") + + apiReq := &api.Platform{ + Id: req.GetId(), + } + + resp := server.DeletePlatform(ctx, apiReq) + if respSuccess(resp) { + t.Log("pass") + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + + getPlatforms(t, req.GetId().GetValue(), 0) + }) + + t.Run("删除平台时没有传递平台id,返回失败", func(t *testing.T) { + req := &api.Platform{} + + resp := server.DeletePlatform(defaultCtx, req) + if !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error") + } + }) + + t.Run("删除平台时没有传递平台Token,返回失败", func(t *testing.T) { + req, _ := createCommonPlatform(t, 1) + defer cleanPlatform(req.GetId().GetValue()) + + apiReq := &api.Platform{ + Id: req.GetId(), + } + + resp := server.DeletePlatform(defaultCtx, apiReq) + if !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("并发删除平台,返回成功", func(t *testing.T) { + var wg sync.WaitGroup + for i := 1; i <= 500; i++ { + wg.Add(1) + go func(index int) { + defer wg.Done() + _, resp := createCommonPlatform(t, index) + defer cleanPlatform(resp.GetId().GetValue()) + removeCommonPlatform(t, resp) + }(i) + } + wg.Wait() + t.Log("pass") + }) +} + +/** + * @brief 测试更新平台 + */ +func TestUpdatePlatform(t *testing.T) { + t.Run("更新平台,返回成功", func(t *testing.T) { + req, _ := createCommonPlatform(t, 1) + defer cleanPlatform(req.GetId().GetValue()) + updatePlatformContent(req) + updateCommonPlatform(t, req) + filter := map[string]string{ + "id": req.GetId().GetValue(), + } + resp := server.GetPlatforms(filter) + if !respSuccess(resp) { + t.Fatal("error") + } + comparePlatform(req, resp.GetPlatforms()[0]) + }) + + t.Run("更新不存在的平台,返回失败", func(t *testing.T) { + req, _ := createCommonPlatform(t, 1) + cleanPlatform(req.GetId().GetValue()) + resp := server.UpdatePlatform(defaultCtx, req) + if !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("更新平台时没有传递token,返回错误", func(t *testing.T) { + req, _ := createCommonPlatform(t, 1) + defer cleanPlatform(req.GetId().GetValue()) + req.Token = utils.NewStringValue("") + + resp := server.UpdatePlatform(defaultCtx, req) + if !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("并发更新平台,返回成功", func(t *testing.T) { + var wg sync.WaitGroup + for i := 1; i <= 500; i++ { + wg.Add(1) + go func(index int) { + defer wg.Done() + req, resp := createCommonPlatform(t, index) + defer cleanPlatform(resp.GetId().GetValue()) + updatePlatformContent(req) + updateCommonPlatform(t, req) + }(i) + } + wg.Wait() + t.Log("pass") + }) +} + +/** + * @brief 测试查询平台 + */ +func TestGetPlatform(t *testing.T) { + platformNum := 10 + platformName := "name" + for i := 1; i <= platformNum; i++ { + req, _ := createCommonPlatform(t, i) + defer cleanPlatform(req.GetId().GetValue()) + } + + t.Run("查询平台,过滤条件为id", func(t *testing.T) { + filter := map[string]string{ + "id": fmt.Sprintf("id-%d", platformNum), + } + resp := server.GetPlatforms(filter) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if resp.GetSize().GetValue() != 1 { + t.Fatalf("expect num is 1, actual num is %d", resp.GetSize().GetValue()) + } + t.Log("pass") + }) + + t.Run("查询平台,过滤条件为name", func(t *testing.T) { + filter := map[string]string{ + "name": platformName, + } + resp := server.GetPlatforms(filter) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if resp.GetSize().GetValue() != uint32(platformNum) { + t.Fatalf("expect num is %d, actual num is %d", platformNum, resp.GetSize().GetValue()) + } + t.Log("pass") + }) + + t.Run("查询平台,过滤条件为name和owner", func(t *testing.T) { + filter := map[string]string{ + "name": platformName, + "owner": fmt.Sprintf("owner-%d", platformNum), + } + resp := server.GetPlatforms(filter) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if resp.GetSize().GetValue() != 1 { + t.Fatalf("expect num is 1, actual num is %d", resp.GetSize().GetValue()) + } + t.Log("pass") + }) + + t.Run("查询平台,过滤条件为不存在的name", func(t *testing.T) { + filter := map[string]string{ + "name": "not exist", + } + resp := server.GetPlatforms(filter) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if resp.GetSize().GetValue() != 0 { + t.Fatalf("expect num is 0, actual num is %d", resp.GetSize().GetValue()) + } + t.Log("pass") + }) + + t.Run("查询平台,过滤条件为domain,返回失败", func(t *testing.T) { + filter := map[string]string{ + "domain": "test", + } + resp := server.GetPlatforms(filter) + if !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("查询平台,offset为负数,返回失败", func(t *testing.T) { + filter := map[string]string{ + "offset": "-3", + } + resp := server.GetPlatforms(filter) + if !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error") + } + }) +} diff --git a/naming/test/ratelimit_config_test.go b/naming/test/ratelimit_config_test.go new file mode 100644 index 000000000..88458c6e8 --- /dev/null +++ b/naming/test/ratelimit_config_test.go @@ -0,0 +1,537 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package test + +import ( + "context" + "encoding/json" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/utils" + "github.com/golang/protobuf/ptypes/duration" + "sync" + "testing" + "time" +) + +/** + * @brief 测试创建限流规则 + */ +func TestCreateRateLimit(t *testing.T) { + _, serviceResp := createCommonService(t, 0) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + defer cleanRateLimitRevision(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + t.Run("正常创建限流规则", func(t *testing.T) { + _ = server.Cache().Clear() + rateLimitReq, rateLimitResp := createCommonRateLimit(t, serviceResp, 3) + defer cleanRateLimit(rateLimitResp.GetId().GetValue()) + + // 等待缓存更新 + time.Sleep(updateCacheInterval) + resp := server.GetRateLimitWithCache(context.Background(), serviceResp) + checkRateLimit(t, rateLimitReq, resp.GetRateLimit().GetRules()[0]) + }) + + t.Run("创建限流规则,删除,再创建,可以正常创建", func(t *testing.T) { + _ = server.Cache().Clear() + rateLimitReq, rateLimitResp := createCommonRateLimit(t, serviceResp, 3) + defer cleanRateLimit(rateLimitResp.GetId().GetValue()) + deleteRateLimit(t, rateLimitResp) + if resp := server.CreateRateLimit(defaultCtx, rateLimitReq); !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + + // 等待缓存更新 + time.Sleep(updateCacheInterval) + resp := server.GetRateLimitWithCache(context.Background(), serviceResp) + checkRateLimit(t, rateLimitReq, resp.GetRateLimit().GetRules()[0]) + cleanRateLimit(rateLimitResp.GetId().GetValue()) + }) + + t.Run("重复创建限流规则,返回成功", func(t *testing.T) { + rateLimitReq, rateLimitResp := createCommonRateLimit(t, serviceResp, 3) + defer cleanRateLimit(rateLimitResp.GetId().GetValue()) + if resp := server.CreateRateLimit(defaultCtx, rateLimitReq); !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } else { + t.Log("pass") + } + cleanRateLimit(rateLimitResp.GetId().GetValue()) + }) + + t.Run("创建限流规则时,没有传递token,返回失败", func(t *testing.T) { + rateLimit := &api.Rule{ + Service: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + Labels: map[string]*api.MatchString{}, + } + if resp := server.CreateRateLimit(defaultCtx, rateLimit); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("创建限流规则时,没有传递labels,返回失败", func(t *testing.T) { + rateLimit := &api.Rule{ + Service: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + ServiceToken: serviceResp.GetToken(), + } + if resp := server.CreateRateLimit(defaultCtx, rateLimit); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error") + } + }) + + t.Run("创建限流规则时,amounts具有相同的duration,返回失败", func(t *testing.T) { + rateLimit := &api.Rule{ + Service: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + Labels: map[string]*api.MatchString{}, + Amounts: []*api.Amount{ + { + MaxAmount: utils.NewUInt32Value(1), + ValidDuration: &duration.Duration{ + Seconds: 10, + Nanos: 10, + }, + }, + { + MaxAmount: utils.NewUInt32Value(2), + ValidDuration: &duration.Duration{ + Seconds: 10, + Nanos: 10, + }, + }, + }, + ServiceToken: serviceResp.GetToken(), + } + if resp := server.CreateRateLimit(defaultCtx, rateLimit); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error") + } + }) + + t.Run("并发创建同一服务的限流规则,可以正常创建", func(t *testing.T) { + var wg sync.WaitGroup + for i := 1; i <= 500; i++ { + wg.Add(1) + go func(index int) { + defer wg.Done() + _, rateLimitResp := createCommonRateLimit(t, serviceResp, index) + defer cleanRateLimit(rateLimitResp.GetId().GetValue()) + }(i) + } + wg.Wait() + t.Log("pass") + }) + + t.Run("并发创建不同服务的限流规则,可以正常创建", func(t *testing.T) { + var wg sync.WaitGroup + for i := 1; i <= 500; i++ { + wg.Add(1) + go func(index int) { + defer wg.Done() + _, serviceResp := createCommonService(t, index) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + defer cleanRateLimitRevision(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + _, rateLimitResp := createCommonRateLimit(t, serviceResp, 3) + defer cleanRateLimit(rateLimitResp.GetId().GetValue()) + }(i) + } + wg.Wait() + t.Log("pass") + }) + + t.Run("为不存在的服务创建限流规则,返回失败", func(t *testing.T) { + _, serviceResp := createCommonService(t, 2) + cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + defer cleanRateLimitRevision(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + rateLimit := &api.Rule{ + Service: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + Labels: map[string]*api.MatchString{}, + ServiceToken: serviceResp.GetToken(), + } + if resp := server.CreateRateLimit(defaultCtx, rateLimit); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) +} + +/** + * @brief 测试删除限流规则 + */ +func TestDeleteRateLimit(t *testing.T) { + _, serviceResp := createCommonService(t, 0) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + defer cleanRateLimitRevision(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + getRateLimits := func(t *testing.T, service *api.Service, expectNum uint32) []*api.Rule { + filters := map[string]string{ + "service": service.GetName().GetValue(), + "namespace": service.GetNamespace().GetValue(), + } + resp := server.GetRateLimits(filters) + if !respSuccess(resp) { + t.Fatalf("error") + } + if resp.GetAmount().GetValue() != expectNum { + t.Fatalf("error") + } + return resp.GetRateLimits() + } + + t.Run("删除存在的限流规则,可以正常删除", func(t *testing.T) { + _, rateLimitResp := createCommonRateLimit(t, serviceResp, 3) + defer cleanRateLimit(rateLimitResp.GetId().GetValue()) + deleteRateLimit(t, rateLimitResp) + getRateLimits(t, serviceResp, 0) + t.Log("pass") + }) + + t.Run("删除不存在的限流规则,返回正常", func(t *testing.T) { + _, rateLimitResp := createCommonRateLimit(t, serviceResp, 3) + cleanRateLimit(rateLimitResp.GetId().GetValue()) + deleteRateLimit(t, rateLimitResp) + getRateLimits(t, serviceResp, 0) + t.Log("pass") + }) + + t.Run("删除限流规则时,没有传递token,返回失败", func(t *testing.T) { + rateLimitReq, rateLimitResp := createCommonRateLimit(t, serviceResp, 3) + defer cleanRateLimit(rateLimitResp.GetId().GetValue()) + rateLimitReq.ServiceToken = utils.NewStringValue("") + if resp := server.DeleteRateLimit(defaultCtx, rateLimitReq); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatal("error") + } + }) + + t.Run("并发删除限流规则,可以正常删除", func(t *testing.T) { + var wg sync.WaitGroup + for i := 1; i <= 500; i++ { + wg.Add(1) + go func(index int) { + defer wg.Done() + _, serviceResp := createCommonService(t, index) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + defer cleanRateLimitRevision(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + rateLimitReq, rateLimitResp := createCommonRateLimit(t, serviceResp, 3) + defer cleanRateLimit(rateLimitResp.GetId().GetValue()) + deleteRateLimit(t, rateLimitReq) + }(i) + } + wg.Wait() + t.Log("pass") + }) +} + +/** + * @brief 测试更新限流规则 + */ +func TestUpdateRateLimit(t *testing.T) { + _, serviceResp := createCommonService(t, 0) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + defer cleanRateLimitRevision(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + _, rateLimitResp := createCommonRateLimit(t, serviceResp, 1) + defer cleanRateLimit(rateLimitResp.GetId().GetValue()) + + updateRateLimitContent(rateLimitResp, 2) + + t.Run("更新单个限流规则,可以正常更新", func(t *testing.T) { + updateRateLimit(t, rateLimitResp) + filters := map[string]string{ + "service": serviceResp.GetName().GetValue(), + "namespace": serviceResp.GetNamespace().GetValue(), + } + resp := server.GetRateLimits(filters) + if !respSuccess(resp) { + t.Fatalf("error") + } + checkRateLimit(t, rateLimitResp, resp.GetRateLimits()[0]) + }) + + t.Run("更新一个不存在的限流规则", func(t *testing.T) { + cleanRateLimit(rateLimitResp.GetId().GetValue()) + if resp := server.UpdateRateLimit(defaultCtx, rateLimitResp); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error") + } + }) + + t.Run("更新限流规则时,没有传递token, 返回错误", func(t *testing.T) { + rateLimitResp.ServiceToken = utils.NewStringValue("") + if resp := server.UpdateRateLimit(defaultCtx, rateLimitResp); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error") + } + }) + + t.Run("并发更新限流规则时,可以正常更新", func(t *testing.T) { + var wg sync.WaitGroup + for i := 1; i <= 500; i++ { + wg.Add(1) + go func(index int) { + defer wg.Done() + _, serviceResp := createCommonService(t, index) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + defer cleanRateLimitRevision(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + _, rateLimitResp := createCommonRateLimit(t, serviceResp, i) + defer cleanRateLimit(rateLimitResp.GetId().GetValue()) + updateRateLimitContent(rateLimitResp, i+1) + updateRateLimit(t, rateLimitResp) + filters := map[string]string{ + "service": serviceResp.GetName().GetValue(), + "namespace": serviceResp.GetNamespace().GetValue(), + } + resp := server.GetRateLimits(filters) + if !respSuccess(resp) { + t.Fatalf("error") + } + checkRateLimit(t, rateLimitResp, resp.GetRateLimits()[0]) + }(i) + } + wg.Wait() + t.Log("pass") + }) +} + +/* + * @brief 测试查询限流规则 + */ +func TestGetRateLimit(t *testing.T) { + serviceNum := 10 + rateLimitsNum := 30 + rateLimits := make([]*api.Rule, rateLimitsNum) + serviceName := "" + namespaceName := "" + labels := make(map[string]*api.MatchString) + labelsKey := "name-0" + for i := 0; i < serviceNum; i++ { + _, serviceResp := createCommonService(t, i) + if i == 5 { + serviceName = serviceResp.GetName().GetValue() + namespaceName = serviceResp.GetNamespace().GetValue() + } + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + defer cleanRateLimitRevision(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + for j := 0; j < rateLimitsNum/serviceNum; j++ { + _, rateLimitResp := createCommonRateLimit(t, serviceResp, j) + if j == 0 { + labels = rateLimitResp.GetLabels() + } + defer cleanRateLimit(rateLimitResp.GetId().GetValue()) + rateLimits = append(rateLimits, rateLimitResp) + } + } + + labelsValue := labels[labelsKey] + + t.Run("查询限流规则,过滤条件为service", func(t *testing.T) { + filters := map[string]string{ + "service": serviceName, + } + resp := server.GetRateLimits(filters) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if resp.GetSize().GetValue() != uint32(rateLimitsNum/serviceNum) { + t.Fatalf("expect num is %d, actual num is %d", rateLimitsNum/serviceNum, resp.GetSize().GetValue()) + } + t.Logf("pass: num is %d", resp.GetSize().GetValue()) + }) + + t.Run("查询限流规则,过滤条件为namespace", func(t *testing.T) { + filters := map[string]string{ + "namespace": namespaceName, + } + resp := server.GetRateLimits(filters) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if resp.GetSize().GetValue() != uint32(rateLimitsNum) { + t.Fatalf("expect num is %d, actual num is %d", serviceNum, resp.GetSize().GetValue()) + } + t.Logf("pass: num is %d", resp.GetSize().GetValue()) + }) + + t.Run("查询限流规则,过滤条件为不存在的namespace", func(t *testing.T) { + filters := map[string]string{ + "namespace": "Development", + } + resp := server.GetRateLimits(filters) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if resp.GetSize().GetValue() != 0 { + t.Fatalf("expect num is 0, actual num is %d", resp.GetSize().GetValue()) + } + t.Logf("pass: num is %d", resp.GetSize().GetValue()) + }) + + t.Run("查询限流规则,过滤条件为namespace和service", func(t *testing.T) { + filters := map[string]string{ + "service": serviceName, + "namespace": namespaceName, + } + resp := server.GetRateLimits(filters) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if resp.GetSize().GetValue() != uint32(rateLimitsNum/serviceNum) { + t.Fatalf("expect num is %d, actual num is %d", rateLimitsNum/serviceNum, resp.GetSize().GetValue()) + } + t.Logf("pass: num is %d", resp.GetSize().GetValue()) + }) + + t.Run("查询限流规则,过滤条件为offset和limit", func(t *testing.T) { + filters := map[string]string{ + "offset": "1", + "limit": "5", + } + resp := server.GetRateLimits(filters) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if resp.GetSize().GetValue() != 5 { + t.Fatalf("expect num is 5, actual num is %d", resp.GetSize().GetValue()) + } + t.Logf("pass: num is %d", resp.GetSize().GetValue()) + }) + + t.Run("查询限流规则,过滤条件为labels中的key", func(t *testing.T) { + filters := map[string]string{ + "labels": labelsKey, + } + resp := server.GetRateLimits(filters) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if resp.GetSize().GetValue() != uint32(serviceNum) { + t.Fatalf("expect num is %d, actual num is %d", serviceNum, resp.GetSize().GetValue()) + } + }) + + t.Run("查询限流规则,过滤条件为labels中的value", func(t *testing.T) { + filters := map[string]string{ + "labels": labelsValue.GetValue().GetValue(), + } + resp := server.GetRateLimits(filters) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if resp.GetSize().GetValue() != uint32(serviceNum) { + t.Fatalf("expect num is %d, actual num is %d", serviceNum, resp.GetSize().GetValue()) + } + }) + + t.Run("查询限流规则,过滤条件为labels中的key和value", func(t *testing.T) { + labelsString, err := json.Marshal(labels) + if err != nil { + panic(err) + } + filters := map[string]string{ + "labels": string(labelsString), + } + resp := server.GetRateLimits(filters) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if resp.GetSize().GetValue() != uint32(serviceNum) { + t.Fatalf("expect num is %d, actual num is %d", serviceNum, resp.GetSize().GetValue()) + } + }) + + t.Run("查询限流规则,offset为负数,返回错误", func(t *testing.T) { + filters := map[string]string{ + "service": serviceName, + "namespace": namespaceName, + "offset": "-5", + } + resp := server.GetRateLimits(filters) + if !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error") + } + }) + + t.Run("查询限流规则,limit为负数,返回错误", func(t *testing.T) { + filters := map[string]string{ + "service": serviceName, + "namespace": namespaceName, + "limit": "-5", + } + resp := server.GetRateLimits(filters) + if !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error") + } + }) +} + +// test对ratelimit字段进行校验 +func TestCheckRatelimitFieldLen(t *testing.T) { + rateLimit := &api.Rule{ + Service: utils.NewStringValue("test"), + Namespace: utils.NewStringValue("default"), + Labels: map[string]*api.MatchString{}, + ServiceToken: utils.NewStringValue("test"), + } + t.Run("创建限流规则,服务名超长", func(t *testing.T) { + str := genSpecialStr(129) + oldName := rateLimit.Service + rateLimit.Service = utils.NewStringValue(str) + resp := server.CreateRateLimit(defaultCtx, rateLimit) + rateLimit.Service = oldName + if resp.Code.Value != api.InvalidServiceName { + t.Fatalf("%+v", resp) + } + }) + t.Run("创建限流规则,命名空间超长", func(t *testing.T) { + str := genSpecialStr(129) + oldNamespace := rateLimit.Namespace + rateLimit.Namespace = utils.NewStringValue(str) + resp := server.CreateRateLimit(defaultCtx, rateLimit) + rateLimit.Namespace = oldNamespace + if resp.Code.Value != api.InvalidNamespaceName { + t.Fatalf("%+v", resp) + } + }) + t.Run("创建限流规则,toeken超长", func(t *testing.T) { + str := genSpecialStr(2049) + oldServiceToken := rateLimit.ServiceToken + rateLimit.ServiceToken = utils.NewStringValue(str) + resp := server.CreateRateLimit(defaultCtx, rateLimit) + rateLimit.ServiceToken = oldServiceToken + if resp.Code.Value != api.InvalidServiceToken { + t.Fatalf("%+v", resp) + } + }) +} diff --git a/naming/test/routing_config_test.go b/naming/test/routing_config_test.go new file mode 100644 index 000000000..846fd7fb8 --- /dev/null +++ b/naming/test/routing_config_test.go @@ -0,0 +1,316 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package test + +import ( + "encoding/json" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/utils" + "github.com/polarismesh/polaris-server/naming" + . "github.com/smartystreets/goconvey/convey" + "testing" + "time" +) + +// 检查routingConfig前后是否一致 +func checkSameRoutingConfig(t *testing.T, lhs *api.Routing, rhs *api.Routing) { + if lhs.GetService().GetValue() != rhs.GetService().GetValue() || + lhs.GetNamespace().GetValue() != rhs.GetNamespace().GetValue() { + t.Fatalf("error: (%s), (%s)", lhs, rhs) + } + + checkFunc := func(in []*api.Route, out []*api.Route) bool { + if in == nil && out == nil { + return true + } + + if in == nil || out == nil { + t.Fatalf("error: empty") + return false + } + + if len(in) != len(out) { + t.Fatalf("error: %d, %d", len(in), len(out)) + return false + } + + inStr, err := json.Marshal(in) + if err != nil { + t.Fatalf("error: %s", err.Error()) + return false + } + + outStr, err := json.Marshal(out) + if err != nil { + t.Fatalf("error: %s", err.Error()) + return false + } + + if string(inStr) != string(outStr) { + t.Fatalf("error: (%s), (%s)", string(inStr), string(outStr)) + return false + } + + return true + } + + checkFunc(lhs.Inbounds, rhs.Inbounds) + checkFunc(lhs.Outbounds, rhs.Outbounds) +} + +// 测试创建路由配置 +func TestCreateRoutingConfig(t *testing.T) { + Convey("正常创建路由配置配置请求", t, func() { + _, serviceResp := createCommonService(t, 200) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + req, _ := createCommonRoutingConfig(t, serviceResp, 3, 0) + + // 对写进去的数据进行查询 + time.Sleep(updateCacheInterval) + out := server.GetRoutingConfigWithCache(defaultCtx, serviceResp) + defer cleanCommonRoutingConfig(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + if !respSuccess(out) { + t.Fatalf("error: %+v", out) + } + checkSameRoutingConfig(t, req, out.GetRouting()) + }) + + Convey("同一个服务重复创建路由配置,报错", t, func() { + _, serviceResp := createCommonService(t, 10) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + req, _ := createCommonRoutingConfig(t, serviceResp, 1, 0) + defer cleanCommonRoutingConfig(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + resp := server.CreateRoutingConfig(defaultCtx, req) + So(respSuccess(resp), ShouldEqual, false) + t.Logf("%s", resp.GetInfo().GetValue()) + }) +} + +// 测试创建路由配置 +func TestCreateRoutingConfig2(t *testing.T) { + Convey("参数缺失,报错", t, func() { + _, serviceResp := createCommonService(t, 20) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + req := &api.Routing{} + resp := server.CreateRoutingConfig(defaultCtx, req) + So(respSuccess(resp), ShouldEqual, false) + t.Logf("%s", resp.GetInfo().GetValue()) + + req.Service = serviceResp.Name + resp = server.CreateRoutingConfig(defaultCtx, req) + So(respSuccess(resp), ShouldEqual, false) + t.Logf("%s", resp.GetInfo().GetValue()) + + req.Namespace = serviceResp.Namespace + resp = server.CreateRoutingConfig(defaultCtx, req) + So(respSuccess(resp), ShouldEqual, false) + t.Logf("%s", resp.GetInfo().GetValue()) + + req.ServiceToken = serviceResp.Token + resp = server.CreateRoutingConfig(defaultCtx, req) + defer cleanCommonRoutingConfig(req.GetService().GetValue(), req.GetNamespace().GetValue()) + So(respSuccess(resp), ShouldEqual, true) + t.Logf("%s", resp.GetInfo().GetValue()) + }) + + Convey("服务不存在,创建路由配置,报错", t, func() { + _, serviceResp := createCommonService(t, 120) + cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + req := &api.Routing{} + req.Service = serviceResp.Name + req.Namespace = serviceResp.Namespace + req.ServiceToken = serviceResp.Token + resp := server.CreateRoutingConfig(defaultCtx, req) + So(respSuccess(resp), ShouldEqual, false) + t.Logf("%s", resp.GetInfo().GetValue()) + }) +} + +// 测试删除路由配置 +func TestDeleteRoutingConfig(t *testing.T) { + Convey("可以正常删除路由配置", t, func() { + _, serviceResp := createCommonService(t, 100) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + _, resp := createCommonRoutingConfig(t, serviceResp, 3, 0) + resp.ServiceToken = utils.NewStringValue(serviceResp.GetToken().GetValue()) + deleteCommonRoutingConfig(t, resp) + defer cleanCommonRoutingConfig(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + // 删除之后,数据不见 + time.Sleep(updateCacheInterval) + out := server.GetRoutingConfigWithCache(defaultCtx, serviceResp) + So(out.GetRouting(), ShouldBeNil) + }) +} + +// 测试更新路由配置 +func TestUpdateRoutingConfig(t *testing.T) { + Convey("可以正常更新路由配置", t, func() { + _, serviceResp := createCommonService(t, 50) + serviceName := serviceResp.GetName().GetValue() + namespace := serviceResp.GetNamespace().GetValue() + defer cleanServiceName(serviceName, namespace) + + _, resp := createCommonRoutingConfig(t, serviceResp, 2, 0) + defer cleanCommonRoutingConfig(serviceName, namespace) + resp.ServiceToken = utils.NewStringValue(serviceResp.GetToken().GetValue()) + + resp.Outbounds = resp.Inbounds + resp.Inbounds = make([]*api.Route, 0) + updateCommonRoutingConfig(t, resp) + + time.Sleep(updateCacheInterval) + out := server.GetRoutingConfigWithCache(defaultCtx, serviceResp) + checkSameRoutingConfig(t, resp, out.GetRouting()) + }) +} + +// 测试缓存获取路由配置 +func TestGetRoutingConfigWithCache(t *testing.T) { + Convey("多个服务的,多个路由配置,都可以查询到", t, func() { + total := 20 + serviceResps := make([]*api.Service, 0, total) + routingResps := make([]*api.Routing, 0, total) + for i := 0; i < total; i++ { + _, resp := createCommonService(t, i) + defer cleanServiceName(resp.GetName().GetValue(), resp.GetNamespace().GetValue()) + serviceResps = append(serviceResps, resp) + + _, routingResp := createCommonRoutingConfig(t, resp, 2, 0) + defer cleanCommonRoutingConfig(resp.GetName().GetValue(), resp.GetNamespace().GetValue()) + routingResps = append(routingResps, routingResp) + } + + time.Sleep(updateCacheInterval) + for i := 0; i < total; i++ { + out := server.GetRoutingConfigWithCache(defaultCtx, serviceResps[i]) + checkSameRoutingConfig(t, routingResps[i], out.GetRouting()) + } + }) + Convey("服务路由数据不改变,传递了路由revision,不返回数据", t, func() { + _, serviceResp := createCommonService(t, 10) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + _, routingResp := createCommonRoutingConfig(t, serviceResp, 2, 0) + defer cleanCommonRoutingConfig(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + time.Sleep(updateCacheInterval) + firstResp := server.GetRoutingConfigWithCache(defaultCtx, serviceResp) + checkSameRoutingConfig(t, routingResp, firstResp.GetRouting()) + + serviceResp.Revision = firstResp.Service.Revision + secondResp := server.GetRoutingConfigWithCache(defaultCtx, serviceResp) + if secondResp.GetService().GetRevision().GetValue() != serviceResp.GetRevision().GetValue() { + t.Fatalf("error") + } + if secondResp.GetRouting() != nil { + t.Fatalf("error: %+v", secondResp.GetRouting()) + } + t.Logf("%+v", secondResp) + }) + Convey("路由不存在,不会出异常", t, func() { + _, serviceResp := createCommonService(t, 10) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + time.Sleep(updateCacheInterval) + if resp := server.GetRoutingConfigWithCache(defaultCtx, serviceResp); !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) +} + +// 测试直接从数据库读取路由配置数据 +func TestGetRoutings(t *testing.T) { + Convey("直接从数据库查询数据,可以查询成功", t, func() { + total := 10 + var serviceResp *api.Service + for i := 0; i < total; i++ { + tmp, resp := createCommonService(t, i) + serviceResp = tmp + defer cleanServiceName(resp.GetName().GetValue(), resp.GetNamespace().GetValue()) + + createCommonRoutingConfig(t, resp, 2, 0) + defer cleanCommonRoutingConfig(resp.GetName().GetValue(), resp.GetNamespace().GetValue()) + } + + resp := server.GetRoutingConfigs(defaultCtx, nil) + So(api.CalcCode(resp), ShouldEqual, 200) + So(len(resp.GetRoutings()), ShouldBeGreaterThanOrEqualTo, total) + + resp = server.GetRoutingConfigs(defaultCtx, map[string]string{"limit": "5"}) + So(api.CalcCode(resp), ShouldEqual, 200) + So(len(resp.GetRoutings()), ShouldEqual, 5) + + resp = server.GetRoutingConfigs(defaultCtx, map[string]string{"namespace": naming.DefaultNamespace}) + So(api.CalcCode(resp), ShouldEqual, 200) + So(len(resp.GetRoutings()), ShouldBeGreaterThanOrEqualTo, total) + + // 按命名空间和名字过滤,得到一个 + filter := map[string]string{ + "namespace": naming.DefaultNamespace, + "service": serviceResp.GetName().GetValue(), + } + resp = server.GetRoutingConfigs(defaultCtx, filter) + So(api.CalcCode(resp), ShouldEqual, 200) + So(len(resp.GetRoutings()), ShouldEqual, 1) + }) +} + +// test对routing字段进行校验 +func TestCheckRoutingFieldLen(t *testing.T) { + req := &api.Routing{ + ServiceToken: utils.NewStringValue("test"), + Service: utils.NewStringValue("test"), + Namespace: utils.NewStringValue("default"), + } + + t.Run("创建路由规则,服务名超长", func(t *testing.T) { + str := genSpecialStr(129) + oldName := req.Service + req.Service = utils.NewStringValue(str) + resp := server.CreateRoutingConfig(defaultCtx, req) + req.Service = oldName + if resp.Code.Value != api.InvalidServiceName { + t.Fatalf("%+v", resp) + } + }) + t.Run("创建路由规则,命名空间超长", func(t *testing.T) { + str := genSpecialStr(129) + oldNamespace := req.Namespace + req.Namespace = utils.NewStringValue(str) + resp := server.CreateRoutingConfig(defaultCtx, req) + req.Namespace = oldNamespace + if resp.Code.Value != api.InvalidNamespaceName { + t.Fatalf("%+v", resp) + } + }) + t.Run("创建路由规则,toeken超长", func(t *testing.T) { + str := genSpecialStr(2049) + oldServiceToken := req.ServiceToken + req.ServiceToken = utils.NewStringValue(str) + resp := server.CreateRoutingConfig(defaultCtx, req) + req.ServiceToken = oldServiceToken + if resp.Code.Value != api.InvalidServiceToken { + t.Fatalf("%+v", resp) + } + }) +} \ No newline at end of file diff --git a/naming/test/run.sh b/naming/test/run.sh new file mode 100644 index 000000000..159a0e7ea --- /dev/null +++ b/naming/test/run.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +go test -v -mod=vendor -cover -timeout=3600s -coverprofile=cover.out -coverpkg=github.com/polarismesh/polaris-server/naming,github.com/polarismesh/polaris-server/naming/batch,github.com/polarismesh/polaris-server/naming/cache,github.com/polarismesh/polaris-server/naming/auth,github.com/polarismesh/polaris-server/store/defaultStore,github.com/polarismesh/polaris-server/plugin/ratelimit/tokenBucket,github.com/polarismesh/polaris-server/common/model | tee test.log +go tool cover -html=cover.out -o index.html +coverage=$(cat test.log | grep "coverage:" | awk '{print $2}' | cut -d '%' -f 1) diff --git a/naming/test/service_alias_test.go b/naming/test/service_alias_test.go new file mode 100644 index 000000000..bb737ea45 --- /dev/null +++ b/naming/test/service_alias_test.go @@ -0,0 +1,684 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package test + +import ( + "context" + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/utils" + "github.com/polarismesh/polaris-server/naming" + . "github.com/smartystreets/goconvey/convey" + "regexp" + "strings" + "sync" + "testing" + "time" +) + +// 创建一个服务别名 +func createCommonAlias(service *api.Service, alias string, typ api.AliasType) *api.Response { + req := &api.ServiceAlias{ + Service: service.Name, + Namespace: service.Namespace, + Alias: utils.NewStringValue(alias), + Type: typ, + ServiceToken: service.Token, + } + return server.CreateServiceAlias(defaultCtx, req) +} + +// 创建一个服务别名(不需要鉴权) +func createCommonAliasNoAuth(service *api.Service, alias string, typ api.AliasType) *api.Response { + req := &api.ServiceAlias{ + Service: service.Name, + Namespace: service.Namespace, + Alias: utils.NewStringValue(alias), + Type: typ, + Owners: utils.NewStringValue("alias-owner"), + Comment: utils.NewStringValue("comment"), + } + return server.CreateServiceAliasNoAuth(defaultCtx, req) +} + +// 创建别名,并检查 +func createCommonAliasCheck(t *testing.T, service *api.Service, alias string, + typ api.AliasType) *api.Response { + resp := createCommonAlias(service, alias, typ) + if !respSuccess(resp) { + t.Fatalf("error") + } + return resp +} + +// 检查一个服务别名是否是sid +func isSid(alias string) bool { + items := strings.Split(alias, ":") + if len(items) != 2 { + return false + } + + for _, it := range items { + if ok, _ := regexp.MatchString("^[0-9]+$", it); !ok { + return false + } + } + + return true +} + +// 正常场景测试 +func TestCreateServiceAlias(t *testing.T) { + _, serviceResp := createCommonService(t, 123) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + Convey("正常创建非Sid的别名", t, func() { + alias := fmt.Sprintf("alias.%d", time.Now().Unix()) + resp := createCommonAlias(serviceResp, alias, api.AliasType_DEFAULT) + defer cleanServiceName(alias, serviceResp.GetNamespace().GetValue()) + So(respSuccess(resp), ShouldEqual, true) + So(resp.Alias.Alias.Value, ShouldEqual, alias) + }) + + Convey("正常创建Sid别名", t, func() { + resp := createCommonAlias(serviceResp, "", api.AliasType_CL5SID) + So(respSuccess(resp), ShouldEqual, true) + defer cleanServiceName(resp.Alias.Alias.Value, serviceResp.GetNamespace().GetValue()) + So(isSid(resp.Alias.Alias.Value), ShouldEqual, true) + t.Logf("alias sid: %s", resp.Alias.Alias.Value) + }) + + Convey("使用ctx带上的token可以创建成功", t, func() { + req := &api.ServiceAlias{ + Service: serviceResp.Name, + Namespace: serviceResp.Namespace, + Type: api.AliasType_CL5SID, + } + ctx := context.WithValue(defaultCtx, utils.StringContext("polaris-token"), + serviceResp.GetToken().GetValue()) + resp := server.CreateServiceAlias(ctx, req) + So(respSuccess(resp), ShouldEqual, true) + cleanServiceName(resp.Alias.Alias.Value, serviceResp.GetNamespace().GetValue()) + + // 带上系统token,也可以成功 + ctx = context.WithValue(defaultCtx, utils.StringContext("polaris-token"), + "polaris@12345678") + resp = server.CreateServiceAlias(ctx, req) + So(respSuccess(resp), ShouldEqual, true) + cleanServiceName(resp.Alias.Alias.Value, serviceResp.GetNamespace().GetValue()) + }) + Convey("不允许为别名创建别名", t, func() { + resp := createCommonAliasCheck(t, serviceResp, "", api.AliasType_CL5SID) + defer cleanServiceName(resp.Alias.Alias.Value, serviceResp.Namespace.Value) + + service := &api.Service{ + Name: resp.Alias.Alias, + Namespace: serviceResp.Namespace, + Token: serviceResp.Token, + } + repeatedResp := createCommonAlias(service, "", api.AliasType_CL5SID) + if respSuccess(repeatedResp) { + t.Fatalf("error: %+v", repeatedResp) + } + t.Logf("%+v", repeatedResp) + }) +} + +// 测试不需要鉴权的创建服务别名场景 +func TestCreateServiceAliasNoAuth(t *testing.T) { + _, serviceResp := createCommonService(t, 1) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + Convey("正常创建非Sid的别名(新接口)", t, func() { + alias := fmt.Sprintf("alias.%d", time.Now().Unix()) + resp := createCommonAliasNoAuth(serviceResp, alias, api.AliasType_DEFAULT) + defer cleanServiceName(alias, serviceResp.GetNamespace().GetValue()) + So(respSuccess(resp), ShouldEqual, true) + So(resp.GetAlias().GetAlias().GetValue(), ShouldEqual, alias) + }) + + Convey("正常创建Sid的别名(新接口)", t, func() { + resp := createCommonAliasNoAuth(serviceResp, "", api.AliasType_CL5SID) + defer cleanServiceName(resp.GetAlias().GetAlias().GetValue(), serviceResp.GetNamespace().GetValue()) + So(respSuccess(resp), ShouldEqual, true) + So(isSid(resp.Alias.Alias.Value), ShouldEqual, true) + t.Logf("alias sid: %s", resp.Alias.Alias.Value) + }) + + Convey("不允许为别名创建别名(新接口)", t, func() { + resp := createCommonAliasCheck(t, serviceResp, "", api.AliasType_CL5SID) + defer cleanServiceName(resp.Alias.Alias.Value, serviceResp.Namespace.Value) + + service := &api.Service{ + Name: resp.Alias.Alias, + Namespace: serviceResp.Namespace, + } + repeatedResp := createCommonAliasNoAuth(service, "", api.AliasType_CL5SID) + if respSuccess(repeatedResp) { + t.Fatalf("error: %+v", repeatedResp) + } + t.Logf("%+v", repeatedResp) + }) +} + +// 重点测试创建sid别名的场景 +// 注意:该测试函数出错的情况,会遗留一些测试数据无法清理 TODO +func TestCreateSid(t *testing.T) { + Convey("创建不同命名空间的sid,可以返回符合规范的sid", t, func() { + for namespace, layout := range naming.Namespace2SidLayoutID { + service := &api.Service{ + Name: utils.NewStringValue("sid-test-xxx"), + Namespace: utils.NewStringValue(namespace), + Revision: utils.NewStringValue("revision111"), + Owners: utils.NewStringValue("owners111"), + } + cleanServiceName(service.GetName().GetValue(), service.GetNamespace().GetValue()) + serviceResp := server.CreateService(defaultCtx, service) + So(respSuccess(serviceResp), ShouldEqual, true) + + aliasResp := createCommonAlias(serviceResp.Service, "", api.AliasType_CL5SID) + So(respSuccess(aliasResp), ShouldEqual, true) + modID, cmdID := parseStr2Sid(aliasResp.GetAlias().GetAlias().GetValue()) + So(modID, ShouldNotEqual, uint32(0)) + So(cmdID, ShouldNotEqual, uint32(0)) + So(modID>>6, ShouldBeGreaterThanOrEqualTo, 3000001) // module + So(modID&63, ShouldEqual, layout) // 根据保留字段标识服务名 + So(aliasResp.GetAlias().GetNamespace().GetValue(), ShouldEqual, namespace) + cleanServiceName(aliasResp.GetAlias().GetAlias().GetValue(), namespace) + cleanServiceName(service.GetName().GetValue(), service.GetNamespace().GetValue()) + } + }) + Convey("非默认的5个命名空间,不允许创建sid别名", t, func() { + namespace := &api.Namespace{ + Name: utils.NewStringValue("other-namespace-xxx"), + Owners: utils.NewStringValue("aaa"), + } + So(respSuccess(server.CreateNamespace(defaultCtx, namespace)), ShouldEqual, true) + defer cleanNamespace(namespace.Name.Value) + + service := &api.Service{ + Name: utils.NewStringValue("sid-test-xxx"), + Namespace: utils.NewStringValue(namespace.Name.Value), + Revision: utils.NewStringValue("revision111"), + Owners: utils.NewStringValue("owners111"), + } + serviceResp := server.CreateService(defaultCtx, service) + So(respSuccess(serviceResp), ShouldEqual, true) + defer cleanServiceName(service.GetName().GetValue(), service.GetNamespace().GetValue()) + aliasResp := createCommonAlias(serviceResp.Service, "", api.AliasType_CL5SID) + So(respSuccess(aliasResp), ShouldEqual, false) + t.Logf("%s", aliasResp.GetInfo().GetValue()) + }) +} + +// 并发测试 +func TestConcurrencyCreateSid(t *testing.T) { + _, serviceResp := createCommonService(t, 234) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + Convey("并发创建sid别名,sid不会重复", t, func() { + c := 200 + var wg sync.WaitGroup + resultCh := make(chan *api.Response, 1) + results := make([]*api.Response, 0, 200) + shutdown := make(chan struct{}) + + go func() { + for { + select { + case result := <-resultCh: + results = append(results, result) + case <-shutdown: + t.Log("[Alias] concurrency function exit") + return + } + } + }() + + for i := 0; i < c; i++ { + wg.Add(1) + go func(index int) { + defer func() { + t.Logf("[Alias] finish creating alias sid func index(%d)", index) + wg.Done() + }() + resp := createCommonAlias(serviceResp, "", api.AliasType_CL5SID) + resultCh <- resp + }(i) + } + + wg.Wait() + time.Sleep(time.Second) + close(shutdown) + + repeated := make(map[string]bool) + for i := 0; i < c; i++ { + resp := results[i] + So(respSuccess(resp), ShouldEqual, true) + defer cleanServiceName(resp.Alias.Alias.Value, serviceResp.GetNamespace().GetValue()) + So(isSid(resp.Alias.Alias.Value), ShouldEqual, true) + repeated[resp.Alias.Alias.Value] = true + } + // 检查是否重复,必须是200个 + So(len(repeated), ShouldEqual, c) + }) +} + +// 异常测试 +func TestExceptCreateAlias(t *testing.T) { + _, serviceResp := createCommonService(t, 345) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + Convey("参数缺失,报错", t, func() { + noService := &api.Service{} + resp := createCommonAlias(noService, "x1.x2.x3", api.AliasType_DEFAULT) + So(respSuccess(resp), ShouldEqual, false) + + noService.Name = utils.NewStringValue("123") + resp = createCommonAlias(noService, "x1.x2.x3", api.AliasType_DEFAULT) + So(respSuccess(resp), ShouldEqual, false) + + noService.Namespace = utils.NewStringValue("456") + resp = createCommonAlias(noService, "x1.x2.x3", api.AliasType_DEFAULT) + So(respSuccess(resp), ShouldEqual, false) + + noService.Token = utils.NewStringValue("567") + resp = createCommonAlias(noService, "", api.AliasType_DEFAULT) + So(respSuccess(resp), ShouldEqual, false) + t.Logf("return code: %d", resp.Code.Value) + }) + + Convey("不存在的源服务,报错", t, func() { + noService := &api.Service{ + Name: utils.NewStringValue("my.service.2020.02.19"), + Namespace: utils.NewStringValue("123123"), + Token: utils.NewStringValue("aaa"), + } + resp := createCommonAlias(noService, "x1.x2.x3", api.AliasType_DEFAULT) + So(respSuccess(resp), ShouldEqual, false) + t.Logf("return code: %d", resp.Code.Value) + So(resp.Code.Value, ShouldEqual, api.NotFoundService) + }) + + Convey("同名alias,报错", t, func() { + resp := createCommonAlias(serviceResp, "x1.x2.x3", api.AliasType_DEFAULT) + So(respSuccess(resp), ShouldEqual, true) + defer cleanServiceName(resp.Alias.Alias.Value, serviceResp.GetNamespace().GetValue()) + + resp = createCommonAlias(serviceResp, "x1.x2.x3", api.AliasType_DEFAULT) + So(respSuccess(resp), ShouldEqual, false) + t.Logf("same alias return code: %d", resp.Code.Value) + }) + + Convey("鉴权失败,报错", t, func() { + service := &api.Service{ + Name: serviceResp.Name, + Namespace: serviceResp.Namespace, + Token: utils.NewStringValue("123123123"), + } + resp := createCommonAlias(service, "x1.x2.x3", api.AliasType_DEFAULT) + So(respSuccess(resp), ShouldEqual, false) + t.Logf("error token, return code: %d", resp.Code.Value) + }) + + Convey("指向的服务不存在(新接口)", t, func() { + _, serviceResp2 := createCommonService(t, 2) + cleanServiceName(serviceResp2.GetName().GetValue(), serviceResp2.GetNamespace().GetValue()) + resp := createCommonAliasNoAuth(serviceResp2, "", api.AliasType_CL5SID) + if respSuccess(resp) { + t.Fatalf("error: %+v", resp) + } + t.Logf("%+v", resp) + }) + + Convey("请求参数没有负责人(新接口)", t, func() { + req := &api.ServiceAlias{ + Service: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + Type: api.AliasType_CL5SID, + } + resp := server.CreateServiceAliasNoAuth(defaultCtx, req) + if respSuccess(resp) { + t.Fatalf("error: %+v", resp) + } + t.Logf("%+v", resp) + }) +} + +// 别名修改的测试 +func TestUpdateServiceAlias(t *testing.T) { + _, serviceResp := createCommonService(t, 3) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + Convey("修改别名负责人", t, func() { + resp := createCommonAliasNoAuth(serviceResp, "", api.AliasType_CL5SID) + So(respSuccess(resp), ShouldEqual, true) + defer cleanServiceName(resp.GetAlias().GetAlias().GetValue(), serviceResp.GetNamespace().GetValue()) + + // 修改别名负责人 + req := &api.ServiceAlias{ + Service: resp.GetAlias().GetService(), + Namespace: resp.GetAlias().GetNamespace(), + Alias: resp.GetAlias().GetAlias(), + Owners: utils.NewStringValue("alias-owner-new"), + ServiceToken: resp.GetAlias().GetServiceToken(), + } + + repeatedResp := server.UpdateServiceAlias(defaultCtx, req) + So(respSuccess(repeatedResp), ShouldEqual, true) + + query := map[string]string{ + "alias": req.GetAlias().GetValue(), + "namespace": req.GetNamespace().GetValue(), + } + aliasResponse := server.GetServiceAliases(query) + // 判断负责人是否一致 + So(aliasResponse.GetAliases()[0].GetOwners().GetValue(), ShouldEqual, "alias-owner-new") + t.Logf("pass, owner is %v", aliasResponse.GetAliases()[0].GetOwners().GetValue()) + }) + + Convey("修改指向服务", t, func() { + resp := createCommonAliasNoAuth(serviceResp, "", api.AliasType_CL5SID) + So(respSuccess(resp), ShouldEqual, true) + defer cleanServiceName(resp.GetAlias().GetAlias().GetValue(), serviceResp.GetNamespace().GetValue()) + + // 创建新的服务 + _, serviceResp2 := createCommonService(t, 4) + defer cleanServiceName(serviceResp2.GetName().GetValue(), serviceResp2.GetNamespace().GetValue()) + + // 修改别名指向 + req := &api.ServiceAlias{ + Service: serviceResp2.GetName(), + Namespace: serviceResp2.GetNamespace(), + Alias: resp.GetAlias().GetAlias(), + Owners: resp.GetAlias().GetOwners(), + Comment: resp.GetAlias().GetComment(), + ServiceToken: resp.GetAlias().GetServiceToken(), + } + + repeatedResp := server.UpdateServiceAlias(defaultCtx, req) + So(respSuccess(repeatedResp), ShouldEqual, true) + + query := map[string]string{ + "alias": req.GetAlias().GetValue(), + "namespace": req.GetNamespace().GetValue(), + } + aliasResponse := server.GetServiceAliases(query) + // 判断指向服务是否一致 + So(aliasResponse.GetAliases()[0].GetService().GetValue(), ShouldEqual, serviceResp2.GetName().GetValue()) + t.Logf("pass, service is %v", aliasResponse.GetAliases()[0].GetService().GetValue()) + }) + + Convey("要指向的服务不存在", t, func() { + resp := createCommonAliasNoAuth(serviceResp, "", api.AliasType_CL5SID) + So(respSuccess(resp), ShouldEqual, true) + defer cleanServiceName(resp.GetAlias().GetAlias().GetValue(), serviceResp.GetNamespace().GetValue()) + + // 创建新的服务并删除 + _, serviceResp2 := createCommonService(t, 4) + cleanServiceName(serviceResp2.GetName().GetValue(), serviceResp2.GetNamespace().GetValue()) + + // 修改别名指向 + req := &api.ServiceAlias{ + Service: serviceResp2.GetName(), + Namespace: serviceResp2.GetNamespace(), + Alias: resp.GetAlias().GetAlias(), + Owners: resp.GetAlias().GetOwners(), + Comment: resp.GetAlias().GetComment(), + ServiceToken: resp.GetAlias().GetServiceToken(), + } + repeatedResp := server.UpdateServiceAlias(defaultCtx, req) + if respSuccess(repeatedResp) { + t.Fatalf("error: %+v", repeatedResp) + } + t.Logf("%+v", repeatedResp) + }) + + Convey("鉴权失败", t, func() { + resp := createCommonAliasNoAuth(serviceResp, "", api.AliasType_CL5SID) + So(respSuccess(resp), ShouldEqual, true) + defer cleanServiceName(resp.GetAlias().GetAlias().GetValue(), serviceResp.GetNamespace().GetValue()) + + // 修改service token + req := resp.GetAlias() + req.ServiceToken = utils.NewStringValue("") + repeatedResp := server.UpdateServiceAlias(defaultCtx, req) + if respSuccess(repeatedResp) { + t.Fatalf("error: %+v", repeatedResp) + } + t.Logf("%+v", repeatedResp) + }) +} + +// 别名删除 +func TestDeleteServiceAlias(t *testing.T) { + _, serviceResp := createCommonService(t, 201) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + Convey("通过服务删除接口可以直接删除别名", t, func() { + resp := createCommonAlias(serviceResp, "", api.AliasType_CL5SID) + So(respSuccess(resp), ShouldEqual, true) + defer cleanServiceName(resp.Alias.Alias.Value, serviceResp.Namespace.Value) + + service := &api.Service{ + Name: resp.Alias.Alias, + Namespace: serviceResp.Namespace, + Token: serviceResp.Token, + } + removeCommonServices(t, []*api.Service{service}) + + query := map[string]string{"name": resp.Alias.Alias.Value} + queryResp := server.GetServices(query) + So(respSuccess(queryResp), ShouldEqual, true) + So(len(queryResp.Services), ShouldEqual, 0) + }) + + Convey("通过ctx带上token,可以删除别名成功", t, func() { + resp := createCommonAlias(serviceResp, "", api.AliasType_CL5SID) + So(respSuccess(resp), ShouldEqual, true) + defer cleanServiceName(resp.Alias.Alias.Value, serviceResp.Namespace.Value) + + service := &api.Service{Name: resp.Alias.Alias, Namespace: serviceResp.Namespace} + ctx := context.WithValue(defaultCtx, utils.StringContext("polaris-token"), + "polaris@12345678") + So(respSuccess(server.DeleteService(ctx, service)), ShouldEqual, true) + }) +} + +// 服务实例与服务路由关联测试 +func TestServiceAliasRelated(t *testing.T) { + _, serviceResp := createCommonService(t, 202) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + resp := createCommonAlias(serviceResp, "", api.AliasType_CL5SID) + if !respSuccess(resp) { + t.Fatalf("errror") + } + defer cleanServiceName(resp.Alias.Alias.Value, serviceResp.Namespace.Value) + Convey("实例新建,不允许为别名新建实例", t, func() { + instance := &api.Instance{ + Service: resp.Alias.Alias, + Namespace: serviceResp.Namespace, + ServiceToken: serviceResp.Token, + Host: utils.NewStringValue("1.12.123.132"), + Port: utils.NewUInt32Value(8080), + } + instanceResp := server.CreateInstance(defaultCtx, instance) + So(respSuccess(instanceResp), ShouldEqual, false) + t.Logf("alias create instance ret code(%d), msg(%s)", + instanceResp.Code.Value, instanceResp.Info.Value) + }) + Convey("实例Discover,别名查询实例,返回源服务的实例信息", t, func() { + _, instanceResp := createCommonInstance(t, serviceResp, 123) + defer cleanInstance(instanceResp.GetId().GetValue()) + + time.Sleep(updateCacheInterval) + service := &api.Service{Name: resp.Alias.Alias, Namespace: resp.Alias.Namespace} + disResp := server.ServiceInstancesCache(defaultCtx, service) + So(respSuccess(disResp), ShouldEqual, true) + So(len(disResp.Instances), ShouldEqual, 1) + }) + Convey("路由新建,不允许为别名新建路由", t, func() { + routing := &api.Routing{ + Service: resp.Alias.Alias, + Namespace: resp.Alias.Namespace, + ServiceToken: serviceResp.Token, + Inbounds: make([]*api.Route, 0), + } + routingResp := server.CreateRoutingConfig(defaultCtx, routing) + So(respSuccess(routingResp), ShouldEqual, false) + t.Logf("create routing ret code(%d), info(%s)", routingResp.Code.Value, routingResp.Info.Value) + }) + Convey("路由Discover,别名查询路由,返回源服务的路由信息", t, func() { + createCommonRoutingConfig(t, serviceResp, 1, 0) // in=1, out=0 + defer cleanCommonRoutingConfig(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + time.Sleep(updateCacheInterval) + service := &api.Service{Name: resp.Alias.Alias, Namespace: resp.Alias.Namespace} + disResp := server.GetRoutingConfigWithCache(defaultCtx, service) + So(respSuccess(disResp), ShouldEqual, true) + So(len(disResp.Routing.Inbounds), ShouldEqual, 1) + So(len(disResp.Routing.Outbounds), ShouldEqual, 0) + }) +} + +// 测试获取别名列表 +func TestGetServiceAliases(t *testing.T) { + _, serviceResp := createCommonService(t, 203) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + var aliases []*api.Response + count := 5 + for i := 0; i < count; i++ { + resp := createCommonAlias(serviceResp, "", api.AliasType_CL5SID) + if !respSuccess(resp) { + t.Fatalf("error: %+v", resp) + } + defer cleanServiceName(resp.Alias.Alias.Value, serviceResp.Namespace.Value) + aliases = append(aliases, resp) + } + + Convey("可以查询到全量别名", t, func() { + resp := server.GetServiceAliases(nil) + So(respSuccess(resp), ShouldEqual, true) + So(len(resp.Aliases), ShouldBeGreaterThanOrEqualTo, count) + So(resp.Amount.Value, ShouldBeGreaterThanOrEqualTo, count) + }) + Convey("offset,limit测试", t, func() { + query := map[string]string{"offset": "0", "limit": "100"} + resp := server.GetServiceAliases(query) + So(respSuccess(resp), ShouldEqual, true) + So(len(resp.Aliases), ShouldBeGreaterThanOrEqualTo, count) + So(resp.Amount.Value, ShouldBeGreaterThanOrEqualTo, count) + + query["limit"] = "0" + resp = server.GetServiceAliases(query) + So(respSuccess(resp), ShouldEqual, true) + So(len(resp.Aliases), ShouldEqual, 0) + So(resp.Amount.Value, ShouldBeGreaterThanOrEqualTo, count) + }) + Convey("不合法的过滤条件", t, func() { + query := map[string]string{"xxx": "1", "limit": "100"} + resp := server.GetServiceAliases(query) + So(respSuccess(resp), ShouldEqual, false) + }) + Convey("过滤条件可以生效", t, func() { + query := map[string]string{ + "alias": aliases[2].Alias.Alias.Value, + "service": serviceResp.Name.Value, + "namespace": serviceResp.Namespace.Value, + } + resp := server.GetServiceAliases(query) + So(respSuccess(resp), ShouldEqual, true) + So(len(resp.Aliases), ShouldEqual, 1) + So(resp.Amount.Value, ShouldEqual, 1) + }) + Convey("找不到别名", t, func() { + query := map[string]string{"alias": "x1.1.x2.x3"} + resp := server.GetServiceAliases(query) + So(respSuccess(resp), ShouldEqual, true) + So(len(resp.Aliases), ShouldEqual, 0) + So(resp.Amount.Value, ShouldEqual, 0) + }) + Convey("支持owner过滤", t, func() { + query := map[string]string{"owner": "service-owner-203"} + resp := server.GetServiceAliases(query) + So(respSuccess(resp), ShouldEqual, true) + So(len(resp.Aliases), ShouldEqual, count) + So(resp.Amount.Value, ShouldEqual, count) + }) +} + +// test对serviceAlias字段进行校验 +func TestCheckServiceAliasFieldLen(t *testing.T) { + serviceAlias := &api.ServiceAlias{ + Service: utils.NewStringValue("test-123"), + Namespace: utils.NewStringValue("default"), + Alias: utils.NewStringValue("0"), + Type: api.AliasType_DEFAULT, + Owners: utils.NewStringValue("alias-owner"), + Comment: utils.NewStringValue("comment"), + } + t.Run("服务名超长", func(t *testing.T) { + str := genSpecialStr(129) + oldService := serviceAlias.Service + serviceAlias.Service = utils.NewStringValue(str) + resp := server.CreateServiceAliasNoAuth(defaultCtx, serviceAlias) + serviceAlias.Service = oldService + if resp.Code.Value != api.InvalidServiceName { + t.Fatalf("%+v", resp) + } + }) + t.Run("命名空间超长", func(t *testing.T) { + str := genSpecialStr(129) + oldNamespace := serviceAlias.Namespace + serviceAlias.Namespace = utils.NewStringValue(str) + resp := server.CreateServiceAliasNoAuth(defaultCtx, serviceAlias) + serviceAlias.Namespace = oldNamespace + if resp.Code.Value != api.InvalidNamespaceName { + t.Fatalf("%+v", resp) + } + }) + t.Run("别名超长", func(t *testing.T) { + str := genSpecialStr(129) + oldAlias := serviceAlias.Alias + serviceAlias.Alias = utils.NewStringValue(str) + resp := server.CreateServiceAliasNoAuth(defaultCtx, serviceAlias) + serviceAlias.Alias = oldAlias + if resp.Code.Value != api.InvalidServiceAlias { + t.Fatalf("%+v", resp) + } + }) + t.Run("服务别名comment超长", func(t *testing.T) { + str := genSpecialStr( 1025) + oldComment := serviceAlias.Comment + serviceAlias.Comment = utils.NewStringValue(str) + resp := server.CreateServiceAliasNoAuth(defaultCtx, serviceAlias) + serviceAlias.Comment = oldComment + if resp.Code.Value != api.InvalidServiceAliasComment { + t.Fatalf("%+v", resp) + } + }) + t.Run("服务owner超长", func(t *testing.T) { + str := genSpecialStr( 1025) + oldOwner := serviceAlias.Owners + serviceAlias.Owners = utils.NewStringValue(str) + resp := server.CreateServiceAliasNoAuth(defaultCtx, serviceAlias) + serviceAlias.Owners = oldOwner + if resp.Code.Value != api.InvalidServiceAliasOwners { + t.Fatalf("%+v", resp) + } + }) +} \ No newline at end of file diff --git a/naming/test/service_test.go b/naming/test/service_test.go new file mode 100644 index 000000000..25511b8dd --- /dev/null +++ b/naming/test/service_test.go @@ -0,0 +1,1076 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package test + +import ( + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/utils" + "github.com/polarismesh/polaris-server/naming" + . "github.com/smartystreets/goconvey/convey" + "strconv" + "sync" + "testing" + "time" +) + +// 测试新增服务 +func TestCreateService(t *testing.T) { + t.Run("正常创建服务", func(t *testing.T) { + serviceReq, serviceResp := createCommonService(t, 9) + defer cleanServiceName(serviceReq.GetName().GetValue(), serviceReq.GetNamespace().GetValue()) + + if serviceResp.GetName().GetValue() == serviceReq.GetName().GetValue() && + serviceResp.GetNamespace().GetValue() == serviceReq.GetNamespace().GetValue() && + serviceResp.GetToken().GetValue() != "" { + t.Logf("pass") + } else { + t.Fatalf("error: %+v", serviceResp) + } + }) + + t.Run("创建重复名字的服务,会返回失败", func(t *testing.T) { + serviceReq, _ := createCommonService(t, 9) + defer cleanServiceName(serviceReq.GetName().GetValue(), serviceReq.GetNamespace().GetValue()) + + resp := server.CreateService(defaultCtx, serviceReq) + if !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error") + } + }) + + t.Run("创建服务,删除,再次创建,可以正常创建", func(t *testing.T) { + serviceReq, serviceResp := createCommonService(t, 100) + defer cleanServiceName(serviceReq.GetName().GetValue(), serviceReq.GetNamespace().GetValue()) + + req := &api.Service{ + Name: utils.NewStringValue(serviceResp.GetName().GetValue()), + Namespace: utils.NewStringValue(serviceResp.GetNamespace().GetValue()), + Token: utils.NewStringValue(serviceResp.GetToken().GetValue()), + } + removeCommonServices(t, []*api.Service{req}) + + if resp := server.CreateService(defaultCtx, serviceReq); !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + + t.Logf("pass") + }) + t.Run("并发创建服务", func(t *testing.T) { + var wg sync.WaitGroup + for i := 0; i < 500; i++ { + wg.Add(1) + go func(index int) { + defer wg.Done() + serviceReq, _ := createCommonService(t, index) + cleanServiceName(serviceReq.GetName().GetValue(), serviceReq.GetNamespace().GetValue()) + }(i) + } + wg.Wait() + }) + t.Run("命名空间不存在,无法创建服务", func(t *testing.T) { + service := &api.Service{ + Name: utils.NewStringValue("abc"), + Namespace: utils.NewStringValue("123456"), + Owners: utils.NewStringValue("my"), + } + resp := server.CreateService(defaultCtx, service) + if respSuccess(resp) { + t.Fatalf("error") + } + t.Logf("pass: %s", resp.GetInfo().GetValue()) + }) + t.Run("创建服务,metadata个数太多,报错", func(t *testing.T) { + service := &api.Service{ + Name: utils.NewStringValue("999"), + Namespace: utils.NewStringValue("Polaris"), + Owners: utils.NewStringValue("my"), + } + service.Metadata = make(map[string]string) + for i := 0; i < naming.MaxMetadataLength+1; i++ { + service.Metadata[fmt.Sprintf("aa-%d", i)] = "value" + } + if resp := server.CreateService(defaultCtx, service); !respSuccess(resp) { + t.Logf("%s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error") + } + }) +} + +// delete services +func TestRemoveServices(t *testing.T) { + t.Run("删除单个服务,删除成功", func(t *testing.T) { + serviceReq, serviceResp := createCommonService(t, 59) + defer cleanServiceName(serviceReq.GetName().GetValue(), serviceReq.GetNamespace().GetValue()) + + req := &api.Service{ + Name: utils.NewStringValue(serviceResp.GetName().GetValue()), + Namespace: utils.NewStringValue(serviceResp.GetNamespace().GetValue()), + Token: utils.NewStringValue(serviceResp.GetToken().GetValue()), + } + + // wait for data cache + time.Sleep(time.Second * 2) + removeCommonServices(t, []*api.Service{req}) + out := server.GetServices(map[string]string{"name": req.GetName().GetValue()}) + if !respSuccess(out) { + t.Fatalf(out.GetInfo().GetValue()) + } + if len(out.GetServices()) != 0 { + t.Fatalf("error: %d", len(out.GetServices())) + } + }) + + t.Run("删除多个服务,删除成功", func(t *testing.T) { + var reqs []*api.Service + for i := 0; i < 100; i++ { + serviceReq, serviceResp := createCommonService(t, i) + defer cleanServiceName(serviceReq.GetName().GetValue(), serviceReq.GetNamespace().GetValue()) + req := &api.Service{ + Name: utils.NewStringValue(serviceResp.GetName().GetValue()), + Namespace: utils.NewStringValue(serviceResp.GetNamespace().GetValue()), + Token: utils.NewStringValue(serviceResp.GetToken().GetValue()), + } + reqs = append(reqs, req) + } + + // wait for data cache + time.Sleep(time.Second * 2) + removeCommonServices(t, reqs) + }) + + t.Run("创建一个服务,马上删除,可以正常删除", func(t *testing.T) { + serviceReq, serviceResp := createCommonService(t, 19) + defer cleanServiceName(serviceReq.GetName().GetValue(), serviceReq.GetNamespace().GetValue()) + + req := &api.Service{ + Name: utils.NewStringValue(serviceResp.GetName().GetValue()), + Namespace: utils.NewStringValue(serviceResp.GetNamespace().GetValue()), + Token: utils.NewStringValue(serviceResp.GetToken().GetValue()), + } + removeCommonServices(t, []*api.Service{req}) + }) + t.Run("创建服务和实例,删除服务,删除失败", func(t *testing.T) { + serviceReq, serviceResp := createCommonService(t, 19) + defer cleanServiceName(serviceReq.GetName().GetValue(), serviceReq.GetNamespace().GetValue()) + + _, instanceResp := createCommonInstance(t, serviceResp, 100) + defer cleanInstance(instanceResp.GetId().GetValue()) + + resp := server.DeleteService(defaultCtx, serviceResp) + if !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error") + } + }) + + t.Run("并发删除服务", func(t *testing.T) { + var wg sync.WaitGroup + for i := 0; i < 20; i++ { + serviceReq, serviceResp := createCommonService(t, i) + defer cleanServiceName(serviceReq.GetName().GetValue(), serviceReq.GetNamespace().GetValue()) + req := &api.Service{ + Name: utils.NewStringValue(serviceResp.GetName().GetValue()), + Namespace: utils.NewStringValue(serviceResp.GetNamespace().GetValue()), + Token: utils.NewStringValue(serviceResp.GetToken().GetValue()), + } + + wg.Add(1) + go func(reqs []*api.Service) { + defer wg.Done() + removeCommonServices(t, reqs) + }([]*api.Service{req}) + } + wg.Wait() + }) +} + +// 关联测试 +func TestDeleteService2(t *testing.T) { + t.Run("存在路由配置的情况下,删除服务会失败", func(t *testing.T) { + serviceReq, serviceResp := createCommonService(t, 20) + defer cleanServiceName(serviceReq.GetName().GetValue(), serviceReq.GetNamespace().GetValue()) + + // 创建一个服务配置 + createCommonRoutingConfig(t, serviceResp, 10, 10) + defer cleanCommonRoutingConfig(serviceReq.GetName().GetValue(), serviceReq.GetNamespace().GetValue()) + // 删除服务 + resp := server.DeleteService(defaultCtx, serviceResp) + if respSuccess(resp) { + t.Fatalf("error") + } + t.Logf("pass: %s", resp.GetInfo().GetValue()) + }) + t.Run("重复删除服务,返回成功", func(t *testing.T) { + serviceReq, serviceResp := createCommonService(t, 20) + defer cleanServiceName(serviceReq.GetName().GetValue(), serviceReq.GetNamespace().GetValue()) + + removeCommonServices(t, []*api.Service{serviceResp}) + removeCommonServices(t, []*api.Service{serviceResp}) + }) + t.Run("存在别名的情况下,删除服务会失败", func(t *testing.T) { + serviceReq, serviceResp := createCommonService(t, 20) + defer cleanServiceName(serviceReq.GetName().GetValue(), serviceReq.GetNamespace().GetValue()) + + aliasResp1 := createCommonAlias(serviceResp, "", api.AliasType_CL5SID) + defer cleanServiceName(aliasResp1.Alias.Alias.Value, serviceResp.Namespace.Value) + aliasResp2 := createCommonAlias(serviceResp, "", api.AliasType_CL5SID) + defer cleanServiceName(aliasResp2.Alias.Alias.Value, serviceResp.Namespace.Value) + + // 删除服务 + resp := server.DeleteService(defaultCtx, serviceResp) + if respSuccess(resp) { + t.Fatalf("error") + } + t.Logf("pass: %s", resp.GetInfo().GetValue()) + }) +} + +// 测试批量获取服务负责人 +func TestGetServiceOwner(t *testing.T) { + t.Run("服务个数为0,返回错误", func(t *testing.T) { + var reqs []*api.Service + if resp := server.GetServiceOwner(defaultCtx, reqs); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) + + t.Run("服务个数超过100,返回错误", func(t *testing.T) { + reqs := make([]*api.Service, 0, 101) + for i := 0; i < 101; i++ { + req := &api.Service{ + Namespace: utils.NewStringValue("Test"), + Name: utils.NewStringValue("test"), + } + reqs = append(reqs, req) + } + if resp := server.GetServiceOwner(defaultCtx, reqs); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) + + t.Run("查询100个超长服务名的服务负责人,数据库不会报错", func(t *testing.T) { + reqs := make([]*api.Service, 0, 100) + for i := 0; i < 100; i++ { + req := &api.Service{ + Namespace: utils.NewStringValue("Development"), + Name: utils.NewStringValue(genSpecialStr(128)), + } + reqs = append(reqs, req) + } + if resp := server.GetServiceOwner(defaultCtx, reqs); !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) +} + +// 测试获取服务函数 +func TestGetService(t *testing.T) { + t.Run("查询服务列表,可以正常返回", func(t *testing.T) { + resp := server.GetServices(map[string]string{}) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.Info.GetValue()) + } + }) + t.Run("查询服务列表,只有limit和offset,可以正常返回预计个数的服务", func(t *testing.T) { + total := 20 + reqs := make([]*api.Service, 0, total) + for i := 0; i < total; i++ { + serviceReq, _ := createCommonService(t, i+10) + reqs = append(reqs, serviceReq) + defer cleanServiceName(serviceReq.GetName().GetValue(), serviceReq.GetNamespace().GetValue()) + } + + // 创建完,直接查询 + filters := map[string]string{"offset": "0", "limit": "100"} + resp := server.GetServices(filters) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.Info.GetValue()) + } + + if resp.GetSize().GetValue() >= uint32(total) && resp.GetSize().GetValue() <= 100 { + t.Logf("pass") + } else { + t.Fatalf("error: %d %d", resp.GetSize().GetValue(), total) + } + }) + + t.Run("查询服务列表,没有filter,只回复默认的service", func(t *testing.T) { + total := 10 + for i := 0; i < total; i++ { + serviceReq, _ := createCommonService(t, i+10) + defer cleanServiceName(serviceReq.GetName().GetValue(), serviceReq.GetNamespace().GetValue()) + } + + resp := server.GetServices(map[string]string{}) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.Info.GetValue()) + } + if resp.GetSize().GetValue() >= 10 { + t.Logf("pass") + } else { + t.Fatalf("error: %d", resp.GetSize().GetValue()) + } + }) + t.Run("查询服务列表,只能查询到源服务,无法查询到别名", func(t *testing.T) { + total := 10 + for i := 0; i < total; i++ { + _, serviceResp := createCommonService(t, i+102) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + aliasResp := createCommonAlias(serviceResp, "", api.AliasType_CL5SID) + defer cleanServiceName(aliasResp.Alias.Alias.Value, serviceResp.Namespace.Value) + } + resp := server.GetServices(map[string]string{"owner": "service-owner-102"}) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.Info.GetValue()) + } + if resp.GetSize().GetValue() != 1 { + t.Fatalf("error: %d", resp.GetSize().GetValue()) + } + }) +} + +// 测试获取服务列表,参数校验 +func TestGetServices2(t *testing.T) { + t.Run("查询服务列表,limit有最大为100的限制", func(t *testing.T) { + total := 101 + for i := 0; i < total; i++ { + serviceReq, _ := createCommonService(t, i+10) + defer cleanServiceName(serviceReq.GetName().GetValue(), serviceReq.GetNamespace().GetValue()) + } + + filters := map[string]string{"offset": "0", "limit": "600"} + resp := server.GetServices(filters) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.Info.GetValue()) + } + if resp.GetSize().GetValue() == naming.QueryMaxLimit { + t.Logf("pass") + } else { + t.Fatalf("error: %d", resp.GetSize().GetValue()) + } + }) + t.Run("查询服务列表,offset参数不为int,返回错误", func(t *testing.T) { + filters := map[string]string{"offset": "abc", "limit": "200"} + resp := server.GetServices(filters) + if !respSuccess(resp) { + t.Logf("pass: %s", resp.Info.GetValue()) + } else { + t.Fatalf("error") + } + }) + t.Run("查询服务列表,limit参数不为int,返回错误", func(t *testing.T) { + filters := map[string]string{"offset": "0", "limit": "ss"} + resp := server.GetServices(filters) + if !respSuccess(resp) { + t.Logf("pass: %s", resp.Info.GetValue()) + } else { + t.Fatalf("error") + } + }) + t.Run("查询服务列表,offset参数为负数,返回错误", func(t *testing.T) { + filters := map[string]string{"offset": "-100", "limit": "10"} + resp := server.GetServices(filters) + if !respSuccess(resp) { + t.Logf("pass: %s", resp.Info.GetValue()) + } else { + t.Fatalf("error") + } + }) + t.Run("查询服务列表,limit参数为负数,返回错误", func(t *testing.T) { + filters := map[string]string{"offset": "100", "limit": "-10"} + resp := server.GetServices(filters) + if !respSuccess(resp) { + t.Logf("pass: %s", resp.Info.GetValue()) + } else { + t.Fatalf("error") + } + }) + t.Run("查询服务列表,单独提供port参数,返回错误", func(t *testing.T) { + filters := map[string]string{"port": "100"} + resp := server.GetServices(filters) + if !respSuccess(resp) { + t.Logf("pass: %s", resp.Info.GetValue()) + } else { + t.Fatalf("error") + } + }) + t.Run("查询服务列表,port参数有误,返回错误", func(t *testing.T) { + filters := map[string]string{"port": "p100", "host":"127.0.0.1"} + resp := server.GetServices(filters) + if !respSuccess(resp) { + t.Logf("pass: %s", resp.Info.GetValue()) + } else { + t.Fatalf("error") + } + }) +} + +// 有基础的过滤条件的查询服务列表 +func TestGetService3(t *testing.T) { + t.Run("根据服务名,可以正常过滤", func(t *testing.T) { + var reqs []*api.Service + serviceReq, _ := createCommonService(t, 100) + reqs = append(reqs, serviceReq) + defer cleanServiceName(serviceReq.GetName().GetValue(), serviceReq.GetNamespace().GetValue()) + + namespaceReq, _ := createCommonNamespace(t, 100) + defer cleanNamespace(namespaceReq.GetName().GetValue()) + + serviceReq.Namespace = utils.NewStringValue(namespaceReq.GetName().GetValue()) + if resp := server.CreateService(defaultCtx, serviceReq); !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + reqs = append(reqs, serviceReq) + defer cleanServiceName(serviceReq.GetName().GetValue(), serviceReq.GetNamespace().GetValue()) + + name := serviceReq.GetName().GetValue() + filters := map[string]string{"offset": "0", "limit": "10", "name": name} + resp := server.GetServices(filters) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + + CheckGetService(t, reqs, resp.GetServices()) + t.Logf("pass") + }) + + t.Run("多重过滤条件,可以生效", func(t *testing.T) { + total := 10 + var name, namespace string + for i := 0; i < total; i++ { + serviceReq, _ := createCommonService(t, 100) + defer cleanServiceName(serviceReq.GetName().GetValue(), serviceReq.GetNamespace().GetValue()) + if i == 5 { + name = serviceReq.GetName().GetValue() + namespace = serviceReq.GetNamespace().GetValue() + } + } + filters := map[string]string{"offset": "0", "limit": "10", "name": name, "namespace": namespace} + resp := server.GetServices(filters) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if len(resp.Services) != 1 { + t.Fatalf("error: %d", len(resp.Services)) + } + }) + + t.Run("owner过滤条件会生效", func(t *testing.T) { + total := 60 + for i := 0; i < total; i++ { + serviceReq, _ := createCommonService(t, i+10) + defer cleanServiceName(serviceReq.GetName().GetValue(), serviceReq.GetNamespace().GetValue()) + } + + filters := map[string]string{"offset": "0", "limit": "100", "owner": "service-owner-10"} + resp := server.GetServices(filters) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if len(resp.Services) != 1 { + t.Fatalf("error: %d", len(resp.Services)) + } + }) +} + +// 异常场景 +func TestGetServices4(t *testing.T) { + t.Run("查询服务列表,新建一批服务,删除部分,再查询,可以过滤掉删除的", func(t *testing.T) { + total := 50 + for i := 0; i < total; i++ { + serviceReq, serviceResp := createCommonService(t, i+5) + defer cleanServiceName(serviceReq.GetName().GetValue(), serviceReq.GetNamespace().GetValue()) + if i%2 == 0 { + removeCommonServices(t, []*api.Service{serviceResp}) + } + } + + query := map[string]string{ + "offset": "0", + "limit": "100", + "name": "test-service-*", + } + resp := server.GetServices(query) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.Info.GetValue()) + } + if resp.GetSize().GetValue() == uint32(total/2) { + t.Logf("pass") + } else { + t.Fatalf("error: %d", resp.GetSize().GetValue()) + } + }) + // 新建几个服务,不同metadata + t.Run("根据metadata可以过滤services", func(t *testing.T) { + service1 := genMainService(1) + service1.Metadata = map[string]string{ + "key1": "value1", + "key2": "value2", + "key3": "value3", + } + service2 := genMainService(2) + service2.Metadata = map[string]string{ + "key2": "value2", + "key3": "value3", + } + service3 := genMainService(3) + service3.Metadata = map[string]string{"key3": "value3"} + if resp := server.CreateServices(defaultCtx, []*api.Service{service1, service2, service3}); !respSuccess(resp) { + t.Fatalf("error: %+v", resp) + } + defer cleanServiceName(service1.GetName().GetValue(), service1.GetNamespace().GetValue()) + defer cleanServiceName(service2.GetName().GetValue(), service2.GetNamespace().GetValue()) + defer cleanServiceName(service3.GetName().GetValue(), service3.GetNamespace().GetValue()) + + resps := server.GetServices(map[string]string{"keys": "key3", "values": "value3"}) + if len(resps.GetServices()) != 3 && resps.GetAmount().GetValue() != 3 { + t.Fatalf("error: %d", len(resps.GetServices())) + } + resps = server.GetServices(map[string]string{"keys": "key2", "values": "value2"}) + if len(resps.GetServices()) != 2 && resps.GetAmount().GetValue() != 2 { + t.Fatalf("error: %d", len(resps.GetServices())) + } + resps = server.GetServices(map[string]string{"keys": "key1", "values": "value1"}) + if len(resps.GetServices()) != 1 && resps.GetAmount().GetValue() != 1 { + t.Fatalf("error: %d", len(resps.GetServices())) + } + resps = server.GetServices(map[string]string{"keys": "key1", "values": "value2"}) + if len(resps.GetServices()) != 0 && resps.GetAmount().GetValue() != 0 { + t.Fatalf("error: %d", len(resps.GetServices())) + } + }) +} + +// 联合查询场景 +func TestGetServices5(t *testing.T) { + getServiceCheck := func(resp *api.BatchQueryResponse, amount, size uint32) { + t.Logf("gocheck resp: %v", resp) + So(respSuccess(resp), ShouldEqual, true) + So(resp.GetAmount().GetValue(), ShouldEqual, amount) + So(resp.GetSize().GetValue(), ShouldEqual, size) + } + Convey("支持host查询到服务", t, func() { + _, serviceResp := createCommonService(t, 200) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + instanceReq, instanceResp := createCommonInstance(t, serviceResp, 100) + defer cleanInstance(instanceResp.GetId().GetValue()) + instanceReq, instanceResp = createCommonInstance(t, serviceResp, 101) + defer cleanInstance(instanceResp.GetId().GetValue()) + query := map[string]string{ + "owner": "service-owner-200", + "host": instanceReq.GetHost().GetValue(), + } + Convey("check-1", func() { getServiceCheck(server.GetServices(query), 1, 1) }) + + // 同host的实例,对应一个服务,那么返回值也是一个 + instanceReq.Port.Value = 999 + resp := server.CreateInstance(defaultCtx, instanceReq) + So(respSuccess(resp), ShouldEqual, true) + defer cleanInstance(resp.Instance.GetId().GetValue()) + Convey("check-2", func() { getServiceCheck(server.GetServices(query), 1, 1) }) + }) + Convey("支持host和port配合查询服务", t, func() { + host1 := "127.0.0.1" + port1 := uint32(8081) + host2 := "127.0.0.2" + port2 := uint32(8082) + _, serviceResp1 := createCommonService(t, 200) + defer cleanServiceName(serviceResp1.GetName().GetValue(), serviceResp1.GetNamespace().GetValue()) + _, instanceResp1 := addHostPortInstance(t, serviceResp1, host1, port1) + defer cleanInstance(instanceResp1.GetId().GetValue()) + _, serviceResp2 := createCommonService(t, 300) + defer cleanServiceName(serviceResp2.GetName().GetValue(), serviceResp2.GetNamespace().GetValue()) + _, instanceResp2 := addHostPortInstance(t, serviceResp2, host1, port2) + defer cleanInstance(instanceResp2.GetId().GetValue()) + _, serviceResp3 := createCommonService(t, 400) + defer cleanServiceName(serviceResp3.GetName().GetValue(), serviceResp3.GetNamespace().GetValue()) + _, instanceResp3 := addHostPortInstance(t, serviceResp3, host2, port1) + defer cleanInstance(instanceResp3.GetId().GetValue()) + _, serviceResp4 := createCommonService(t, 500) + defer cleanServiceName(serviceResp4.GetName().GetValue(), serviceResp4.GetNamespace().GetValue()) + _, instanceResp4 := addHostPortInstance(t, serviceResp4, host2, port2) + defer cleanInstance(instanceResp4.GetId().GetValue()) + + query := map[string]string { + "host": host1, + "port": strconv.Itoa(int(port1)), + } + Convey("check-1-1", func() {getServiceCheck(server.GetServices(query), 1, 1)}) + query["host"] = host1+","+host2 + Convey("check-2-1", func() {getServiceCheck(server.GetServices(query), 2, 2)}) + query["port"] = fmt.Sprintf("%d,%d", port1, port2) + Convey("check-2-2", func() {getServiceCheck(server.GetServices(query), 4, 4)}) + }) + Convey("多个服务,对应同个host,返回多个服务", t, func() { + count := 10 + var instance *api.Instance + for i := 0; i < count; i++ { + _, serviceResp := createCommonService(t, i) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + _, instanceResp := createCommonInstance(t, serviceResp, 100) + defer cleanInstance(instanceResp.GetId().GetValue()) + instance = instanceResp + _, instanceResp = createCommonInstance(t, serviceResp, 202) + defer cleanInstance(instanceResp.GetId().GetValue()) + } + query := map[string]string{ + "host": instance.GetHost().GetValue(), + "limit": "5", + } + Convey("check-1", func() { getServiceCheck(server.GetServices(query), uint32(count), 5) }) + }) +} + +// 测试更新服务 +func TestUpdateService(t *testing.T) { + _, serviceResp := createCommonService(t, 200) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + t.Run("正常更新服务,所有属性都生效", func(t *testing.T) { + updateReq := &api.Service{ + Name: serviceResp.Name, + Namespace: serviceResp.Namespace, + Metadata: map[string]string{ + "new-key": "1", + "new-key-2": "2", + "new-key-3": "3", + }, + Ports: utils.NewStringValue("new-ports"), + Business: utils.NewStringValue("new-business"), + Department: utils.NewStringValue("new-business"), + CmdbMod1: utils.NewStringValue("new-cmdb-mod1"), + CmdbMod2: utils.NewStringValue("new-cmdb-mo2"), + CmdbMod3: utils.NewStringValue("new-cmdb-mod3"), + Comment: utils.NewStringValue("new-comment"), + Owners: utils.NewStringValue("new-owner"), + Token: serviceResp.Token, + } + resp := server.UpdateService(defaultCtx, updateReq) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + + // get service + query := map[string]string{ + "name": updateReq.GetName().GetValue(), + "namespace": updateReq.GetNamespace().GetValue(), + } + services := server.GetServices(query) + if !respSuccess(services) { + t.Fatalf("error: %s", services.GetInfo().GetValue()) + } + if services.GetSize().GetValue() != 1 { + t.Fatalf("error: %d", services.GetSize().GetValue()) + } + + serviceCheck(t, updateReq, services.GetServices()[0]) + }) + t.Run("更新服务,metadata数据个数太多,报错", func(t *testing.T) { + serviceResp.Metadata = make(map[string]string) + for i := 0; i < naming.MaxMetadataLength+1; i++ { + serviceResp.Metadata[fmt.Sprintf("update-%d", i)] = "abc" + } + if resp := server.UpdateService(defaultCtx, serviceResp); !respSuccess(resp) { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error") + } + }) + t.Run("更新服务,metadata为空,长度为0,则删除所有metadata", func(t *testing.T) { + serviceResp.Metadata = make(map[string]string) + if resp := server.UpdateService(defaultCtx, serviceResp); !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + getResp := server.GetServices(map[string]string{"name": serviceResp.Name.Value}) + if !respSuccess(getResp) { + t.Fatalf("error: %s", getResp.GetInfo().GetValue()) + } + if len(getResp.Services[0].Metadata) != 0 { + t.Fatalf("error: %d", len(getResp.Services[0].Metadata)) + } + }) + t.Run("更新服务,不允许更新别名", func(t *testing.T) { + aliasResp := createCommonAlias(serviceResp, "update.service.alias.xxx", api.AliasType_DEFAULT) + defer cleanServiceName(aliasResp.Alias.Alias.Value, serviceResp.Namespace.Value) + + aliasService := &api.Service{ + Name: aliasResp.Alias.Alias, + Namespace: serviceResp.Namespace, + Department: utils.NewStringValue("123"), + Token: serviceResp.Token, + } + if resp := server.UpdateService(defaultCtx, aliasService); respSuccess(resp) { + t.Fatalf("error: update alias success") + } else { + t.Logf("update alias return: %s", resp.GetInfo().GetValue()) + } + }) +} + +// 服务更新,noChange测试 +func TestNoNeedUpdateService(t *testing.T) { + _, serviceResp := createCommonService(t, 500) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + t.Run("数据没有任意变更,返回不需要变更", func(t *testing.T) { + resp := server.UpdateService(defaultCtx, serviceResp) + if resp.GetCode().GetValue() != api.NoNeedUpdate { + t.Fatalf("error: %+v", resp) + } + }) + req := &api.Service{ + Name: serviceResp.Name, + Namespace: serviceResp.Namespace, + Token: serviceResp.Token, + } + t.Run("metadata为空,不需要变更", func(t *testing.T) { + req.Metadata = nil + if resp := server.UpdateService(defaultCtx, req); resp.GetCode().GetValue() != api.NoNeedUpdate { + t.Fatalf("error: %+v", resp) + } + req.Comment = serviceResp.Comment + if resp := server.UpdateService(defaultCtx, req); resp.GetCode().GetValue() != api.NoNeedUpdate { + t.Fatalf("error: %+v", resp) + } + }) + t.Run("metadata不为空,但是没变更,也不需要更新", func(t *testing.T) { + req.Metadata = serviceResp.Metadata + if resp := server.UpdateService(defaultCtx, req); resp.GetCode().GetValue() != api.NoNeedUpdate { + t.Fatalf("error: %+v", resp) + } + }) + t.Run("其他字段更新,metadata没有更新,不需要更新metadata", func(t *testing.T) { + req.Metadata = serviceResp.Metadata + req.Comment = utils.NewStringValue("1357986420") + if resp := server.UpdateService(defaultCtx, req); resp.GetCode().GetValue() != api.ExecuteSuccess { + t.Fatalf("error: %+v", resp) + } + }) + t.Run("只有一个字段变更,service就执行变更操作", func(t *testing.T) { + baseReq := api.Service{ + Name: serviceResp.Name, + Namespace: serviceResp.Namespace, + Token: serviceResp.Token, + } + + r := baseReq + r.Ports = utils.NewStringValue("90909090") + if resp := server.UpdateService(defaultCtx, &r); resp.GetCode().GetValue() != api.ExecuteSuccess { + t.Fatalf("error: %+v", resp) + } + + r = baseReq + r.Business = utils.NewStringValue("new-business") + if resp := server.UpdateService(defaultCtx, &r); resp.GetCode().GetValue() != api.ExecuteSuccess { + t.Fatalf("error: %+v", resp) + } + + r = baseReq + r.Department = utils.NewStringValue("new-department-1") + if resp := server.UpdateService(defaultCtx, &r); resp.GetCode().GetValue() != api.ExecuteSuccess { + t.Fatalf("error: %+v", resp) + } + + r = baseReq + r.CmdbMod1 = utils.NewStringValue("new-CmdbMod1-1") + if resp := server.UpdateService(defaultCtx, &r); resp.GetCode().GetValue() != api.ExecuteSuccess { + t.Fatalf("error: %+v", resp) + } + + r = baseReq + r.CmdbMod2 = utils.NewStringValue("new-CmdbMod2-1") + if resp := server.UpdateService(defaultCtx, &r); resp.GetCode().GetValue() != api.ExecuteSuccess { + t.Fatalf("error: %+v", resp) + } + + r = baseReq + r.CmdbMod3 = utils.NewStringValue("new-CmdbMod3-1") + if resp := server.UpdateService(defaultCtx, &r); resp.GetCode().GetValue() != api.ExecuteSuccess { + t.Fatalf("error: %+v", resp) + } + + r = baseReq + r.Comment = utils.NewStringValue("new-Comment-1") + if resp := server.UpdateService(defaultCtx, &r); resp.GetCode().GetValue() != api.ExecuteSuccess { + t.Fatalf("error: %+v", resp) + } + + r = baseReq + r.Owners = utils.NewStringValue("new-Owners-1") + if resp := server.UpdateService(defaultCtx, &r); resp.GetCode().GetValue() != api.ExecuteSuccess { + t.Fatalf("error: %+v", resp) + } + }) +} + +// 测试serviceToken相关的操作 +func TestServiceToken(t *testing.T) { + _, serviceResp := createCommonService(t, 200) + defer cleanServiceName(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + t.Run("可以正常获取serviceToken", func(t *testing.T) { + req := &api.Service{ + Name: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + Token: serviceResp.GetToken(), + } + + resp := server.GetServiceToken(defaultCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + if resp.GetService().GetToken().GetValue() != serviceResp.GetToken().GetValue() { + t.Fatalf("error") + } + }) + + t.Run("获取别名的token,返回源服务的token", func(t *testing.T) { + aliasResp := createCommonAlias(serviceResp, "get.token.xxx", api.AliasType_DEFAULT) + defer cleanServiceName(aliasResp.Alias.Alias.Value, serviceResp.Namespace.Value) + t.Logf("%+v", aliasResp) + + req := &api.Service{ + Name: aliasResp.Alias.Alias, + Namespace: serviceResp.GetNamespace(), + Token: serviceResp.GetToken(), + } + t.Logf("%+v", req) + if resp := server.GetServiceToken(defaultCtx, req); !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } else if resp.GetService().GetToken().GetValue() != serviceResp.GetToken().GetValue() { + t.Fatalf("error") + } + }) + + t.Run("可以正常更新serviceToken", func(t *testing.T) { + resp := server.UpdateServiceToken(defaultCtx, serviceResp) + if !respSuccess(resp) { + t.Fatalf("error :%s", resp.GetInfo().GetValue()) + } + if resp.GetService().GetToken().GetValue() == serviceResp.GetToken().GetValue() { + t.Fatalf("error: %s %s", resp.GetService().GetToken().GetValue(), + serviceResp.GetToken().GetValue()) + } + serviceResp.Token.Value = resp.Service.Token.Value // set token + }) + + t.Run("alias不允许更新token", func(t *testing.T) { + aliasResp := createCommonAlias(serviceResp, "update.token.xxx", api.AliasType_DEFAULT) + defer cleanServiceName(aliasResp.Alias.Alias.Value, serviceResp.Namespace.Value) + + req := &api.Service{ + Name: aliasResp.Alias.Alias, + Namespace: serviceResp.Namespace, + Token: serviceResp.Token, + } + if resp := server.UpdateServiceToken(defaultCtx, req); respSuccess(resp) { + t.Fatalf("error") + } + }) +} + +// 测试response格式化 +func TestFormatBatchWriteResponse(t *testing.T) { + t.Run("同样的错误码,返回一个错误码4XX", func(t *testing.T) { + responses := api.NewBatchWriteResponse(api.ExecuteSuccess) + for i := 0; i < 10; i++ { + responses.Collect(api.NewResponse(api.NotFoundService)) + } + + responses = api.FormatBatchWriteResponse(responses) + if responses.GetCode().GetValue() != api.NotFoundService { + t.Fatalf("%+v", responses) + } + }) + t.Run("同样的错误码,返回一个错误码5XX", func(t *testing.T) { + responses := api.NewBatchWriteResponse(api.ExecuteSuccess) + for i := 0; i < 10; i++ { + responses.Collect(api.NewResponse(api.StoreLayerException)) + } + + responses = api.FormatBatchWriteResponse(responses) + if responses.GetCode().GetValue() != api.StoreLayerException { + t.Fatalf("%+v", responses) + } + }) + t.Run("有5XX和2XX,返回5XX", func(t *testing.T) { + responses := api.NewBatchWriteResponse(api.ExecuteSuccess) + responses.Collect(api.NewResponse(api.ExecuteSuccess)) + responses.Collect(api.NewResponse(api.NotFoundNamespace)) + responses.Collect(api.NewResponse(api.ParseRateLimitException)) + responses.Collect(api.NewResponse(api.ParseException)) + responses = api.FormatBatchWriteResponse(responses) + if responses.GetCode().GetValue() != api.ExecuteException { + t.Fatalf("%+v", responses) + } + }) + t.Run("没有5XX,有4XX,返回4XX", func(t *testing.T) { + responses := api.NewBatchWriteResponse(api.ExecuteSuccess) + responses.Collect(api.NewResponse(api.ExecuteSuccess)) + responses.Collect(api.NewResponse(api.NotFoundNamespace)) + responses.Collect(api.NewResponse(api.NoNeedUpdate)) + responses.Collect(api.NewResponse(api.InvalidInstanceID)) + responses.Collect(api.NewResponse(api.ExecuteSuccess)) + responses = api.FormatBatchWriteResponse(responses) + if responses.GetCode().GetValue() != api.BadRequest { + t.Fatalf("%+v", responses) + } + }) + t.Run("全是2XX", func(t *testing.T) { + responses := api.NewBatchWriteResponse(api.ExecuteSuccess) + responses.Collect(api.NewResponse(api.ExecuteSuccess)) + responses.Collect(api.NewResponse(api.NoNeedUpdate)) + responses.Collect(api.NewResponse(api.DataNoChange)) + responses.Collect(api.NewResponse(api.NoNeedUpdate)) + responses.Collect(api.NewResponse(api.ExecuteSuccess)) + responses = api.FormatBatchWriteResponse(responses) + if responses.GetCode().GetValue() != api.ExecuteSuccess { + t.Fatalf("%+v", responses) + } + }) +} + +// test对service字段进行校验 +func TestCheckServiceFieldLen(t *testing.T) { + service := genMainService(400) + t.Run("服务名超长", func(t *testing.T) { + str := genSpecialStr(129) + oldName := service.Name + service.Name = utils.NewStringValue(str) + resp := server.CreateService(defaultCtx, service) + service.Name = oldName + if resp.Code.Value != api.InvalidServiceName { + t.Fatalf("%+v", resp) + } + }) + t.Run("命名空间超长", func(t *testing.T) { + str := genSpecialStr(129) + oldNameSpace := service.Namespace + service.Namespace = utils.NewStringValue(str) + resp := server.CreateService(defaultCtx, service) + service.Namespace = oldNameSpace + if resp.Code.Value != api.InvalidNamespaceName { + t.Fatalf("%+v", resp) + } + }) + t.Run("Metadata超长", func(t *testing.T) { + str := genSpecialStr(129) + oldMetadata := service.Metadata + oldMetadata[str] = str + resp := server.CreateService(defaultCtx, service) + service.Metadata = make(map[string]string) + if resp.Code.Value != api.InvalidMetadata { + t.Fatalf("%+v", resp) + } + }) + t.Run("服务ports超长", func(t *testing.T) { + str := genSpecialStr(8193) + oldPort := service.Ports + service.Ports = utils.NewStringValue(str) + resp := server.CreateService(defaultCtx, service) + service.Ports = oldPort + if resp.Code.Value != api.InvalidServicePorts { + t.Fatalf("%+v", resp) + } + }) + t.Run("服务Business超长", func(t *testing.T) { + str := genSpecialStr(129) + oldBusiness := service.Business + service.Business = utils.NewStringValue(str) + resp := server.CreateService(defaultCtx, service) + service.Business = oldBusiness + if resp.Code.Value != api.InvalidServiceBusiness { + t.Fatalf("%+v", resp) + } + }) + t.Run("服务-部门超长", func(t *testing.T) { + str := genSpecialStr(1025) + oldDepartment := service.Department + service.Department = utils.NewStringValue(str) + resp := server.CreateService(defaultCtx, service) + service.Department = oldDepartment + if resp.Code.Value != api.InvalidServiceDepartment { + t.Fatalf("%+v", resp) + } + }) + t.Run("服务cmdb超长", func(t *testing.T) { + str := genSpecialStr(1025) + oldCMDB := service.CmdbMod1 + service.CmdbMod1 = utils.NewStringValue(str) + resp := server.CreateService(defaultCtx, service) + service.CmdbMod1 = oldCMDB + if resp.Code.Value != api.InvalidServiceCMDB { + t.Fatalf("%+v", resp) + } + }) + t.Run("服务comment超长", func(t *testing.T) { + str := genSpecialStr(1025) + oldComment := service.Comment + service.Comment = utils.NewStringValue(str) + resp := server.CreateService(defaultCtx, service) + service.Comment = oldComment + if resp.Code.Value != api.InvalidServiceComment { + t.Fatalf("%+v", resp) + } + }) + t.Run("服务owner超长", func(t *testing.T) { + str := genSpecialStr(1025) + oldOwner := service.Owners + service.Owners = utils.NewStringValue(str) + resp := server.CreateService(defaultCtx, service) + service.Owners = oldOwner + if resp.Code.Value != api.InvalidServiceOwners { + t.Fatalf("%+v", resp) + } + }) + t.Run("服务token超长", func(t *testing.T) { + str := genSpecialStr(2049) + oldToken := service.Token + service.Token = utils.NewStringValue(str) + resp := server.CreateService(defaultCtx, service) + service.Token = oldToken + if resp.Code.Value != api.InvalidServiceToken { + t.Fatalf("%+v", resp) + } + }) + t.Run("检测字段为空指针", func(t *testing.T) { + oldName := service.Name + service.Name = nil + resp := server.CreateService(defaultCtx, service) + service.Name = oldName + if resp.Code.Value != api.InvalidServiceName { + t.Fatalf("%+v", resp) + } + }) + t.Run("检测字段为空", func(t *testing.T) { + oldName := service.Name + service.Name = utils.NewStringValue("") + resp := server.CreateService(defaultCtx, service) + service.Name = oldName + if resp.Code.Value != api.InvalidServiceName { + t.Fatalf("%+v", resp) + } + }) +} diff --git a/naming/test/test.yaml b/naming/test/test.yaml new file mode 100644 index 000000000..91f68d483 --- /dev/null +++ b/naming/test/test.yaml @@ -0,0 +1,83 @@ +bootstrap: + logger: + level: debug +naming: + batch: + register: + open: true + queueSize: 1024 + waitTime: 32ms + maxBatchCount: 32 + concurrency: 16 + deregister: + open: true + queueSize: 1024 + waitTime: 32ms + maxBatchCount: 32 + concurrency: 16 + healthcheck: + open: true + kvConnNum: 50 + kvServiceName: polarisHeartbeatCkv + kvNamespace: Polaris + kvPasswd: polaris@2021 + slotNum: 30 +cache: + open: true + resources: + - name: service # 加载服务数据 + option: + disableBusiness: false # 不加载业务服务 + needMeta: true # 加载服务元数据 + - name: instance # 加载实例数据 + option: + disableBusiness: false # 不加载业务服务实例 + needMeta: true # 加载实例元数据 + - name: routingConfig # 加载路由数据 + - name: rateLimitConfig # 加载限流数据 + - name: circuitBreakerConfig # 加载熔断数据 + - name: meshConfig # 加载网格数据 + - name: l5 # 加载l5数据 + - name: mesh + - name: meshService +store: + name: defaultStore + option: + master: + dbType: mysql + dbUser: polaris + dbPwd: polaris + dbAddr: 9.134.38.82:3306 + dbName: polaris_server_for_test + maxOpenConns: 128 + maxIdleConns: 16 + connMaxLifetime: 60 + txIsolationLevel: 2 + slave: + dbType: mysql + dbUser: polaris + dbPwd: polaris + dbAddr: 9.134.38.82:3306 + dbName: polaris_server_for_test + maxOpenConns: 128 + maxIdleConns: 16 + connMaxLifetime: 60 + txIsolationLevel: 2 +plugin: + history: + name: HistoryLogger + meshResourceValidate: + name: grpc + option: + agentIP: 127.0.0.1 + agentPort: 6789 + ratelimit: + name: token-bucket + option: + remote-conf: false + instance-limit: + open: true + global: + bucket: 2 + rate: 2 + resource-cache-amount: 8 diff --git a/naming/test/testdata/mesh/invalid-virtualService.yaml b/naming/test/testdata/mesh/invalid-virtualService.yaml new file mode 100644 index 000000000..27859a71a --- /dev/null +++ b/naming/test/testdata/mesh/invalid-virtualService.yaml @@ -0,0 +1,25 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: reviews-route +spec: hosts: +- reviews.prod.svc.cluster.local + http: + - name: "reviews-v2-routes" + match: + - uri: + prefix: "/wpcatalog" + - uri: + prefix: "/consumercatalog" + rewrite: + uri: "/newcatalog" + route: + - destination: + host: reviews.prod.svc.cluster.local + subset: v2 + - name: "reviews-v1-route" + route: + - destination: + host: reviews.prod.svc.cluster.local + subset: v1 + diff --git a/naming/test/testdata/mesh/normal-gateway.yaml b/naming/test/testdata/mesh/normal-gateway.yaml new file mode 100644 index 000000000..4bf306706 --- /dev/null +++ b/naming/test/testdata/mesh/normal-gateway.yaml @@ -0,0 +1,51 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: Gateway +metadata: + name: my-gateway + namespace: some-config-namespace +spec: + selector: + app: my-gateway-controller + servers: + - port: + number: 80 + name: http + protocol: HTTP + hosts: + - uk.bookinfo.com + - eu.bookinfo.com + tls: + httpsRedirect: true # sends 301 redirect for http requests + - port: + number: 443 + name: https-443 + protocol: HTTPS + hosts: + - uk.bookinfo.com + - eu.bookinfo.com + tls: + mode: SIMPLE # enables HTTPS on this port + serverCertificate: /etc/certs/servercert.pem + privateKey: /etc/certs/privatekey.pem + - port: + number: 9443 + name: https-9443 + protocol: HTTPS + hosts: + - "bookinfo-namespace/*.bookinfo.com" + tls: + mode: SIMPLE # enables HTTPS on this port + credentialName: bookinfo-secret # fetches certs from Kubernetes secret + - port: + number: 9080 + name: http-wildcard + protocol: HTTP + hosts: + - "*" + - port: + number: 2379 # to expose internal service via external port 2379 + name: mongo + protocol: MONGO + hosts: + - "*" + diff --git a/naming/test/testdata/mesh/normal-virtualService-nometa.yaml b/naming/test/testdata/mesh/normal-virtualService-nometa.yaml new file mode 100644 index 000000000..1eb1094d3 --- /dev/null +++ b/naming/test/testdata/mesh/normal-virtualService-nometa.yaml @@ -0,0 +1,24 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +spec: + hosts: + - reviews.prod.svc.cluster.local + http: + - name: "reviews-v2-routes" + match: + - uri: + prefix: "/wpcatalog" + - uri: + prefix: "/consumercatalog" + rewrite: + uri: "/newcatalog" + route: + - destination: + host: reviews.prod.svc.cluster.local + subset: v2 + - name: "reviews-v1-route" + route: + - destination: + host: reviews.prod.svc.cluster.local + subset: v1 + diff --git a/naming/test/testdata/mesh/normal-virtualService.yaml b/naming/test/testdata/mesh/normal-virtualService.yaml new file mode 100644 index 000000000..513800b78 --- /dev/null +++ b/naming/test/testdata/mesh/normal-virtualService.yaml @@ -0,0 +1,26 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: reviews-route +spec: + hosts: + - reviews.prod.svc.cluster.local + http: + - name: "reviews-v2-routes" + match: + - uri: + prefix: "/wpcatalog" + - uri: + prefix: "/consumercatalog" + rewrite: + uri: "/newcatalog" + route: + - destination: + host: reviews.prod.svc.cluster.local + subset: v2 + - name: "reviews-v1-route" + route: + - destination: + host: reviews.prod.svc.cluster.local + subset: v1 + diff --git a/naming/test/testdata/mesh/update-virtualService.yaml b/naming/test/testdata/mesh/update-virtualService.yaml new file mode 100644 index 000000000..4ee6f9b1c --- /dev/null +++ b/naming/test/testdata/mesh/update-virtualService.yaml @@ -0,0 +1,26 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: reviews-route +spec: + hosts: + - reviews.prod.svc.cluster.global + http: + - name: "reviews-v3-routes" + match: + - uri: + prefix: "/wpcatalog" + - uri: + prefix: "/consumercatalog" + rewrite: + uri: "/newcatalog" + route: + - destination: + host: reviews.prod.svc.cluster.local + subset: v2 + - name: "reviews-v1-route" + route: + - destination: + host: reviews.prod.svc.cluster.local + subset: v1 + diff --git a/naming/testAuthPlugin/circuitbreaker_config_test.go b/naming/testAuthPlugin/circuitbreaker_config_test.go new file mode 100644 index 000000000..a6c03a908 --- /dev/null +++ b/naming/testAuthPlugin/circuitbreaker_config_test.go @@ -0,0 +1,412 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package testAuthPlugin + +import ( + "context" + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/utils" + "github.com/polarismesh/polaris-server/naming" + "testing" +) + +/** + * @brief 测试使用平台Token绑定/解绑熔断规则 + */ +func TestCircuitBreakerAuthByPlatform(t *testing.T) { + ctx := context.Background() + ctx = context.WithValue(ctx, utils.StringContext("platform-id"), platformID) + ctx = context.WithValue(ctx, utils.StringContext("platform-token"), platformToken) + + // 创建熔断规则 + circuitBreaker := createCircuitBreaker(t, 1) + defer cleanCircuitBreaker(circuitBreaker.GetId().GetValue(), circuitBreaker.GetVersion().GetValue()) + + // 创建熔断规则版本 + resp := createCircuitBreakerVersion(t, circuitBreaker) + defer cleanCircuitBreaker(resp.GetId().GetValue(), resp.GetVersion().GetValue()) + + // 创建服务 + serviceResp := createService(t, 334) + defer cleanService(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + t.Run("使用平台Token发布熔断规则,有权限", func(t *testing.T) { + releaseCircuitBreaker(t, circuitBreaker, serviceResp, ctx) + defer cleanCircuitBreakerRelation(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue(), + circuitBreaker.GetId().GetValue(), circuitBreaker.GetVersion().GetValue()) + + t.Log("pass") + }) + + t.Run("使用平台Token解绑熔断规则, 有权限", func(t *testing.T) { + releaseCircuitBreaker(t, circuitBreaker, serviceResp, ctx) + defer cleanCircuitBreakerRelation(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue(), + circuitBreaker.GetId().GetValue(), circuitBreaker.GetVersion().GetValue()) + + unbind := &api.ConfigRelease{ + Service: &api.Service{ + Name: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + }, + CircuitBreaker: circuitBreaker, + } + + resp := server.UnBindCircuitBreaker(ctx, unbind) + if !respSuccess(resp) { + t.Fatalf("error: %+v", resp) + } + t.Log("pass") + }) +} + +/** + * @brief 测试使用服务Token绑定熔断规则 + */ +func TestReleaseCircuitBreakerAuthByService(t *testing.T) { + ctx := context.Background() + ctx = context.WithValue(ctx, utils.StringContext("platform-id"), platformID) + ctx = context.WithValue(ctx, utils.StringContext("platform-token"), "test") + + correctCtx := context.Background() + correctCtx = context.WithValue(correctCtx, utils.StringContext("platform-id"), platformID) + correctCtx = context.WithValue(correctCtx, utils.StringContext("platform-token"), platformToken) + + // 创建熔断规则 + circuitBreaker := createCircuitBreaker(t, 2) + defer cleanCircuitBreaker(circuitBreaker.GetId().GetValue(), circuitBreaker.GetVersion().GetValue()) + + // 创建熔断规则版本 + resp := createCircuitBreakerVersion(t, circuitBreaker) + defer cleanCircuitBreaker(resp.GetId().GetValue(), resp.GetVersion().GetValue()) + + // 创建服务 + serviceResp := createService(t, 134) + defer cleanService(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + t.Run("平台信息为空,使用服务Token发布熔断规则,有权限", func(t *testing.T) { + req := &api.ConfigRelease{ + Service: serviceResp, + CircuitBreaker: circuitBreaker, + } + + resp := server.ReleaseCircuitBreaker(defaultCtx, req) + defer cleanCircuitBreakerRelation(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue(), + circuitBreaker.GetId().GetValue(), circuitBreaker.GetVersion().GetValue()) + + if !respSuccess(resp) { + t.Fatalf("error: %+v", resp) + } + t.Log("pass") + }) + + t.Run("平台信息不正确,使用服务Token发布熔断规则,有权限", func(t *testing.T) { + req := &api.ConfigRelease{ + Service: serviceResp, + CircuitBreaker: circuitBreaker, + } + + resp := server.ReleaseCircuitBreaker(ctx, req) + defer cleanCircuitBreakerRelation(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue(), + circuitBreaker.GetId().GetValue(), circuitBreaker.GetVersion().GetValue()) + + if !respSuccess(resp) { + t.Fatalf("error: %+v", resp) + } + t.Log("pass") + }) + + t.Run("平台信息不正确,使用系统Token发布熔断规则,有权限", func(t *testing.T) { + req := &api.ConfigRelease{ + Service: &api.Service{ + Name: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + }, + CircuitBreaker: circuitBreaker, + } + + globalCtx := context.WithValue(ctx, utils.StringContext("polaris-token"), "polaris@12345678") + + resp := server.ReleaseCircuitBreaker(globalCtx, req) + defer cleanCircuitBreakerRelation(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue(), + circuitBreaker.GetId().GetValue(), circuitBreaker.GetVersion().GetValue()) + + if !respSuccess(resp) { + t.Fatalf("error: %+v", resp) + } + t.Log("pass") + }) + + t.Run("无服务Token和平台信息,发布熔断规则,返回错误", func(t *testing.T) { + req := &api.ConfigRelease{ + Service: &api.Service{ + Name: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + }, + CircuitBreaker: circuitBreaker, + } + + resp := server.ReleaseCircuitBreaker(defaultCtx, req) + if resp.GetCode().GetValue() == api.InvalidServiceToken { + t.Log("pass") + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) + + t.Run("服务Token错误,发布熔断规则,返回错误", func(t *testing.T) { + req := &api.ConfigRelease{ + Service: &api.Service{ + Name: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + Token: utils.NewStringValue("test"), + }, + CircuitBreaker: circuitBreaker, + } + + resp := server.ReleaseCircuitBreaker(defaultCtx, req) + if resp.GetCode().GetValue() == api.Unauthorized { + t.Log("pass") + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) +} + +/** + * @brief 测试使用服务Token解绑熔断规则 + */ +func TestUnbindCircuitBreakerAuthByService(t *testing.T) { + ctx := context.Background() + ctx = context.WithValue(ctx, utils.StringContext("platform-id"), platformID) + ctx = context.WithValue(ctx, utils.StringContext("platform-token"), "test") + + correctCtx := context.Background() + correctCtx = context.WithValue(correctCtx, utils.StringContext("platform-id"), platformID) + correctCtx = context.WithValue(correctCtx, utils.StringContext("platform-token"), platformToken) + + // 创建熔断规则 + circuitBreaker := createCircuitBreaker(t, 2) + defer cleanCircuitBreaker(circuitBreaker.GetId().GetValue(), circuitBreaker.GetVersion().GetValue()) + + // 创建熔断规则版本 + resp := createCircuitBreakerVersion(t, circuitBreaker) + defer cleanCircuitBreaker(resp.GetId().GetValue(), resp.GetVersion().GetValue()) + + // 创建服务 + serviceResp := createService(t, 134) + defer cleanService(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + t.Run("平台信息为空,使用服务Token解绑熔断规则,有权限", func(t *testing.T) { + releaseCircuitBreaker(t, circuitBreaker, serviceResp, correctCtx) + defer cleanCircuitBreakerRelation(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue(), + circuitBreaker.GetId().GetValue(), circuitBreaker.GetVersion().GetValue()) + + req := &api.ConfigRelease{ + Service: serviceResp, + CircuitBreaker: circuitBreaker, + } + + resp := server.UnBindCircuitBreaker(defaultCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %+v", resp) + } + t.Log("pass") + }) + + t.Run("平台信息不正确,使用服务Token解绑熔断规则,有权限", func(t *testing.T) { + releaseCircuitBreaker(t, circuitBreaker, serviceResp, correctCtx) + defer cleanCircuitBreakerRelation(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue(), + circuitBreaker.GetId().GetValue(), circuitBreaker.GetVersion().GetValue()) + + req := &api.ConfigRelease{ + Service: serviceResp, + CircuitBreaker: circuitBreaker, + } + + resp := server.UnBindCircuitBreaker(ctx, req) + if !respSuccess(resp) { + t.Fatalf("error: %+v", resp) + } + t.Log("pass") + }) + + t.Run("平台信息不正确,使用系统Token解绑熔断规则,有权限", func(t *testing.T) { + releaseCircuitBreaker(t, circuitBreaker, serviceResp, correctCtx) + defer cleanCircuitBreakerRelation(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue(), + circuitBreaker.GetId().GetValue(), circuitBreaker.GetVersion().GetValue()) + + req := &api.ConfigRelease{ + Service: &api.Service{ + Name: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + }, + CircuitBreaker: circuitBreaker, + } + + globalCtx := context.WithValue(ctx, utils.StringContext("polaris-token"), "polaris@12345678") + + resp := server.UnBindCircuitBreaker(globalCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %+v", resp) + } + t.Log("pass") + }) + + t.Run("无服务Token和平台信息,解绑熔断规则,返回错误", func(t *testing.T) { + releaseCircuitBreaker(t, circuitBreaker, serviceResp, correctCtx) + defer cleanCircuitBreakerRelation(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue(), + circuitBreaker.GetId().GetValue(), circuitBreaker.GetVersion().GetValue()) + + req := &api.ConfigRelease{ + Service: &api.Service{ + Name: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + }, + CircuitBreaker: circuitBreaker, + } + + resp := server.UnBindCircuitBreaker(defaultCtx, req) + if resp.GetCode().GetValue() == api.InvalidServiceToken { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) + + t.Run("服务Token错误,解绑熔断规则,返回错误", func(t *testing.T) { + releaseCircuitBreaker(t, circuitBreaker, serviceResp, correctCtx) + defer cleanCircuitBreakerRelation(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue(), + circuitBreaker.GetId().GetValue(), circuitBreaker.GetVersion().GetValue()) + + req := &api.ConfigRelease{ + Service: &api.Service{ + Name: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + Token: utils.NewStringValue("test"), + }, + CircuitBreaker: circuitBreaker, + } + + resp := server.UnBindCircuitBreaker(defaultCtx, req) + if resp.GetCode().GetValue() == api.Unauthorized { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) +} + +/** + * @brief 创建熔断规则 + */ +func createCircuitBreaker(t *testing.T, id int) *api.CircuitBreaker { + circuitBreaker := &api.CircuitBreaker{ + Name: utils.NewStringValue(fmt.Sprintf("name-test-%d", id)), + Namespace: utils.NewStringValue(naming.DefaultNamespace), + Owners: utils.NewStringValue("owner-test"), + } + ruleNum := 1 + // 填充source规则 + sources := make([]*api.SourceMatcher, 0, ruleNum) + for i := 0; i < ruleNum; i++ { + source := &api.SourceMatcher{ + Service: utils.NewStringValue(fmt.Sprintf("service-test-%d", i)), + Namespace: utils.NewStringValue(fmt.Sprintf("namespace-test-%d", i)), + Labels: map[string]*api.MatchString{ + fmt.Sprintf("name-%d", i): { + Type: api.MatchString_EXACT, + Value: utils.NewStringValue(fmt.Sprintf("value-%d", i)), + }, + }, + } + sources = append(sources, source) + } + + // 填充inbound规则 + inbounds := make([]*api.CbRule, 0, ruleNum) + for i := 0; i < ruleNum; i++ { + inbound := &api.CbRule{ + Sources: sources, + } + inbounds = append(inbounds, inbound) + } + circuitBreaker.Inbounds = inbounds + + resp := server.CreateCircuitBreaker(defaultCtx, circuitBreaker) + if !respSuccess(resp) { + t.Fatalf("error: %+v", resp) + } + + return resp.GetCircuitBreaker() +} + +/** + * @brief 创建熔断规则版本 + */ +func createCircuitBreakerVersion(t *testing.T, circuitBreaker *api.CircuitBreaker) *api.CircuitBreaker { + circuitBreaker.Version = utils.NewStringValue("test-version") + resp := server.CreateCircuitBreakerVersion(defaultCtx, circuitBreaker) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + + return resp.GetCircuitBreaker() +} + +/** + * @brief 发布熔断规则 + */ +func releaseCircuitBreaker(t *testing.T, circuitBreaker *api.CircuitBreaker, service *api.Service, + ctx context.Context) *api.ConfigRelease { + release := &api.ConfigRelease{ + Service: &api.Service{ + Name: service.GetName(), + Namespace: service.GetNamespace(), + }, + CircuitBreaker: circuitBreaker, + } + + resp := server.ReleaseCircuitBreaker(ctx, release) + if !respSuccess(resp) { + t.Fatalf("error: %+v", resp) + } + + return resp.GetConfigRelease() +} + +/** + * @brief 彻底删除熔断规则 + */ +func cleanCircuitBreaker(id, version string) { + str := `delete from circuitbreaker_rule where id = ? and version = ?` + if _, err := db.Exec(str, id, version); err != nil { + panic(err) + } +} + +/** + * @brief 彻底删除熔断规则发布记录 + */ +func cleanCircuitBreakerRelation(name, namespace, ruleID, ruleVersion string) { + str := `delete from circuitbreaker_rule_relation using circuitbreaker_rule_relation, service where + service_id = service.id and name = ? and namespace = ? and rule_id = ? and rule_version = ?` + if _, err := db.Exec(str, name, namespace, ruleID, ruleVersion); err != nil { + panic(err) + } +} diff --git a/naming/testAuthPlugin/instance_test.go b/naming/testAuthPlugin/instance_test.go new file mode 100644 index 000000000..a566fe6fe --- /dev/null +++ b/naming/testAuthPlugin/instance_test.go @@ -0,0 +1,565 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package testAuthPlugin + +import ( + "context" + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/utils" + "sync" + "testing" +) + +/** + * @brief 测试使用平台Token操作实例 + */ +func TestInstanceAuthByPlatform(t *testing.T) { + ctx := context.Background() + ctx = context.WithValue(ctx, utils.StringContext("platform-id"), platformID) + ctx = context.WithValue(ctx, utils.StringContext("platform-token"), platformToken) + + // 创建服务 + serviceResp := createService(t, 2) + defer cleanService(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + t.Run("使用平台Token创建服务实例,有权限", func(t *testing.T) { + resp := createInstance(t, serviceResp, 1, ctx) + defer cleanInstance(resp.GetId().GetValue()) + t.Log("pass") + }) + + t.Run("使用平台Token并发创建服务实例,有权限", func(t *testing.T) { + var wg sync.WaitGroup + for i := 100; i <= 500; i++ { + wg.Add(1) + go func(index int) { + defer wg.Done() + resp := createInstance(t, serviceResp, index, ctx) + cleanInstance(resp.GetId().GetValue()) + }(i) + } + wg.Wait() + }) + + t.Run("使用平台Token修改服务实例,有权限", func(t *testing.T) { + instance := createInstance(t, serviceResp, 2, ctx) + defer cleanInstance(instance.GetId().GetValue()) + + instance.Isolate = utils.NewBoolValue(false) + resp := server.UpdateInstance(ctx, instance) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("使用平台Token删除服务实例,有权限", func(t *testing.T) { + instance := createInstance(t, serviceResp, 3, ctx) + defer cleanInstance(instance.GetId().GetValue()) + + resp := server.DeleteInstance(ctx, instance) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("使用平台Token,根据host修改服务实例,有权限", func(t *testing.T) { + instance := createInstance(t, serviceResp, 4, ctx) + defer cleanInstance(instance.GetId().GetValue()) + + instance.Isolate = utils.NewBoolValue(true) + resp := server.UpdateInstanceIsolate(ctx, instance) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("使用平台Token,根据host删除服务实例,有权限", func(t *testing.T) { + instance := createInstance(t, serviceResp, 5, ctx) + defer cleanInstance(instance.GetId().GetValue()) + + resp := server.DeleteInstanceByHost(ctx, instance) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + +} + +/** + * @brief 测试使用服务Token创建实例 + */ +func TestCreateInstanceAuthByService(t *testing.T) { + ctx := context.Background() + ctx = context.WithValue(ctx, utils.StringContext("platform-id"), platformID) + ctx = context.WithValue(ctx, utils.StringContext("platform-token"), "test") + + correctCtx := context.Background() + correctCtx = context.WithValue(correctCtx, utils.StringContext("platform-id"), platformID) + correctCtx = context.WithValue(correctCtx, utils.StringContext("platform-token"), platformToken) + + // 创建服务 + serviceResp := createService(t, 3) + defer cleanService(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + t.Run("平台信息为空,使用服务Token创建实例,有权限", func(t *testing.T) { + req := &api.Instance{ + Service: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + Host: utils.NewStringValue("test-host"), + Port: utils.NewUInt32Value(11), + ServiceToken: serviceResp.GetToken(), + } + + resp := server.CreateInstance(defaultCtx, req) + defer cleanInstance(resp.GetInstance().GetId().GetValue()) + + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("平台信息不正确,使用服务Token创建实例,有权限", func(t *testing.T) { + req := &api.Instance{ + Service: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + Host: utils.NewStringValue("test-host"), + Port: utils.NewUInt32Value(22), + ServiceToken: serviceResp.GetToken(), + } + + resp := server.CreateInstance(ctx, req) + defer cleanInstance(resp.GetInstance().GetId().GetValue()) + + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("平台信息不正确,使用系统Token创建实例,有权限", func(t *testing.T) { + req := &api.Instance{ + Service: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + Host: utils.NewStringValue("test-host"), + Port: utils.NewUInt32Value(33), + } + globalCtx := context.WithValue(ctx, utils.StringContext("polaris-token"), "polaris@12345678") + + resp := server.CreateInstance(globalCtx, req) + defer cleanInstance(resp.GetInstance().GetId().GetValue()) + + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("无服务Token和平台信息,创建实例,返回错误", func(t *testing.T) { + req := &api.Instance{ + Service: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + Host: utils.NewStringValue("test-host"), + Port: utils.NewUInt32Value(44), + } + + resp := server.CreateInstance(defaultCtx, req) + if resp.GetCode().GetValue() == api.InvalidServiceToken { + t.Log("pass") + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) + + t.Run("服务Token错误,创建实例,返回错误", func(t *testing.T) { + req := &api.Instance{ + Service: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + Host: utils.NewStringValue("test-host"), + Port: utils.NewUInt32Value(55), + ServiceToken: utils.NewStringValue("test"), + } + + resp := server.CreateInstance(defaultCtx, req) + if resp.GetCode().GetValue() == api.Unauthorized { + t.Log("pass") + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) +} + +/** + * @brief 测试使用服务Token修改实例 + */ +func TestUpdateInstanceAuthByService(t *testing.T) { + ctx := context.Background() + ctx = context.WithValue(ctx, utils.StringContext("platform-id"), platformID) + ctx = context.WithValue(ctx, utils.StringContext("platform-token"), "test") + + correctCtx := context.Background() + correctCtx = context.WithValue(correctCtx, utils.StringContext("platform-id"), platformID) + correctCtx = context.WithValue(correctCtx, utils.StringContext("platform-token"), platformToken) + + // 创建服务 + serviceResp := createService(t, 3) + defer cleanService(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + t.Run("平台信息为空,使用服务Token修改实例,有权限", func(t *testing.T) { + req := createInstance(t, serviceResp, 66, correctCtx) + defer cleanInstance(req.GetId().GetValue()) + + req.Isolate = utils.NewBoolValue(true) + req.ServiceToken = serviceResp.GetToken() + resp := server.UpdateInstance(defaultCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("平台信息不正确,使用服务Token修改实例,有权限", func(t *testing.T) { + req := createInstance(t, serviceResp, 77, correctCtx) + defer cleanInstance(req.GetId().GetValue()) + + req.Isolate = utils.NewBoolValue(true) + req.ServiceToken = serviceResp.GetToken() + resp := server.UpdateInstance(ctx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("平台信息不正确,使用系统Token修改实例,有权限", func(t *testing.T) { + req := createInstance(t, serviceResp, 88, correctCtx) + defer cleanInstance(req.GetId().GetValue()) + + globalCtx := context.WithValue(ctx, utils.StringContext("polaris-token"), "polaris@12345678") + + req.Isolate = utils.NewBoolValue(true) + resp := server.UpdateInstance(globalCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("无服务Token和平台信息,修改实例,返回错误", func(t *testing.T) { + req := createInstance(t, serviceResp, 99, correctCtx) + defer cleanInstance(req.GetId().GetValue()) + + resp := server.UpdateInstance(defaultCtx, req) + if resp.GetCode().GetValue() == api.InvalidServiceToken { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) + + t.Run("服务Token错误,修改实例,返回错误", func(t *testing.T) { + req := createInstance(t, serviceResp, 10, correctCtx) + defer cleanInstance(req.GetId().GetValue()) + + req.ServiceToken = utils.NewStringValue("test") + resp := server.UpdateInstance(ctx, req) + if resp.GetCode().GetValue() == api.Unauthorized { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) +} + +/** + * @brief 测试使用服务Token根据host修改实例 + */ +func TestUpdateIsolateAuthByService(t *testing.T) { + ctx := context.Background() + ctx = context.WithValue(ctx, utils.StringContext("platform-id"), platformID) + ctx = context.WithValue(ctx, utils.StringContext("platform-token"), "test") + + correctCtx := context.Background() + correctCtx = context.WithValue(correctCtx, utils.StringContext("platform-id"), platformID) + correctCtx = context.WithValue(correctCtx, utils.StringContext("platform-token"), platformToken) + + // 创建服务 + serviceResp := createService(t, 3) + defer cleanService(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + t.Run("平台信息为空,使用服务Token修改实例,有权限", func(t *testing.T) { + req := createInstance(t, serviceResp, 1111, correctCtx) + defer cleanInstance(req.GetId().GetValue()) + + req.Isolate = utils.NewBoolValue(true) + req.ServiceToken = serviceResp.GetToken() + resp := server.UpdateInstanceIsolate(defaultCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("平台信息不正确,使用服务Token修改实例,有权限", func(t *testing.T) { + req := createInstance(t, serviceResp, 2222, correctCtx) + defer cleanInstance(req.GetId().GetValue()) + + req.Isolate = utils.NewBoolValue(true) + req.ServiceToken = serviceResp.GetToken() + resp := server.UpdateInstanceIsolate(ctx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("平台信息不正确,使用系统Token修改实例,有权限", func(t *testing.T) { + req := createInstance(t, serviceResp, 3333, correctCtx) + defer cleanInstance(req.GetId().GetValue()) + + globalCtx := context.WithValue(ctx, utils.StringContext("polaris-token"), "polaris@12345678") + + req.Isolate = utils.NewBoolValue(true) + resp := server.UpdateInstanceIsolate(globalCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("无服务Token和平台信息,修改实例,返回错误", func(t *testing.T) { + req := createInstance(t, serviceResp, 4444, correctCtx) + defer cleanInstance(req.GetId().GetValue()) + + req.Isolate = utils.NewBoolValue(true) + resp := server.UpdateInstanceIsolate(defaultCtx, req) + if resp.GetCode().GetValue() == api.InvalidServiceToken { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) + + t.Run("服务Token错误,修改实例,返回错误", func(t *testing.T) { + req := createInstance(t, serviceResp, 5555, correctCtx) + defer cleanInstance(req.GetId().GetValue()) + + req.Isolate = utils.NewBoolValue(true) + req.ServiceToken = utils.NewStringValue("test") + resp := server.UpdateInstanceIsolate(ctx, req) + if resp.GetCode().GetValue() == api.Unauthorized { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) +} + +/** + * @brief 测试使用服务Token删除实例 + */ +func TestDeleteInstanceAuthByService(t *testing.T) { + ctx := context.Background() + ctx = context.WithValue(ctx, utils.StringContext("platform-id"), platformID) + ctx = context.WithValue(ctx, utils.StringContext("platform-token"), "test") + + correctCtx := context.Background() + correctCtx = context.WithValue(correctCtx, utils.StringContext("platform-id"), platformID) + correctCtx = context.WithValue(correctCtx, utils.StringContext("platform-token"), platformToken) + + // 创建服务 + serviceResp := createService(t, 3) + defer cleanService(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + t.Run("平台信息为空,使用服务Token删除实例,有权限", func(t *testing.T) { + req := createInstance(t, serviceResp, 13, correctCtx) + defer cleanInstance(req.GetId().GetValue()) + + req.ServiceToken = serviceResp.GetToken() + resp := server.DeleteInstance(defaultCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("平台信息不正确,使用服务Token删除实例,有权限", func(t *testing.T) { + req := createInstance(t, serviceResp, 14, correctCtx) + defer cleanInstance(req.GetId().GetValue()) + + req.ServiceToken = serviceResp.GetToken() + resp := server.DeleteInstance(ctx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("平台信息不正确,使用系统Token删除实例,有权限", func(t *testing.T) { + req := createInstance(t, serviceResp, 15, correctCtx) + defer cleanInstance(req.GetId().GetValue()) + + globalCtx := context.WithValue(ctx, utils.StringContext("polaris-token"), "polaris@12345678") + + resp := server.DeleteInstance(globalCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("无服务Token和平台信息,删除实例,返回错误", func(t *testing.T) { + req := createInstance(t, serviceResp, 17, correctCtx) + defer cleanInstance(req.GetId().GetValue()) + + resp := server.DeleteInstance(defaultCtx, req) + if resp.GetCode().GetValue() == api.InvalidServiceToken { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) + + t.Run("服务Token错误,删除实例,返回错误", func(t *testing.T) { + req := createInstance(t, serviceResp, 19, correctCtx) + defer cleanInstance(req.GetId().GetValue()) + + req.ServiceToken = utils.NewStringValue("test") + resp := server.DeleteInstance(ctx, req) + if resp.GetCode().GetValue() == api.Unauthorized { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) +} + +/** + * @brief 测试使用服务Token根据host删除实例 + */ +func TestDeleteHostAuthByService(t *testing.T) { + ctx := context.Background() + ctx = context.WithValue(ctx, utils.StringContext("platform-id"), platformID) + ctx = context.WithValue(ctx, utils.StringContext("platform-token"), "test") + + correctCtx := context.Background() + correctCtx = context.WithValue(correctCtx, utils.StringContext("platform-id"), platformID) + correctCtx = context.WithValue(correctCtx, utils.StringContext("platform-token"), platformToken) + + // 创建服务 + serviceResp := createService(t, 3) + defer cleanService(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + t.Run("平台信息为空,使用服务Token删除实例,有权限", func(t *testing.T) { + req := createInstance(t, serviceResp, 101, correctCtx) + defer cleanInstance(req.GetId().GetValue()) + + req.ServiceToken = serviceResp.GetToken() + resp := server.DeleteInstanceByHost(defaultCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("平台信息不正确,使用服务Token删除实例,有权限", func(t *testing.T) { + req := createInstance(t, serviceResp, 102, correctCtx) + defer cleanInstance(req.GetId().GetValue()) + + req.ServiceToken = serviceResp.GetToken() + resp := server.DeleteInstanceByHost(ctx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("平台信息不正确,使用系统Token删除实例,有权限", func(t *testing.T) { + req := createInstance(t, serviceResp, 103, correctCtx) + defer cleanInstance(req.GetId().GetValue()) + + globalCtx := context.WithValue(ctx, utils.StringContext("polaris-token"), "polaris@12345678") + + resp := server.DeleteInstanceByHost(globalCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("无服务Token和平台信息,删除实例,返回错误", func(t *testing.T) { + req := createInstance(t, serviceResp, 104, correctCtx) + defer cleanInstance(req.GetId().GetValue()) + + resp := server.DeleteInstanceByHost(defaultCtx, req) + if resp.GetCode().GetValue() == api.InvalidServiceToken { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) + + t.Run("服务Token错误,删除实例,返回错误", func(t *testing.T) { + req := createInstance(t, serviceResp, 105, correctCtx) + defer cleanInstance(req.GetId().GetValue()) + + req.ServiceToken = utils.NewStringValue("test") + resp := server.DeleteInstanceByHost(ctx, req) + if resp.GetCode().GetValue() == api.Unauthorized { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) +} + +/** + * @brief 创建服务实例 + */ +func createInstance(t *testing.T, service *api.Service, id int, ctx context.Context) *api.Instance { + req := &api.Instance{ + Service: service.GetName(), + Namespace: service.GetNamespace(), + Host: utils.NewStringValue(fmt.Sprintf("%d.%d.%d.%d", id, id, id, id)), + Port: utils.NewUInt32Value(uint32(id)), + } + + resp := server.CreateInstance(ctx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + + return resp.GetInstance() +} + +/** + * @brief 从数据库中删除服务实例 + */ +func cleanInstance(id string) { + log.Infof("clean instance: %s", id) + str := `delete from instance where id = ?` + if _, err := db.Exec(str, id); err != nil { + panic(err) + } +} diff --git a/naming/testAuthPlugin/main_test.go b/naming/testAuthPlugin/main_test.go new file mode 100644 index 000000000..cc5b10a2e --- /dev/null +++ b/naming/testAuthPlugin/main_test.go @@ -0,0 +1,198 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package testAuthPlugin + +import ( + "context" + "database/sql" + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/utils" + "github.com/polarismesh/polaris-server/config" + "github.com/polarismesh/polaris-server/naming" + "github.com/polarismesh/polaris-server/plugin" + "github.com/polarismesh/polaris-server/store" + "gopkg.in/yaml.v2" + "os" + "sync" + "time" + + _ "github.com/polarismesh/polaris-server/plugin/auth/platform" + _ "github.com/polarismesh/polaris-server/plugin/history/logger" + _ "github.com/polarismesh/polaris-server/plugin/ratelimit/tokenBucket" + _ "github.com/polarismesh/polaris-server/store/defaultStore" + _ "github.com/go-sql-driver/mysql" +) + +var ( + cfg = config.Config{} + once = sync.Once{} + server = &naming.Server{} + db = &sql.DB{} + defaultCtx = context.Background() + platformToken = "" +) + +const ( + platformID = "test-platform-id" +) + +/** + * @brief 内部初始化函数 + */ +func initialize() error { + options := log.DefaultOptions() + options.SetLogCallers(log.DefaultScopeName, true) + log.Configure(options) + var err error + once.Do(func() { + err = loadConfigWithAuthPlugin() + if err != nil { + return + } + // 初始化ctx + defaultCtx = context.WithValue(defaultCtx, utils.StringContext("request-id"), "test-auth-plugin") + + // 初始化存储层 + store.SetStoreConfig(&cfg.Store) + store.GetStore() + + // 初始化插件 + plugin.SetPluginConfig(&cfg.Plugin) + + // 初始化naming server + ctx := context.Background() + + naming.SetHealthCheckConfig(&cfg.Naming.HealthCheck) + if err := naming.Initialize(ctx, &cfg.Naming, &cfg.Cache); err != nil { + panic(err) + } + + server, err = naming.GetServer() + if err != nil { + panic(err) + } + + entry := cfg.Store.Option["master"] + config, ok := entry.(map[interface{}]interface{}) + if !ok { + panic("database cfg is invalid") + } + + dbType := config["dbType"].(string) + dbUser := config["dbUser"].(string) + dbPwd := config["dbPwd"].(string) + dbAddr := config["dbAddr"].(string) + dbName := config["dbName"].(string) + + dbSource := fmt.Sprintf("%s:%s@tcp(%s)/%s", dbUser, dbPwd, dbAddr, dbName) + db, err = sql.Open(dbType, dbSource) + if err != nil { + panic(err) + } + + // 创建平台 + resp := createPlatform() + platformToken = resp.GetToken().GetValue() + + // 初始化ctx + defaultCtx = context.WithValue(defaultCtx, utils.StringContext("request-id"), "test-request-id") + + // 等待数据加载到缓存 + time.Sleep(time.Second * 2) + }) + return err +} + +/** + * @brief 加载配置 + */ +func loadConfigWithAuthPlugin() error { + file, err := os.Open("test.yaml") + if err != nil { + fmt.Printf("[ERROR] open file err: %s\n", err.Error()) + return err + } + + if err := yaml.NewDecoder(file).Decode(&cfg); err != nil { + fmt.Printf("[ERROR] decode err: %s\n", err.Error()) + return err + } + return err +} + +/** + * @brief 判断请求是否成功 + */ +func respSuccess(resp api.ResponseMessage) bool { + if api.CalcCode(resp) != 200 { + return false + } + + return true +} + +/** + * @brief 创建一个平台 + */ +func createPlatform() *api.Platform { + platform := &api.Platform{ + Id: utils.NewStringValue(platformID), + Name: utils.NewStringValue("test-platform-name"), + Domain: utils.NewStringValue("test-platform-domain"), + Qps: utils.NewUInt32Value(1), + Owner: utils.NewStringValue("test-platform-owner"), + Department: utils.NewStringValue("test-platform-department"), + Comment: utils.NewStringValue("test-platform-comment"), + } + + cleanPlatform(platformID) + + resp := server.CreatePlatform(defaultCtx, platform) + if !respSuccess(resp) { + panic(resp.GetInfo().GetValue()) + } + + return resp.GetPlatform() +} + +/** + * @brief 从数据库中彻底删除平台 + */ +func cleanPlatform(id string) { + if id == "" { + panic("id is empty") + } + + log.Infof("clean platform: %s", id) + str := `delete from platform where id = ?` + if _, err := db.Exec(str, id); err != nil { + panic(err) + } +} + +/** + * @brief 初始化函数 + */ +func init() { + if err := initialize(); err != nil { + fmt.Printf("[test_auth_plugin] init err: %s", err.Error()) + panic(err) + } +} diff --git a/naming/testAuthPlugin/ratelimit_config_test.go b/naming/testAuthPlugin/ratelimit_config_test.go new file mode 100644 index 000000000..691535b5b --- /dev/null +++ b/naming/testAuthPlugin/ratelimit_config_test.go @@ -0,0 +1,413 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package testAuthPlugin + +import ( + "context" + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/utils" + "github.com/golang/protobuf/ptypes/duration" + "testing" +) + +/** + * @brief 测试使用平台Token操作限流规则 + */ +func TestRateLimitAuthByPlatform(t *testing.T) { + ctx := context.Background() + ctx = context.WithValue(ctx, utils.StringContext("platform-id"), platformID) + ctx = context.WithValue(ctx, utils.StringContext("platform-token"), platformToken) + + // 创建服务 + serviceResp := createService(t, 4) + defer cleanService(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + t.Run("使用平台Token创建限流规则,有权限", func(t *testing.T) { + rateLimit := createRateLimit(t, serviceResp, 1, ctx) + defer cleanRateLimit(rateLimit.GetId().GetValue()) + t.Log("pass") + }) + + t.Run("使用平台Token修改限流规则,有权限", func(t *testing.T) { + rateLimit := createRateLimit(t, serviceResp, 2, ctx) + defer cleanRateLimit(rateLimit.GetId().GetValue()) + + rateLimit.Labels = map[string]*api.MatchString{} + resp := server.UpdateRateLimit(ctx, rateLimit) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("使用平台Token删除限流规则,有权限", func(t *testing.T) { + rateLimit := createRateLimit(t, serviceResp, 3, ctx) + defer cleanRateLimit(rateLimit.GetId().GetValue()) + + resp := server.DeleteRateLimit(ctx, rateLimit) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) +} + +/** + * @brief 测试使用服务Token创建限流规则 + */ +func TestCreateRateLimitAuthByService(t *testing.T) { + ctx := context.Background() + ctx = context.WithValue(ctx, utils.StringContext("platform-id"), platformID) + ctx = context.WithValue(ctx, utils.StringContext("platform-token"), "test") + + correctCtx := context.Background() + correctCtx = context.WithValue(correctCtx, utils.StringContext("platform-id"), platformID) + correctCtx = context.WithValue(correctCtx, utils.StringContext("platform-token"), platformToken) + + // 创建服务 + serviceResp := createService(t, 6) + defer cleanService(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + t.Run("平台信息为空,使用服务Token创建限流规则,有权限", func(t *testing.T) { + req := &api.Rule{ + Service: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + Labels: map[string]*api.MatchString{ + "111": { + Type: 0, + Value: utils.NewStringValue("aaa"), + }, + }, + ServiceToken: serviceResp.GetToken(), + } + + resp := server.CreateRateLimit(defaultCtx, req) + defer cleanRateLimit(resp.GetRateLimit().GetId().GetValue()) + + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("平台信息不正确,使用服务Token创建限流规则,有权限", func(t *testing.T) { + req := &api.Rule{ + Service: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + ServiceToken: serviceResp.GetToken(), + Labels: map[string]*api.MatchString{ + "111": { + Type: 0, + Value: utils.NewStringValue("aaa"), + }, + }, + } + + resp := server.CreateRateLimit(ctx, req) + defer cleanRateLimit(resp.GetInstance().GetId().GetValue()) + + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("平台信息不正确,使用系统Token创建限流规则,有权限", func(t *testing.T) { + req := &api.Rule{ + Service: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + Labels: map[string]*api.MatchString{ + "111": { + Type: 0, + Value: utils.NewStringValue("aaa"), + }, + }, + } + + globalCtx := context.WithValue(ctx, utils.StringContext("polaris-token"), "polaris@12345678") + + resp := server.CreateRateLimit(globalCtx, req) + defer cleanRateLimit(resp.GetInstance().GetId().GetValue()) + + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("无服务Token和平台信息,创建限流规则,返回错误", func(t *testing.T) { + req := &api.Rule{ + Service: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + Labels: map[string]*api.MatchString{ + "111": { + Type: 0, + Value: utils.NewStringValue("aaa"), + }, + }, + } + + resp := server.CreateRateLimit(defaultCtx, req) + if resp.GetCode().GetValue() == api.InvalidServiceToken { + t.Log("pass") + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) + + t.Run("服务Token错误,创建限流规则,返回错误", func(t *testing.T) { + req := &api.Rule{ + Service: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + Labels: map[string]*api.MatchString{ + "111": { + Type: 0, + Value: utils.NewStringValue("aaa"), + }, + }, + ServiceToken: utils.NewStringValue("test"), + } + + resp := server.CreateRateLimit(defaultCtx, req) + if resp.GetCode().GetValue() == api.Unauthorized { + t.Log("pass") + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) +} + +/** + * @brief 测试使用服务Token修改限流规则 + */ +func TestUpdateRateLimitAuthByService(t *testing.T) { + ctx := context.Background() + ctx = context.WithValue(ctx, utils.StringContext("platform-id"), platformID) + ctx = context.WithValue(ctx, utils.StringContext("platform-token"), "test") + + correctCtx := context.Background() + correctCtx = context.WithValue(correctCtx, utils.StringContext("platform-id"), platformID) + correctCtx = context.WithValue(correctCtx, utils.StringContext("platform-token"), platformToken) + + // 创建服务 + serviceResp := createService(t, 6) + defer cleanService(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + t.Run("平台信息为空,使用服务Token修改限流规则,有权限", func(t *testing.T) { + req := createRateLimit(t, serviceResp, 22, correctCtx) + defer cleanRateLimit(req.GetId().GetValue()) + + req.RegexCombine = utils.NewBoolValue(true) + req.ServiceToken = serviceResp.GetToken() + resp := server.UpdateRateLimit(defaultCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("平台信息不正确,使用服务Token修改限流规则,有权限", func(t *testing.T) { + req := createRateLimit(t, serviceResp, 33, correctCtx) + defer cleanRateLimit(req.GetId().GetValue()) + + req.RegexCombine = utils.NewBoolValue(true) + req.ServiceToken = serviceResp.GetToken() + resp := server.UpdateRateLimit(ctx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("平台信息不正确,使用系统Token修改限流规则,有权限", func(t *testing.T) { + req := createRateLimit(t, serviceResp, 33, correctCtx) + defer cleanRateLimit(req.GetId().GetValue()) + + req.RegexCombine = utils.NewBoolValue(true) + + globalCtx := context.WithValue(ctx, utils.StringContext("polaris-token"), "polaris@12345678") + + resp := server.UpdateRateLimit(globalCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("无服务Token和平台信息,修改限流规则,返回错误", func(t *testing.T) { + req := createRateLimit(t, serviceResp, 44, correctCtx) + defer cleanRateLimit(req.GetId().GetValue()) + + resp := server.UpdateRateLimit(defaultCtx, req) + if resp.GetCode().GetValue() == api.InvalidServiceToken { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) + + t.Run("服务Token错误,修改限流规则,返回错误", func(t *testing.T) { + req := createRateLimit(t, serviceResp, 55, correctCtx) + defer cleanRateLimit(req.GetId().GetValue()) + + req.ServiceToken = utils.NewStringValue("test") + resp := server.UpdateRateLimit(ctx, req) + if resp.GetCode().GetValue() == api.Unauthorized { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) +} + +/** + * @brief 测试使用服务Token删除限流规则 + */ +func TestDeleteRateLimitAuthByService(t *testing.T) { + ctx := context.Background() + ctx = context.WithValue(ctx, utils.StringContext("platform-id"), platformID) + ctx = context.WithValue(ctx, utils.StringContext("platform-token"), "test") + + correctCtx := context.Background() + correctCtx = context.WithValue(correctCtx, utils.StringContext("platform-id"), platformID) + correctCtx = context.WithValue(correctCtx, utils.StringContext("platform-token"), platformToken) + + // 创建服务 + serviceResp := createService(t, 6) + defer cleanService(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + t.Run("平台信息为空,使用服务Token删除限流规则,有权限", func(t *testing.T) { + req := createRateLimit(t, serviceResp, 66, correctCtx) + defer cleanRateLimit(req.GetId().GetValue()) + + req.ServiceToken = serviceResp.GetToken() + resp := server.DeleteRateLimit(defaultCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("平台信息不正确,使用服务Token删除限流规则,有权限", func(t *testing.T) { + req := createRateLimit(t, serviceResp, 77, correctCtx) + defer cleanRateLimit(req.GetId().GetValue()) + + req.ServiceToken = serviceResp.GetToken() + resp := server.DeleteRateLimit(ctx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("平台信息不正确,使用系统Token删除限流规则,有权限", func(t *testing.T) { + req := createRateLimit(t, serviceResp, 77, correctCtx) + defer cleanRateLimit(req.GetId().GetValue()) + + globalCtx := context.WithValue(ctx, utils.StringContext("polaris-token"), "polaris@12345678") + + resp := server.DeleteRateLimit(globalCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("无服务Token和平台信息,删除限流规则,返回错误", func(t *testing.T) { + req := createRateLimit(t, serviceResp, 88, correctCtx) + defer cleanRateLimit(req.GetId().GetValue()) + + resp := server.DeleteRateLimit(defaultCtx, req) + if resp.GetCode().GetValue() == api.InvalidServiceToken { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) + + t.Run("服务Token错误,删除限流规则,返回错误", func(t *testing.T) { + req := createRateLimit(t, serviceResp, 99, correctCtx) + defer cleanRateLimit(req.GetId().GetValue()) + + req.ServiceToken = utils.NewStringValue("test") + resp := server.DeleteRateLimit(ctx, req) + if resp.GetCode().GetValue() == api.Unauthorized { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) +} + +/** + * @brief 创建限流规则 + */ +func createRateLimit(t *testing.T, service *api.Service, id int, ctx context.Context) *api.Rule { + rateLimit := &api.Rule{ + Service: service.GetName(), + Namespace: service.GetNamespace(), + Priority: utils.NewUInt32Value(uint32(id)), + Resource: api.Rule_QPS, + Type: api.Rule_GLOBAL, + Labels: map[string]*api.MatchString{ + fmt.Sprintf("name-%d", id): { + Type: api.MatchString_EXACT, + Value: utils.NewStringValue(fmt.Sprintf("value-%d", id)), + }, + fmt.Sprintf("name-%d", id+1): { + Type: api.MatchString_REGEX, + Value: utils.NewStringValue(fmt.Sprintf("value-%d", id+1)), + }, + }, + Amounts: []*api.Amount{ + { + MaxAmount: utils.NewUInt32Value(uint32(10 * id)), + ValidDuration: &duration.Duration{ + Seconds: int64(id), + Nanos: int32(id), + }, + }, + }, + Action: utils.NewStringValue(fmt.Sprintf("behavior-%d", id)), + Disable: utils.NewBoolValue(false), + Report: &api.Report{ + Interval: &duration.Duration{ + Seconds: int64(id), + }, + AmountPercent: utils.NewUInt32Value(uint32(id)), + }, + } + + resp := server.CreateRateLimit(ctx, rateLimit) + if !respSuccess(resp) { + t.Fatalf("error: %+v", resp) + } + + return resp.GetRateLimit() +} + +/** + * @brief 从数据库中删除限流规则 + */ +func cleanRateLimit(id string) { + str := `delete from ratelimit_config where id = ?` + if _, err := db.Exec(str, id); err != nil { + panic(err) + } +} diff --git a/naming/testAuthPlugin/routing_config_test.go b/naming/testAuthPlugin/routing_config_test.go new file mode 100644 index 000000000..d371a9a8a --- /dev/null +++ b/naming/testAuthPlugin/routing_config_test.go @@ -0,0 +1,362 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package testAuthPlugin + +import ( + "context" + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/utils" + "testing" +) + +/** + * @brief 测试使用平台Token操作路由规则 + */ +func TestRoutingConfigAuthByPlatform(t *testing.T) { + ctx := context.Background() + ctx = context.WithValue(ctx, utils.StringContext("platform-id"), platformID) + ctx = context.WithValue(ctx, utils.StringContext("platform-token"), platformToken) + + // 创建服务 + serviceResp := createService(t, 3) + defer cleanService(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + t.Run("使用平台Token创建路由规则,有权限", func(t *testing.T) { + resp := createRoutingConfig(t, serviceResp, 1, ctx) + defer cleanRoutingConfig(resp.GetService().GetValue(), serviceResp.GetNamespace().GetValue()) + t.Log("pass") + }) + + t.Run("使用平台Token修改路由规则,有权限", func(t *testing.T) { + routingConfig := createRoutingConfig(t, serviceResp, 1, ctx) + defer cleanRoutingConfig(routingConfig.GetService().GetValue(), routingConfig.GetNamespace().GetValue()) + + routingConfig.Inbounds = []*api.Route{} + resp := server.UpdateRoutingConfig(ctx, routingConfig) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("使用平台Token删除路由规则,有权限", func(t *testing.T) { + routingConfig := createRoutingConfig(t, serviceResp, 1, ctx) + defer cleanRoutingConfig(routingConfig.GetService().GetValue(), routingConfig.GetNamespace().GetValue()) + + resp := server.DeleteRoutingConfig(ctx, routingConfig) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) +} + +/** + * @brief 测试使用服务Token创建路由规则 + */ +func TestCreateRoutingConfigAuthByService(t *testing.T) { + ctx := context.Background() + ctx = context.WithValue(ctx, utils.StringContext("platform-id"), platformID) + ctx = context.WithValue(ctx, utils.StringContext("platform-token"), "test") + + correctCtx := context.Background() + correctCtx = context.WithValue(correctCtx, utils.StringContext("platform-id"), platformID) + correctCtx = context.WithValue(correctCtx, utils.StringContext("platform-token"), platformToken) + + // 创建服务 + serviceResp := createService(t, 5) + defer cleanService(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + t.Run("平台信息为空,使用服务Token创建路由规则,有权限", func(t *testing.T) { + req := &api.Routing{ + Service: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + ServiceToken: serviceResp.GetToken(), + } + + resp := server.CreateRoutingConfig(defaultCtx, req) + defer cleanRoutingConfig(req.GetService().GetValue(), req.GetNamespace().GetValue()) + + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("平台信息不正确,使用服务Token创建路由规则,有权限", func(t *testing.T) { + req := &api.Routing{ + Service: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + ServiceToken: serviceResp.GetToken(), + } + + resp := server.CreateRoutingConfig(ctx, req) + defer cleanRoutingConfig(req.GetService().GetValue(), req.GetNamespace().GetValue()) + + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("平台信息不正确,使用系统Token创建路由规则,有权限", func(t *testing.T) { + req := &api.Routing{ + Service: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + } + globalCtx := context.WithValue(ctx, utils.StringContext("polaris-token"), "polaris@12345678") + + resp := server.CreateRoutingConfig(globalCtx, req) + defer cleanRoutingConfig(req.GetService().GetValue(), req.GetNamespace().GetValue()) + + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("无服务Token和平台信息,创建路由规则,返回错误", func(t *testing.T) { + req := &api.Routing{ + Service: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + } + + resp := server.CreateRoutingConfig(defaultCtx, req) + if resp.GetCode().GetValue() == api.InvalidServiceToken { + t.Log("pass") + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) + + t.Run("服务Token错误,创建路由规则,返回错误", func(t *testing.T) { + req := &api.Routing{ + Service: serviceResp.GetName(), + Namespace: serviceResp.GetNamespace(), + ServiceToken: utils.NewStringValue("test"), + } + + resp := server.CreateRoutingConfig(defaultCtx, req) + if resp.GetCode().GetValue() == api.Unauthorized { + t.Log("pass") + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) +} + +/** + * @brief 测试使用服务Token修改路由规则 + */ +func TestUpdateRoutingConfigAuthByService(t *testing.T) { + ctx := context.Background() + ctx = context.WithValue(ctx, utils.StringContext("platform-id"), platformID) + ctx = context.WithValue(ctx, utils.StringContext("platform-token"), "test") + + correctCtx := context.Background() + correctCtx = context.WithValue(correctCtx, utils.StringContext("platform-id"), platformID) + correctCtx = context.WithValue(correctCtx, utils.StringContext("platform-token"), platformToken) + + // 创建服务 + serviceResp := createService(t, 5) + defer cleanService(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + t.Run("平台信息为空,使用服务Token修改实例,有权限", func(t *testing.T) { + req := createRoutingConfig(t, serviceResp, 22, correctCtx) + defer cleanRoutingConfig(req.GetService().GetValue(), req.GetNamespace().GetValue()) + + req.ServiceToken = serviceResp.GetToken() + resp := server.UpdateRoutingConfig(defaultCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("平台信息不正确,使用服务Token修改路由规则,有权限", func(t *testing.T) { + req := createRoutingConfig(t, serviceResp, 33, correctCtx) + defer cleanRoutingConfig(req.GetService().GetValue(), req.GetNamespace().GetValue()) + + req.ServiceToken = serviceResp.GetToken() + resp := server.UpdateRoutingConfig(ctx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("平台信息不正确,使用系统Token修改路由规则,有权限", func(t *testing.T) { + req := createRoutingConfig(t, serviceResp, 33, correctCtx) + defer cleanRoutingConfig(req.GetService().GetValue(), req.GetNamespace().GetValue()) + + globalCtx := context.WithValue(ctx, utils.StringContext("polaris-token"), "polaris@12345678") + + resp := server.UpdateRoutingConfig(globalCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("无服务Token和平台信息,修改路由规则,返回错误", func(t *testing.T) { + req := createRoutingConfig(t, serviceResp, 44, correctCtx) + defer cleanRoutingConfig(req.GetService().GetValue(), req.GetNamespace().GetValue()) + + resp := server.UpdateRoutingConfig(defaultCtx, req) + if resp.GetCode().GetValue() == api.InvalidServiceToken { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) + + t.Run("服务Token错误,修改路由规则,返回错误", func(t *testing.T) { + req := createRoutingConfig(t, serviceResp, 55, correctCtx) + defer cleanRoutingConfig(req.GetService().GetValue(), req.GetNamespace().GetValue()) + + req.ServiceToken = utils.NewStringValue("test") + resp := server.UpdateRoutingConfig(ctx, req) + if resp.GetCode().GetValue() == api.Unauthorized { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) +} + +/** + * @brief 使用服务Token删除路由规则 + */ +func TestDeleteRoutingConfigAuthByService(t *testing.T) { + ctx := context.Background() + ctx = context.WithValue(ctx, utils.StringContext("platform-id"), platformID) + ctx = context.WithValue(ctx, utils.StringContext("platform-token"), "test") + + correctCtx := context.Background() + correctCtx = context.WithValue(correctCtx, utils.StringContext("platform-id"), platformID) + correctCtx = context.WithValue(correctCtx, utils.StringContext("platform-token"), platformToken) + + // 创建服务 + serviceResp := createService(t, 5) + defer cleanService(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + t.Run("平台信息为空,使用服务Token删除路由规则,有权限", func(t *testing.T) { + req := createRoutingConfig(t, serviceResp, 66, correctCtx) + defer cleanRoutingConfig(req.GetService().GetValue(), req.GetNamespace().GetValue()) + + req.ServiceToken = serviceResp.GetToken() + resp := server.DeleteRoutingConfig(defaultCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("平台信息不正确,使用服务Token删除路由规则,有权限", func(t *testing.T) { + req := createRoutingConfig(t, serviceResp, 77, correctCtx) + defer cleanRoutingConfig(req.GetService().GetValue(), req.GetNamespace().GetValue()) + + req.ServiceToken = serviceResp.GetToken() + resp := server.DeleteRoutingConfig(ctx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("平台信息不正确,使用系统Token删除路由规则,有权限", func(t *testing.T) { + req := createRoutingConfig(t, serviceResp, 77, correctCtx) + defer cleanRoutingConfig(req.GetService().GetValue(), req.GetNamespace().GetValue()) + + globalCtx := context.WithValue(ctx, utils.StringContext("polaris-token"), "polaris@12345678") + + resp := server.DeleteRoutingConfig(globalCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("无服务Token和平台信息,删除路由规则,返回错误", func(t *testing.T) { + req := createRoutingConfig(t, serviceResp, 88, correctCtx) + defer cleanRoutingConfig(req.GetService().GetValue(), req.GetNamespace().GetValue()) + + resp := server.DeleteRoutingConfig(defaultCtx, req) + if resp.GetCode().GetValue() == api.InvalidServiceToken { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) + + t.Run("服务Token错误,删除路由规则,返回错误", func(t *testing.T) { + req := createRoutingConfig(t, serviceResp, 99, correctCtx) + defer cleanRoutingConfig(req.GetService().GetValue(), req.GetNamespace().GetValue()) + + req.ServiceToken = utils.NewStringValue("test") + resp := server.DeleteRoutingConfig(ctx, req) + if resp.GetCode().GetValue() == api.Unauthorized { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) +} + +/** + * @brief 创建路由规则 + */ +func createRoutingConfig(t *testing.T, service *api.Service, id int, ctx context.Context) *api.Routing { + source := &api.Source{ + Service: utils.NewStringValue(fmt.Sprintf("in-source-service-%d", id)), + Namespace: utils.NewStringValue(fmt.Sprintf("in-source-service-%d", id)), + } + req := &api.Routing{ + Service: service.GetName(), + Namespace: service.GetNamespace(), + Inbounds: []*api.Route{ + { + Sources: []*api.Source{source}, + }, + }, + Outbounds: []*api.Route{ + { + Sources: []*api.Source{source}, + }, + }, + } + + resp := server.CreateRoutingConfig(ctx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + + return resp.GetRouting() +} + +/** + * @brief 从数据库中删除路由规则 + */ +func cleanRoutingConfig(service string, namespace string) { + str := "delete from routing_config where id in (select id from service where name = ? and namespace = ?)" + if _, err := db.Exec(str, service, namespace); err != nil { + panic(err) + } + return +} diff --git a/naming/testAuthPlugin/service_test.go b/naming/testAuthPlugin/service_test.go new file mode 100644 index 000000000..61ede0738 --- /dev/null +++ b/naming/testAuthPlugin/service_test.go @@ -0,0 +1,254 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package testAuthPlugin + +import ( + "context" + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/utils" + "github.com/polarismesh/polaris-server/naming" + "testing" +) + +/** + * @brief 测试使用平台Token操作服务 + */ +func TestServiceAuthByPlatform(t *testing.T) { + ctx := context.Background() + ctx = context.WithValue(ctx, utils.StringContext("platform-id"), platformID) + ctx = context.WithValue(ctx, utils.StringContext("platform-token"), platformToken) + + t.Run("使用平台Token修改服务,有权限", func(t *testing.T) { + serviceResp := createService(t, 1) + defer cleanService(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + req := getCommonService(serviceResp) + req.Owners = utils.NewStringValue("test-owner") + + resp := server.UpdateService(ctx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("使用平台Token删除服务,有权限", func(t *testing.T) { + serviceResp := createService(t, 2) + defer cleanService(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + req := getCommonService(serviceResp) + + resp := server.DeleteService(ctx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) +} + +/** + * @brief 测试使用服务Token操作服务 + */ +func TestServiceAuthByService(t *testing.T) { + ctx := context.Background() + ctx = context.WithValue(ctx, utils.StringContext("platform-id"), platformID) + ctx = context.WithValue(ctx, utils.StringContext("platform-token"), "test") + + t.Run("平台信息为空,使用服务Token修改服务,有权限", func(t *testing.T) { + serviceResp := createService(t, 1) + defer cleanService(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + serviceResp.Owners = utils.NewStringValue("test-owner") + + resp := server.UpdateService(defaultCtx, serviceResp) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("平台信息不正确,使用服务Token修改服务,有权限", func(t *testing.T) { + serviceResp := createService(t, 3) + defer cleanService(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + serviceResp.Owners = utils.NewStringValue("test-owner") + + resp := server.UpdateService(ctx, serviceResp) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("平台信息不正确,使用系统Token修改服务,有权限", func(t *testing.T) { + serviceResp := createService(t, 4) + defer cleanService(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + globalCtx := context.WithValue(ctx, utils.StringContext("polaris-token"), "polaris@12345678") + + req := getCommonService(serviceResp) + req.Owners = utils.NewStringValue("test-owner") + + resp := server.UpdateService(globalCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("无服务Token和平台信息,修改服务,返回错误", func(t *testing.T) { + serviceResp := createService(t, 5) + defer cleanService(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + req := getCommonService(serviceResp) + req.Owners = utils.NewStringValue("test-owner") + + resp := server.UpdateService(defaultCtx, req) + if resp.GetCode().GetValue() == api.InvalidServiceToken { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) + + t.Run("服务Token错误,修改服务,返回错误", func(t *testing.T) { + serviceResp := createService(t, 5) + defer cleanService(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + req := getCommonService(serviceResp) + req.Owners = utils.NewStringValue("test-owner") + req.Token = utils.NewStringValue("test") + + resp := server.UpdateService(defaultCtx, req) + if resp.GetCode().GetValue() == api.Unauthorized { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) + + t.Run("平台信息为空,使用服务Token删除服务,有权限", func(t *testing.T) { + serviceResp := createService(t, 2) + defer cleanService(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + resp := server.DeleteService(defaultCtx, serviceResp) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("平台信息不正确,使用服务Token删除服务,有权限", func(t *testing.T) { + serviceResp := createService(t, 4) + defer cleanService(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + resp := server.DeleteService(ctx, serviceResp) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("平台信息不正确,使用系统Token删除服务,有权限", func(t *testing.T) { + serviceResp := createService(t, 4) + defer cleanService(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + globalCtx := context.WithValue(ctx, utils.StringContext("polaris-token"), "polaris@12345678") + + req := getCommonService(serviceResp) + resp := server.DeleteService(globalCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + t.Log("pass") + }) + + t.Run("无服务Token和平台信息,删除服务,返回错误", func(t *testing.T) { + serviceResp := createService(t, 6) + defer cleanService(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + req := getCommonService(serviceResp) + resp := server.DeleteService(ctx, req) + if resp.GetCode().GetValue() == api.InvalidServiceToken { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) + + t.Run("服务Token错误,删除服务,返回错误", func(t *testing.T) { + serviceResp := createService(t, 7) + defer cleanService(serviceResp.GetName().GetValue(), serviceResp.GetNamespace().GetValue()) + + serviceResp.Token = utils.NewStringValue("test") + resp := server.DeleteService(ctx, serviceResp) + if resp.GetCode().GetValue() == api.Unauthorized { + t.Logf("pass: %s", resp.GetInfo().GetValue()) + } else { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + }) +} + +/** + * @brief 创建服务 + */ +func createService(t *testing.T, id int) *api.Service { + req := &api.Service{ + Name: utils.NewStringValue(fmt.Sprintf("test-service-%d", id)), + Namespace: utils.NewStringValue(naming.DefaultNamespace), + PlatformId: utils.NewStringValue(platformID), + Owners: utils.NewStringValue(fmt.Sprintf("service-owner-%d", id)), + } + + cleanService(req.GetName().GetValue(), req.GetNamespace().GetValue()) + + resp := server.CreateService(defaultCtx, req) + if !respSuccess(resp) { + t.Fatalf("error: %s", resp.GetInfo().GetValue()) + } + + return resp.GetService() +} + +/** + * @brief 删除服务 + */ +func cleanService(name string, namespace string) { + str := `delete from service where name = ? and namespace = ?` + if _, err := db.Exec(str, name, namespace); err != nil { + panic(err) + } + + str = `delete from owner_service_map where service=? and namespace=?` + if _, err := db.Exec(str, name, namespace); err != nil { + panic(err) + } +} + +/** + * @brief 生成服务信息 + */ +func getCommonService(req *api.Service) *api.Service { + service := &api.Service{ + Name: req.GetName(), + Namespace: req.GetNamespace(), + } + return service +} diff --git a/naming/testAuthPlugin/test.yaml b/naming/testAuthPlugin/test.yaml new file mode 100644 index 000000000..df9511ef7 --- /dev/null +++ b/naming/testAuthPlugin/test.yaml @@ -0,0 +1,54 @@ +bootstrap: + logger: + level: debug +naming: + batch: + register: + open: true + queueSize: 1024 + waitTime: 32ms + maxBatchCount: 32 + concurrency: 16 + deregister: + open: true + queueSize: 1024 + waitTime: 32ms + maxBatchCount: 32 + concurrency: 16 + healthcheck: + open: false +cache: + open: false +store: + name: defaultStore + option: + master: + dbType: mysql + dbUser: polaris + dbPwd: polaris + dbAddr: 9.134.38.82:3306 + dbName: polaris_server_for_test + maxOpenConns: 128 + maxIdleConns: 16 + connMaxLifetime: 60 + txIsolationLevel: 2 +plugin: + history: + name: HistoryLogger + ratelimit: + name: token-bucket + option: + remote-conf: false + instance-limit: + open: true + global: + bucket: 2 + rate: 2 + resource-cache-amount: 8 + auth: + name: platform + option: + dbType: mysql + dbAddr: polaris:polaris@tcp(9.134.38.82:3306) + dbName: polaris_server_for_test + interval: 1 # 拉取数据间隔,单位为秒 diff --git a/naming/utils.go b/naming/utils.go new file mode 100644 index 000000000..96f57f215 --- /dev/null +++ b/naming/utils.go @@ -0,0 +1,469 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package naming + +import ( + "context" + "crypto/sha1" + "encoding/hex" + "errors" + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/utils" + "github.com/polarismesh/polaris-server/naming/batch" + "github.com/polarismesh/polaris-server/store" + "github.com/golang/protobuf/ptypes/wrappers" + "go.uber.org/zap" + "io" + "regexp" + "strconv" + "strings" + "unicode/utf8" +) + +const ( + QueryDefaultOffset = 0 + + QueryDefaultLimit = 100 + QueryMaxLimit = 100 + + MaxMetadataLength = 64 + + MaxBusinessLength = 64 + MaxOwnersLength = 1024 + MaxDepartmentLength = 1024 + MaxCommentLength = 1024 + + // service表 + MaxDbServiceNameLength = 128 + MaxDbServiceNamespaceLength = 64 + MaxDbServicePortsLength = 8192 + MaxDbServiceBusinessLength = 128 + MaxDbServiceDeptLength = 1024 + MaxDbServiceCMDBLength = 1024 + MaxDbServiceCommentLength = 1024 + MaxDbServiceOwnerLength = 1024 + MaxDbServiceToken = 2048 + + // instance表 + MaxDbInsHostLength = 128 + MaxDbInsProtocolLength = 32 + MaxDbInsVersionLength = 32 + MaxDbInsLogicSetLength = 128 + + // circuitbreaker表 + MaxDbCircuitbreakerName = 32 + MaxDbCircuitbreakerNamespace = 64 + MaxDbCircuitbreakerBusiness = 64 + MaxDbCircuitbreakerDepartment = 1024 + MaxDbCircuitbreakerComment = 1024 + MaxDbCircuitbreakerOwner = 1024 + MaxDbCircuitbreakerVersion = 32 + + // platform表 + MaxPlatformIDLength = 32 + MaxPlatformNameLength = 128 + MaxPlatformDomainLength = 1024 + MaxPlatformQPS = 65535 + +) + +/* + * 检查资源Name + */ +func checkResourceName(name *wrappers.StringValue) error { + if name == nil { + return errors.New("nil") + } + + if name.GetValue() == "" { + return errors.New("empty") + } + + regStr := "^[0-9A-Za-z-.:_]+$" + ok, err := regexp.MatchString(regStr, name.GetValue()) + if err != nil { + return err + } + if !ok { + return errors.New("name contains invalid character") + } + + return nil +} + +/* + * 检查资源Owners + */ +func checkResourceOwners(owners *wrappers.StringValue) error { + if owners == nil { + return errors.New("nil") + } + + if owners.GetValue() == "" { + return errors.New("empty") + } + + if utf8.RuneCountInString(owners.GetValue()) > MaxOwnersLength { + return errors.New("owners too long") + } + + return nil +} + +/* + * 检查服务实例Host + */ +func checkInstanceHost(host *wrappers.StringValue) error { + if host == nil { + return errors.New("nil") + } + + if host.GetValue() == "" { + return errors.New("empty") + } + + return nil +} + +/* + * 检查服务实例Port + */ +func checkInstancePort(port *wrappers.UInt32Value) error { + if port == nil { + return errors.New("nil") + } + + if port.GetValue() < 0 { + return errors.New("empty") + } + + return nil +} + +// 检查metadata的个数 +// 最大是64个 +// key/value是否符合要求 +func checkMetadata(meta map[string]string) error { + if meta == nil { + return nil + } + + if len(meta) > MaxMetadataLength { + return errors.New("metadata is too long") + } + + /*regStr := "^[0-9A-Za-z-._*]+$" + matchFunc := func(str string) error { + if str == "" { + return nil + } + ok, err := regexp.MatchString(regStr, str) + if err != nil { + log.Errorf("regexp match string(%s) err: %s", str, err.Error()) + return err + } + if !ok { + log.Errorf("metadata string(%s) contains invalid character", str) + return errors.New("contain invalid character") + } + return nil + } + for key, value := range meta { + if err := matchFunc(key); err != nil { + return err + } + if err := matchFunc(value); err != nil { + return err + } + }*/ + + return nil +} + +/* + * 检查查询参数Offset + */ +func checkQueryOffset(offset []string) (int, error) { + if len(offset) == 0 { + return 0, nil + } + + if len(offset) > 1 { + return 0, errors.New("unique") + } + + value, err := strconv.Atoi(offset[0]) + if err != nil { + return 0, err + } + + if value < 0 { + return 0, errors.New("invalid") + } + + return value, nil +} + +/* + * 检查查询参数Limit + */ +func checkQueryLimit(limit []string) (int, error) { + if len(limit) == 0 { + return MaxQuerySize, nil + } + + if len(limit) > 1 { + return 0, errors.New("unique") + } + + value, err := strconv.Atoi(limit[0]) + if err != nil { + return 0, err + } + + if value < 0 { + return 0, errors.New("invalid") + } + + if value > MaxQuerySize { + value = MaxQuerySize + } + + return value, nil +} + +// store code +func storeError2Response(err error) *api.Response { + if err == nil { + return nil + } + return api.NewResponseWithMsg(batch.StoreCode2APICode(err), err.Error()) +} + +// 计算实例ID +func CalculateInstanceID(namespace string, service string, vpcID string, host string, port uint32) (string, error) { + h := sha1.New() + var str string + // 兼容带有vpcID的instance + if vpcID == "" { + str = fmt.Sprintf("%s##%s##%s##%d", namespace, service, host, port) + } else { + str = fmt.Sprintf("%s##%s##%s##%s##%d", namespace, service, vpcID, host, port) + } + + if _, err := io.WriteString(h, str); err != nil { + return "", err + } + + out := hex.EncodeToString(h.Sum(nil)) + return out, nil +} + +// 计算规则ID +func CalculateRuleID(name, namespace string) string { + return name + "." + namespace +} + +// 格式化处理offset参数 +func ParseQueryOffset(offset string) (uint32, error) { + if offset == "" { + return QueryDefaultOffset, nil + } + + tmp, err := strconv.ParseUint(offset, 10, 32) + if err != nil { + log.Errorf("[Server][Query] attribute(offset:%s) is invalid, parse err: %s", + offset, err.Error()) + return 0, err + } + + return uint32(tmp), nil +} + +// 格式化处理limit参数 +func ParseQueryLimit(limit string) (uint32, error) { + if limit == "" { + return QueryDefaultLimit, nil + } + + tmp, err := strconv.ParseUint(limit, 10, 32) + if err != nil { + log.Errorf("[Server][Query] attribute(offset:%s) is invalid, parse err: %s", + limit, err.Error()) + return 0, err + } + if tmp > QueryMaxLimit { + tmp = QueryMaxLimit + } + + return uint32(tmp), nil +} + +// 统一格式化处理Offset和limit参数 +func ParseOffsetAndLimit(query map[string]string) (uint32, uint32, error) { + ofs, err := ParseQueryOffset(query["offset"]) + if err != nil { + return 0, 0, err + } + delete(query, "offset") + + lmt, err := ParseQueryLimit(query["limit"]) + if err != nil { + return 0, 0, err + } + delete(query, "limit") + + return ofs, lmt, nil +} + + +// 解析服务实例的 ip 和 port 查询参数 +func ParseInstanceArgs(query map[string]string) (*store.InstanceArgs, error) { + if len(query) == 0 { + return nil, nil + } + hosts, ok := query["host"] + if !ok { + return nil, fmt.Errorf("port parameter can not be used alone without host") + } + res := &store.InstanceArgs{} + res.Hosts = strings.Split(hosts, ",") + ports, ok := query["port"] + if !ok { + return res, nil + } + portStrs := strings.Split(ports,",") + for _, portStr := range portStrs { + port, err := strconv.ParseUint(portStr,10, 32) + if err != nil { + return nil, fmt.Errorf("%s can not parse as uint, err is %s", portStr, err.Error()) + } + res.Ports = append(res.Ports, uint32(port)) + } + return res, nil +} + +// 从ctx中获取Request-ID +func ParseRequestID(ctx context.Context) string { + if ctx == nil { + return "" + } + rid, _ := ctx.Value(utils.StringContext("request-id")).(string) + return rid +} + +// 从ctx中获取token +func ParseToken(ctx context.Context) string { + if ctx == nil { + return "" + } + + token, _ := ctx.Value(utils.StringContext("polaris-token")).(string) + return token +} + +// 从ctx中获取operator +func ParseOperator(ctx context.Context) string { + defaultOperator := "Polaris" + if ctx == nil { + return defaultOperator + } + + if operator, _ := ctx.Value(utils.StringContext("operator")).(string); operator != "" { + return operator + } + + return defaultOperator +} + +/** + * @brief 从ctx中获取Platform-Id + */ +func ParsePlatformID(ctx context.Context) string { + if ctx == nil { + return "" + } + pid, _ := ctx.Value(utils.StringContext("platform-id")).(string) + return pid +} + +/** + * @brief 从ctx中获取Platform-Token + */ +func ParsePlatformToken(ctx context.Context) string { + if ctx == nil { + return "" + } + pToken, _ := ctx.Value(utils.StringContext("platform-token")).(string) + return pToken +} + +// 生成Request-ID的日志描述 +func ZapRequestID(id string) zap.Field { + return zap.String("request-id", id) +} + +// 生成Platform-ID的日志描述 +func ZapPlatformID(id string) zap.Field { + return zap.String("platform-id", id) +} + +// 检查name字段是否超过DB中对应字段的最大字符长度限制 +func CheckDbStrFieldLen(param *wrappers.StringValue, dbLen int) error { + if param.GetValue() != "" && utf8.RuneCountInString(param.GetValue()) > dbLen { + errMsg := fmt.Sprintf("length of %s is over %d", param.GetValue(), dbLen) + return errors.New(errMsg) + } + return nil +} + +// 检查metadata的K,V是否超过DB中对应字段的最大字符长度限制 +func CheckDbMetaDataFieldLen(metaData map[string]string) error { + for k, v := range metaData { + if utf8.RuneCountInString(k) > 128 || utf8.RuneCountInString(v) > 4096 { + errMsg := fmt.Sprintf("metadata:length of key(%s) or value(%s) is over size(key:128,value:4096)", + k, v) + return errors.New(errMsg) + } + } + return nil +} + +/** + * @brief 使用平台ID鉴权 + */ +func (s *Server) verifyAuthByPlatform(ctx context.Context, sPlatformID string) bool { + // 判断平台鉴权插件是否开启 + if s.auth == nil { + return false + } + // 若服务无平台ID,则采用默认方式鉴权 + if sPlatformID == "" { + return false + } + + platformID := ParsePlatformID(ctx) + platformToken := ParsePlatformToken(ctx) + + if s.auth.Allow(platformID, platformToken) && platformID == sPlatformID { + return true + } + return false +} diff --git a/plugin.go b/plugin.go new file mode 100644 index 000000000..a4ed02de1 --- /dev/null +++ b/plugin.go @@ -0,0 +1,39 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package main + +import ( + _ "github.com/polarismesh/polaris-server/apiserver/eurekaserver" + _ "github.com/polarismesh/polaris-server/apiserver/grpcserver" + _ "github.com/polarismesh/polaris-server/apiserver/httpserver" + _ "github.com/polarismesh/polaris-server/apiserver/l5pbserver" + + _ "github.com/polarismesh/polaris-server/naming/cache" + _ "github.com/polarismesh/polaris-server/store/defaultStore" + _ "github.com/polarismesh/polaris-server/store/memory" + + _ "github.com/polarismesh/polaris-server/plugin/cmdb/memory" + + _ "github.com/polarismesh/polaris-server/plugin/auth/platform" + _ "github.com/polarismesh/polaris-server/plugin/discoverStatis/discoverLocal" + _ "github.com/polarismesh/polaris-server/plugin/history/logger" + _ "github.com/polarismesh/polaris-server/plugin/parsePassword" + _ "github.com/polarismesh/polaris-server/plugin/ratelimit/lrurate" + _ "github.com/polarismesh/polaris-server/plugin/ratelimit/tokenBucket" + _ "github.com/polarismesh/polaris-server/plugin/statis/local" +) diff --git a/plugin/auth.go b/plugin/auth.go new file mode 100644 index 000000000..9aa7ed30e --- /dev/null +++ b/plugin/auth.go @@ -0,0 +1,61 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package plugin + +import ( + "github.com/polarismesh/polaris-server/common/log" + "os" + "sync" +) + +var ( + // 插件初始化原子变量 + authOnce = &sync.Once{} +) + +/** + * @brief AUTH插件接口 + */ +type Auth interface { + Plugin + + Allow(platformID, platformToken string) bool + + IsWhiteList(ip string) bool +} + +/** + * @brief 获取Auth插件 + */ +func GetAuth() Auth { + c := &config.Auth + + plugin, exist := pluginSet[c.Name] + if !exist { + return nil + } + + authOnce.Do(func() { + if err := plugin.Initialize(c); err != nil { + log.Errorf("plugin init err: %s", err.Error()) + os.Exit(-1) + } + }) + + return plugin.(Auth) +} diff --git a/plugin/auth/platform/platform.go b/plugin/auth/platform/platform.go new file mode 100644 index 000000000..e6cb3a9a1 --- /dev/null +++ b/plugin/auth/platform/platform.go @@ -0,0 +1,277 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package platform + +import ( + "database/sql" + "fmt" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/plugin" + "go.uber.org/zap" + "sync" + "time" +) + +const ( + PluginName = "platform" + DefaultTimeDiff = -1 * time.Second * 5 +) + +/** + * @brief 初始化注册函数 + */ +func init() { + plugin.RegisterPlugin(PluginName, &Auth{}) +} + +/** + * @brief 鉴权插件 + */ +type Auth struct { + dbType string + dbSourceName string + interval time.Duration + whiteList string + firstUpdate bool + lastMtime time.Time + ids *sync.Map +} + +/** + * @brief 返回插件名字 + */ +func (a *Auth) Name() string { + return PluginName +} + +/** + * @brief 初始化鉴权插件 + */ +func (a *Auth) Initialize(conf *plugin.ConfigEntry) error { + dbType, _ := conf.Option["dbType"].(string) + dbAddr, _ := conf.Option["dbAddr"].(string) + dbName, _ := conf.Option["dbName"].(string) + interval, _ := conf.Option["interval"].(int) + whiteList, _ := conf.Option["white-list"].(string) + + if dbType == "" || dbAddr == "" || dbName == "" { + return fmt.Errorf("Config Plugin %s missing database params", PluginName) + } + + if interval == 0 { + return fmt.Errorf("Config Plugin %s has error interval: %d", interval) + } + + a.dbType = dbType + a.dbSourceName = dbAddr + "/" + dbName + a.interval = time.Duration(interval) * time.Second + a.whiteList = whiteList + a.ids = new(sync.Map) + a.lastMtime = time.Unix(0, 0) + a.firstUpdate = true + + if err := a.update(); err != nil { + log.Errorf("[Plugin][%s] update err: %s", PluginName, err.Error()) + return err + } + a.firstUpdate = false + + go a.run() + return nil +} + +/** + * @brief 销毁插件 + */ +func (a *Auth) Destroy() error { + return nil +} + +/** + * @brief 判断请求是否允许通过 + */ +func (a *Auth) Allow(platformID, platformToken string) bool { + if platformID == "" || platformToken == "" { + return false + } + platform := a.getPlatformByID(platformID) + if platform == nil { + log.Errorf("[Plugin][%s] platform (%s) does not exist", PluginName, platformID) + return false + } + + if platform.Token == platformToken { + return true + } + + return false +} + +/** + * @brief 判断请求ip是否属于白名单 + */ +func (a *Auth) IsWhiteList(ip string) bool { + if ip == "" || a.whiteList == "" { + return false + } + if ip == a.whiteList { + return true + } + return false +} + +/** + * @brief 主流程 + */ +func (a *Auth) run() { + ticker := time.NewTicker(a.interval) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + a.update() + } + } +} + +/** + * @brief 更新数据 + */ +func (a *Auth) update() error { + start := time.Now() + out, err := a.getPlatforms() + if err != nil { + return err + } + + update, del := a.setPlatform(out) + log.Info("[Plugin][platform] get more platforms", zap.Int("update", update), zap.Int("delete", del), + zap.Time("last", a.lastMtime), zap.Duration("used", time.Now().Sub(start))) + return nil +} + +/** + * @brief 从数据库中拉取增量数据 + */ +func (a *Auth) getPlatforms() ([]*model.Platform, error) { + // 每次采用短连接的方式,重新连接mysql + db, err := sql.Open(a.dbType, a.dbSourceName) + if err != nil { + return nil, err + } + defer db.Close() + + str := `select id, qps, token, flag, unix_timestamp(mtime) from platform where mtime > ?` + if a.firstUpdate { + str += " and flag != 1" // nolint + } + rows, err := db.Query(str, a.lastMtime.Add(DefaultTimeDiff).Format("2006-01-02 15:04:05")) + if err != nil { + log.Errorf("[Store][platform] query platform with mtime err: %s", err.Error()) + return nil, err + } + + out, err := fetchPlatformRows(rows) + if err != nil { + return nil, err + } + return out, nil +} + +/** + * @brief 读取平台信息数据 + */ +func fetchPlatformRows(rows *sql.Rows) ([]*model.Platform, error) { + defer rows.Close() + var out []*model.Platform + for rows.Next() { + var platform model.Platform + var flag int + var mtime int64 + err := rows.Scan(&platform.ID, &platform.QPS, &platform.Token, &flag, &mtime) + if err != nil { + log.Errorf("[Plugin][%s] fetch platform scan err: %s", err.Error()) + return nil, err + } + platform.ModifyTime = time.Unix(mtime, 0) + platform.Valid = true + if flag == 1 { + platform.Valid = false + } + out = append(out, &platform) + } + if err := rows.Err(); err != nil { + log.Errorf("[Plugin][%s] fetch platform next err: %s", err.Error()) + return nil, err + } + return out, nil +} + +/** + * @brief 更新数据到缓存 + */ +func (a *Auth) setPlatform(platforms []*model.Platform) (int, int) { + if len(platforms) == 0 { + return 0, 0 + } + + lastMtime := a.lastMtime.Unix() + update := 0 + del := 0 + for _, entry := range platforms { + if entry.ID == "" { + continue + } + + if entry.ModifyTime.Unix() > lastMtime { + lastMtime = entry.ModifyTime.Unix() + } + + if entry.Valid == false { + del++ + a.ids.Delete(entry.ID) + continue + } + + update++ + a.ids.Store(entry.ID, entry) + } + + if a.lastMtime.Unix() < lastMtime { + a.lastMtime = time.Unix(lastMtime, 0) + } + return update, del +} + +/** + * @brief 根据平台ID获取平台信息 + */ + +func (a *Auth) getPlatformByID(id string) *model.Platform { + if id == "" { + return nil + } + value, ok := a.ids.Load(id) + if !ok { + return nil + } + + return value.(*model.Platform) +} diff --git a/plugin/cmdb.go b/plugin/cmdb.go new file mode 100644 index 000000000..1bf2ff52d --- /dev/null +++ b/plugin/cmdb.go @@ -0,0 +1,66 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package plugin + +import ( + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" + "os" +) + +/** + * @brief CMDB插件接口 + */ +type CMDB interface { + Plugin + + // 在CMDB中没有找到Host,返回error为nil,location为nil + // 插件内部出现错误,返回error不为nil,忽略location + GetLocation(host string) (*model.Location, error) + + // 提供一个Range接口,遍历所有的数据 + // 遍历失败,通过Range返回值error可以额捕获 + // 参数为一个回调函数 + // 返回值:bool,是否继续遍历 + // 返回值:error,回调函数处理结果,error不为nil,则停止遍历过程,并且通过Range返回error + Range(handler func(host string, location *model.Location) (bool, error)) error + + // 获取当前CMDB存储的entry个数 + Size() int32 +} + +/** + * @brief 获取CMDB插件 + */ +func GetCMDB() CMDB { + c := &config.CMDB + + plugin, exist := pluginSet[c.Name] + if !exist { + return nil + } + + once.Do(func() { + if err := plugin.Initialize(c); err != nil { + log.Errorf("plugin init err: %s", err.Error()) + os.Exit(-1) + } + }) + + return plugin.(CMDB) +} diff --git a/plugin/cmdb/memory/memeory.go b/plugin/cmdb/memory/memeory.go new file mode 100644 index 000000000..6b7990dee --- /dev/null +++ b/plugin/cmdb/memory/memeory.go @@ -0,0 +1,78 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package memory + +import ( + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/plugin" +) + +const ( + PluginName = "memory" +) + +// 自注册到插件列表 +func init() { + plugin.RegisterPlugin(PluginName, &Memory{}) +} + +// 定义MemoryCMDB类 +type Memory struct { + key string +} + +// 返回插件名 +func (m *Memory) Name() string { + return PluginName +} + +// 初始化函数 +func (m *Memory) Initialize(c *plugin.ConfigEntry) error { + option := c.Option + m.key = option["key"].(string) + + return nil +} + +// 销毁函数 +func (m *Memory) Destroy() error { + return nil +} + +// 实现CMDB插件接口 +func (m *Memory) GetLocation(host string) (*model.Location, error) { + return nil, nil +} + +// 实现CMDB插件接口 +func (m *Memory) Range(handler func(host string, location *model.Location) (bool, error)) error { + cont, err := handler("", nil) + if err != nil { + return err + } + + if !cont { + return nil + } + return nil +} + +// 实现CMDB插件接口 +func (m *Memory) Size() int32 { + return 0 +} diff --git a/plugin/discoverStatis/discoverLocal/README.md b/plugin/discoverStatis/discoverLocal/README.md new file mode 100644 index 000000000..cc07c3cb2 --- /dev/null +++ b/plugin/discoverStatis/discoverLocal/README.md @@ -0,0 +1,21 @@ +# 统计服务发现请求到本地 + +## 测试 +1. 功能测试: 正常 +2. 压力测试: + 存入服务数量及写入文件耗时: + ``` + === RUN TestWriteFile + local_test.go:52: total num is 250000, duration is 175.246112ms + local_test.go:52: total num is 500000, duration is 361.50121ms + local_test.go:52: total num is 1000000, duration is 842.463092ms + local_test.go:52: total num is 1500000, duration is 1.305463751s + --- PASS: TestWriteFile (5.66s) + PASS + + Process finished with exit code 0 + ``` + + 写入channel测试: + channel大小为1024,并发1000,可以支持10w/s的AddDiscoverCall请求 + \ No newline at end of file diff --git a/plugin/discoverStatis/discoverLocal/discovercall.go b/plugin/discoverStatis/discoverLocal/discovercall.go new file mode 100644 index 000000000..ae1179d9d --- /dev/null +++ b/plugin/discoverStatis/discoverLocal/discovercall.go @@ -0,0 +1,71 @@ +package discoverLocal + +import ( + "bytes" + "go.uber.org/zap" + "time" +) + +/** + * @brief 服务发现统计 + */ +type DiscoverCall struct { + service string + namespace string + time time.Time +} + +/** + * @brief 服务 + */ +type Service struct { + name string + namespace string +} + +/** + * @brief 服务发现统计条目 + */ +type DiscoverCallStatis struct { + statis map[Service]time.Time + + logger *zap.Logger +} + +/** + * @brief 添加服务发现统计数据 + */ +func (d *DiscoverCallStatis) add(dc *DiscoverCall) { + service := Service{ + name: dc.service, + namespace: dc.namespace, + } + + d.statis[service] = dc.time +} + +/** + * @brief 打印服务发现统计 + */ +func (d *DiscoverCallStatis) log() { + if len(d.statis) == 0 { + return + } + + var buffer bytes.Buffer + for service, time := range d.statis { + buffer.WriteString("service=") + buffer.WriteString(service.name) + buffer.WriteString(";") + buffer.WriteString("namespace=") + buffer.WriteString(service.namespace) + buffer.WriteString(";") + buffer.WriteString("visitTime=") + buffer.WriteString(time.Format("2006-01-02 15:04:05")) + buffer.WriteString("\n") + } + + d.logger.Info(buffer.String()) + + d.statis = make(map[Service]time.Time) +} diff --git a/plugin/discoverStatis/discoverLocal/local.go b/plugin/discoverStatis/discoverLocal/local.go new file mode 100644 index 000000000..6e93f9ac8 --- /dev/null +++ b/plugin/discoverStatis/discoverLocal/local.go @@ -0,0 +1,96 @@ +package discoverLocal + +import ( + "fmt" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/plugin" + "time" +) + +/** + * @brief 注册服务发现统计插件 + */ +func init() { + d := &DiscoverStatisWorker{} + plugin.RegisterPlugin(d.Name(), d) +} + +/** + * @brief 服务发现统计插件 + */ +type DiscoverStatisWorker struct { + interval time.Duration + + dcc chan *DiscoverCall + dcs *DiscoverCallStatis +} + +/** + * @brief 获取插件名称 + */ +func (d *DiscoverStatisWorker) Name() string { + return "discoverLocal" +} + +/** + * @brief 初始化服务发现统计插件 + */ +func (d *DiscoverStatisWorker) Initialize(conf *plugin.ConfigEntry) error { + // 设置打印周期 + interval := conf.Option["interval"].(int) + d.interval = time.Duration(interval) * time.Second + + outputPath := conf.Option["outputPath"].(string) + + // 初始化 + d.dcc = make(chan *DiscoverCall, 1024) + d.dcs = &DiscoverCallStatis{ + statis: make(map[Service]time.Time), + logger: newLogger(outputPath + "/" + "discovercall.log"), + } + + go d.Run() + + return nil +} + +/** + * @brief 销毁服务发现统计插件 + */ +func (d *DiscoverStatisWorker) Destroy() error { + return nil +} + +/** + * @brief 上报请求 + */ +func (d *DiscoverStatisWorker) AddDiscoverCall(service, namespace string, time time.Time) error { + select { + case d.dcc <- &DiscoverCall{ + service: service, + namespace: namespace, + time: time, + }: + default: + log.Errorf("[DiscoverStatis] service: %s, namespace: %s is not captured", service, namespace) + return fmt.Errorf("[DiscoverStatis] service: %s, namespace: %s is not captured", service, namespace) + } + return nil +} + +/** + * @brief 主流程 + */ +func (d *DiscoverStatisWorker) Run() { + ticker := time.NewTicker(d.interval) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + d.dcs.log() + case dc := <-d.dcc: + d.dcs.add(dc) + } + } +} diff --git a/plugin/discoverStatis/discoverLocal/local_test.go b/plugin/discoverStatis/discoverLocal/local_test.go new file mode 100644 index 000000000..f78994d8f --- /dev/null +++ b/plugin/discoverStatis/discoverLocal/local_test.go @@ -0,0 +1,87 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package discoverLocal + +import ( + "fmt" + "testing" + "time" +) + +/** + * @brief 测试打印文件所需耗时 + */ +func TestWriteFile(t *testing.T) { + dcs := &DiscoverCallStatis{ + statis: make(map[Service]time.Time), + logger: newLogger("discovercall_test1.log"), + } + namespace := "Test" + totals := []int{25, 50, 100, 150} + for _, num := range totals { + count := 10000 + for i := 0; i <= num; i++ { + for j := 0; j <= count; j++ { + name := fmt.Sprintf("test-service-%d-%d", i, j) + service := Service{ + name: name, + namespace: namespace, + } + dcs.statis[service] = time.Now() + } + } + + startTime := time.Now() + dcs.log() + endTime := time.Now() + t.Logf("total num is %d, duration is %v", num*count, endTime.Sub(startTime)) + } + +} + +/** + * @brief 测试写入chan的情况 + */ +func TestDiscoverStatisWorker_AddDiscoverCall(t *testing.T) { + worker := &DiscoverStatisWorker{ + interval: 60 * time.Second, + dcc: make(chan *DiscoverCall, 1024), + dcs: &DiscoverCallStatis{ + statis: make(map[Service]time.Time), + logger: newLogger("discovercall_test2.log"), + }, + } + + go worker.Run() + + namespace := "Test" + timeout := time.After(time.Minute * 3) + for i := 0; i < 1000; i++ { + go func(index int) { + for { + name := fmt.Sprintf("test-service-%d", index) + if err := worker.AddDiscoverCall(name, namespace, time.Now()); err != nil { + t.Errorf("err: %s", err.Error()) + } + time.Sleep(time.Millisecond * 10) + } + }(i) + } + <-timeout + t.Log("pass") +} diff --git a/plugin/discoverStatis/discoverLocal/logger.go b/plugin/discoverStatis/discoverLocal/logger.go new file mode 100644 index 000000000..0ab4bc58b --- /dev/null +++ b/plugin/discoverStatis/discoverLocal/logger.go @@ -0,0 +1,50 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package discoverLocal + +import ( + "github.com/natefinch/lumberjack" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +/** + * @brief 创建日志打印器 + */ +func newLogger(file string) *zap.Logger { + encCfg := zapcore.EncoderConfig{ + MessageKey: "msg", + TimeKey: "time", + NameKey: "name", + CallerKey: "caller", + StacktraceKey: "stacktrace", + LineEnding: zapcore.DefaultLineEnding, + EncodeLevel: zapcore.LowercaseLevelEncoder, + EncodeDuration: zapcore.StringDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + } + + w := zapcore.AddSync(&lumberjack.Logger{ + Filename: file, + MaxSize: 500, // MB + MaxBackups: 10, + MaxAge: 7, // days + }) + + return zap.New(zapcore.NewCore(zapcore.NewConsoleEncoder(encCfg), w, zap.InfoLevel)) +} diff --git a/plugin/discoverstatis.go b/plugin/discoverstatis.go new file mode 100644 index 000000000..6e32fb25b --- /dev/null +++ b/plugin/discoverstatis.go @@ -0,0 +1,59 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package plugin + +import ( + "github.com/polarismesh/polaris-server/common/log" + "os" + "sync" + "time" +) + +var ( + discoverStatisOnce = &sync.Once{} +) + +/** + * @brief 服务发现统计插件接口 + */ +type DiscoverStatis interface { + Plugin + + AddDiscoverCall(service, namespace string, time time.Time) error +} + +/** + * @brief 获取服务发现统计插件 + */ +func GetDiscoverStatis() DiscoverStatis { + c := &config.DiscoverStatis + + plugin, exist := pluginSet[c.Name] + if !exist { + return nil + } + + discoverStatisOnce.Do(func() { + if err := plugin.Initialize(c); err != nil { + log.Errorf("plugin init err: %s", err.Error()) + os.Exit(-1) + } + }) + + return plugin.(DiscoverStatis) +} diff --git a/plugin/history.go b/plugin/history.go new file mode 100644 index 000000000..bbbf44ac3 --- /dev/null +++ b/plugin/history.go @@ -0,0 +1,54 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package plugin + +import ( + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" + "os" + "sync" +) + +var ( + // 插件初始化原子变量 + historyOnce = &sync.Once{} +) + +// 历史记录插件 +type History interface { + Plugin + Record(entry *model.RecordEntry) +} + +// 获取历史记录插件 +func GetHistory() History { + c := &config.History + plugin, exist := pluginSet[c.Name] + if !exist { + return nil + } + + historyOnce.Do(func() { + if err := plugin.Initialize(c); err != nil { + log.Errorf("plugin init err: %s", err.Error()) + os.Exit(-1) + } + }) + + return plugin.(History) +} diff --git a/plugin/history/README.md b/plugin/history/README.md new file mode 100644 index 000000000..43c42e33f --- /dev/null +++ b/plugin/history/README.md @@ -0,0 +1 @@ +# 操作记录插件 \ No newline at end of file diff --git a/plugin/history/logger/history_logger.go b/plugin/history/logger/history_logger.go new file mode 100644 index 000000000..5cb58fb3c --- /dev/null +++ b/plugin/history/logger/history_logger.go @@ -0,0 +1,103 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package logger + +import ( + "fmt" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/plugin" + "github.com/natefinch/lumberjack" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// 把操作记录记录到日志文件中 +const ( + PluginName = "HistoryLogger" +) + +// 初始化注册函数 +func init() { + plugin.RegisterPlugin(PluginName, &HistoryLogger{}) +} + +// 历史记录logger +type HistoryLogger struct { + logger *zap.Logger +} + +// 返回插件名字 +func (h *HistoryLogger) Name() string { + return PluginName +} + +// 销毁插件 +func (h *HistoryLogger) Destroy() error { + return h.logger.Sync() +} + +// 插件初始化 +func (h *HistoryLogger) Initialize(c *plugin.ConfigEntry) error { + // 日志的encode + encCfg := zapcore.EncoderConfig{ + TimeKey: "time", + //LevelKey: "level", + NameKey: "scope", + CallerKey: "caller", + MessageKey: "msg", + StacktraceKey: "stack", + LineEnding: zapcore.DefaultLineEnding, + EncodeLevel: zapcore.LowercaseLevelEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + EncodeDuration: zapcore.StringDurationEncoder, + //EncodeTime: TimeEncoder, + } + + // 同步到文件中的配置 TODO,参数来自于外部配置文件 + w := zapcore.AddSync(&lumberjack.Logger{ + Filename: "./log/polaris-history.log", // TODO + MaxSize: 500, // megabytes TODO + MaxBackups: 10, + MaxAge: 15, // days TODO + }) + //multiSync := zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), w) + + // 日志 + core := zapcore.NewCore(zapcore.NewConsoleEncoder(encCfg), w, zap.DebugLevel) + logger := zap.New(core) + h.logger = logger + + return nil +} + +// 记录操作记录到日志中 +func (h *HistoryLogger) Record(entry *model.RecordEntry) { + var str string + switch model.GetResourceType(entry.ResourceType) { + case model.ServiceType: + str = fmt.Sprintf("resource_type=%s;operation_type=%s;namespace=%s;service=%s;context=%s;operator=%s;ctime=%s", + string(entry.ResourceType), string(entry.OperationType), entry.Namespace, entry.Service, + entry.Context, entry.Operator, entry.CreateTime.Format("2006-01-02 15:04:05")) + case model.MeshType: + str = fmt.Sprintf( + "resource_type=%s;operation_type=%s;mesh_id=%s;revision=%s;context=%s;operator=%s;ctime=%s", + string(entry.ResourceType), string(entry.OperationType), entry.MeshID, entry.Revision, + entry.Context, entry.Operator, entry.CreateTime.Format("2006-01-02 15:04:05")) + } + h.logger.Info(str) +} diff --git a/plugin/parsePassword.go b/plugin/parsePassword.go new file mode 100644 index 000000000..72f237cc8 --- /dev/null +++ b/plugin/parsePassword.go @@ -0,0 +1,52 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package plugin + +import ( + "github.com/polarismesh/polaris-server/common/log" + "os" + "sync" +) + +var ( + passwordOnce = &sync.Once{} +) + +// 密码插件 +type ParsePassword interface { + Plugin + ParsePassword(cipher string) (string, error) +} + +// 获取解析密码插件 +func GetParsePassword() ParsePassword { + c := &config.ParsePassword + plugin, exist := pluginSet[c.Name] + if !exist { + return nil + } + + passwordOnce.Do(func() { + if err := plugin.Initialize(c); err != nil { + log.Errorf("plugin init err: %s", err.Error()) + os.Exit(-1) + } + }) + + return plugin.(ParsePassword) +} diff --git a/plugin/parsePassword/README.md b/plugin/parsePassword/README.md new file mode 100644 index 000000000..bbdae3e8c --- /dev/null +++ b/plugin/parsePassword/README.md @@ -0,0 +1 @@ +# 密码插件 \ No newline at end of file diff --git a/plugin/parsePassword/parse.go b/plugin/parsePassword/parse.go new file mode 100644 index 000000000..636716a03 --- /dev/null +++ b/plugin/parsePassword/parse.go @@ -0,0 +1,53 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package parseCode + +import "github.com/polarismesh/polaris-server/plugin" + +const ( + PluginName = "localParse" +) + +// 初始化注册函数 +func init() { + plugin.RegisterPlugin(PluginName, &Password{}) +} + +// 密码插件 +type Password struct { +} + +// 返回插件名字 +func (p *Password) Name() string { + return PluginName +} + +// 销毁插件 +func (p *Password) Destroy() error { + return nil +} + +// 插件初始化 +func (p *Password) Initialize(c *plugin.ConfigEntry) error { + return nil +} + +// 解析密码 +func (p *Password) ParsePassword(cipher string) (string, error) { + return "", nil +} diff --git a/plugin/plugin.go b/plugin/plugin.go new file mode 100644 index 000000000..b5e78fecb --- /dev/null +++ b/plugin/plugin.go @@ -0,0 +1,78 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package plugin + +import ( + "fmt" + "sync" +) + +var ( + pluginSet = make(map[string]Plugin) + config = &Config{} + once = &sync.Once{} +) + +/** + * @brief 注册插件 + */ +func RegisterPlugin(name string, plugin Plugin) { + if _, exist := pluginSet[name]; exist { + panic(fmt.Sprintf("existed plugin: name=%v", name)) + } + + pluginSet[name] = plugin +} + +/** + * @brief 设置插件配置 + */ +func SetPluginConfig(c *Config) { + config = c +} + +/** + * @brief 通用插件接口 + */ +type Plugin interface { + Name() string + Initialize(c *ConfigEntry) error + Destroy() error +} + +/** + * @brief 单个插件配置 + */ +type ConfigEntry struct { + Name string `yaml:"name"` + Option map[string]interface{} `yaml:"option"` +} + +/** + * @brief 插件配置 + */ +type Config struct { + CMDB ConfigEntry `yaml:"cmdb"` + RateLimit ConfigEntry `yaml:"ratelimit"` + History ConfigEntry `yaml:"history"` + Statis ConfigEntry `yaml:"statis"` + DiscoverStatis ConfigEntry `yaml:"discoverStatis"` + ParsePassword ConfigEntry `yaml:"parsePassword"` + Auth ConfigEntry `yaml:"auth"` + MeshResourceValidate ConfigEntry `yaml:"meshResourceValidate"` +} diff --git a/plugin/ratelimit.go b/plugin/ratelimit.go new file mode 100644 index 000000000..b8cb980c1 --- /dev/null +++ b/plugin/ratelimit.go @@ -0,0 +1,84 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package plugin + +import ( + "github.com/polarismesh/polaris-server/common/log" + "os" + "sync" +) + +type RatelimitType int + +const ( + // 基于ip的流控 + IPRatelimit RatelimitType = iota + 1 + + // 基于接口级限流 + APIRatelimit + + // 基于service的流控 + ServiceRatelimit + + // 基于Instance的限流 + InstanceRatelimit +) + +var RatelimitStr = map[RatelimitType]string{ + IPRatelimit: "ip-limit", + APIRatelimit: "api-limit", + ServiceRatelimit: "service-limit", + InstanceRatelimit: "instance-limit", +} + +var ( + ratelimitOnce = sync.Once{} +) + +/** + * @brief Ratelimit插件接口 + */ +type Ratelimit interface { + Plugin + + // 是否允许访问, true: 允许, false: 不允许 TODO + // 参数rateType即限流类型,id为限流的key + // 如果rateType为RatelimitIP则id为ip, rateType为RatelimitService则id为ip_namespace_service或ip_serviceId + Allow(typ RatelimitType, key string) bool +} + +/** + * @brief 获取RateLimit插件 + */ +func GetRatelimit() Ratelimit { + c := &config.RateLimit + + plugin, exist := pluginSet[c.Name] + if !exist { + return nil + } + + ratelimitOnce.Do(func() { + if err := plugin.Initialize(c); err != nil { + log.Errorf("plugin init err: %s", err.Error()) + os.Exit(-1) + } + }) + + return plugin.(Ratelimit) +} diff --git a/plugin/ratelimit/README.md b/plugin/ratelimit/README.md new file mode 100644 index 000000000..57840b946 --- /dev/null +++ b/plugin/ratelimit/README.md @@ -0,0 +1 @@ +# 访问频率限制 diff --git a/plugin/ratelimit/lrurate/base.go b/plugin/ratelimit/lrurate/base.go new file mode 100644 index 000000000..323a99299 --- /dev/null +++ b/plugin/ratelimit/lrurate/base.go @@ -0,0 +1,79 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package lrurate + +import ( + "github.com/hashicorp/golang-lru" + "golang.org/x/time/rate" + "hash/crc32" +) + +var ( + ipLruCache *lru.Cache + serviceLruCache *lru.Cache +) + +// 初始化lru组件 +func initEnv() error { + var err error + + ipLruCache, err = lru.New(ratelimitIPLruSize) + if err != nil { + return err + } + + serviceLruCache, err = lru.New(ratelimitServiceLruSize) + if err != nil { + return err + } + + return nil +} + +// crc32取字符串hash值 +func hash(str string) uint32 { + return crc32.ChecksumIEEE([]byte(str)) +} + +// ip限流 +func allowIP(id string) bool { + key := hash(id) + + ipLruCache.ContainsOrAdd(key, rate.NewLimiter(rate.Limit(ratelimitIPRate), ratelimitIPBurst)) + + value, ok := ipLruCache.Get(key) + if ok { + return value.(*rate.Limiter).Allow() + } + + return true +} + +// service限流 +func allowService(id string) bool { + key := hash(id) + + serviceLruCache.ContainsOrAdd(key, rate.NewLimiter(rate.Limit(ratelimitServiceRate), ratelimitServiceBurst)) + + value, ok := serviceLruCache.Get(key) + if ok { + return value.(*rate.Limiter).Allow() + } + + return true +} diff --git a/plugin/ratelimit/lrurate/lrurate.go b/plugin/ratelimit/lrurate/lrurate.go new file mode 100644 index 000000000..062f36160 --- /dev/null +++ b/plugin/ratelimit/lrurate/lrurate.go @@ -0,0 +1,148 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package lrurate + +import ( + "errors" + "github.com/polarismesh/polaris-server/plugin" +) + +const ( + PluginName = "lrurate" +) + +var ( + ratelimitIPLruSize int + ratelimitIPRate int + ratelimitIPBurst int + ratelimitServiceLruSize int + ratelimitServiceRate int + ratelimitServiceBurst int +) + +// 自注册到插件列表 +func init() { + plugin.RegisterPlugin(PluginName, &LRURate{}) +} + +// LRURate Ratelimit +type LRURate struct { +} + +// 返回插件名 +func (m *LRURate) Name() string { + return PluginName +} + +// 初始化函数 +func (m *LRURate) Initialize(c *plugin.ConfigEntry) error { + if err := parseRateLimitIPOption(c.Option); err != nil { + return err + } + if err := parseRateLimitServiceOption(c.Option); err != nil { + return err + } + + if err := initEnv(); err != nil { + return err + } + + return nil +} + +// 获取IP相关的参数 +func parseRateLimitIPOption(opt map[string]interface{}) error { + var ok bool + var val interface{} + + if val = opt["ratelimitIPLruSize"]; val == nil { + return errors.New("not found ratelimit::lrurate::ratelimitIPLruSize") + } + + if ratelimitIPLruSize, ok = val.(int); !ok || ratelimitIPLruSize <= 0 { + return errors.New("invalid ratelimit::lrurate::ratelimitIPLruSize, must be int and > 0") + } + + if val = opt["ratelimitIPRate"]; val == nil { + return errors.New("not found ratelimit::lrurate::ratelimitIPRate") + } + + if ratelimitIPRate, ok = val.(int); !ok || ratelimitIPRate <= 0 { + return errors.New("invalid ratelimit::lrurate::ratelimitIPRate, must be int and > 0") + } + + if val = opt["ratelimitIPBurst"]; val == nil { + return errors.New("not found ratelimit::lrurate::ratelimitIPBurst") + } + + if ratelimitIPBurst, ok = val.(int); !ok || ratelimitIPBurst <= 0 { + return errors.New("invalid ratelimit::lrurate::ratelimitIPBurst, must be int and > 0") + } + + return nil +} + +// 获取service相关的参数 +func parseRateLimitServiceOption(opt map[string]interface{}) error { + var ok bool + var val interface{} + + if val = opt["ratelimitServiceLruSize"]; val == nil { + return errors.New("not found ratelimit::lrurate::ratelimitServiceLruSize") + } + + if ratelimitServiceLruSize, ok = val.(int); !ok || ratelimitServiceLruSize <= 0 { + return errors.New("invalid ratelimit::lrurate::ratelimitServiceLruSize, must be int and > 0") + } + + if val = opt["ratelimitServiceRate"]; val == nil { + return errors.New("not found ratelimit::lrurate::ratelimitServiceRate") + } + + if ratelimitServiceRate, ok = val.(int); !ok || ratelimitServiceRate <= 0 { + return errors.New("invalid ratelimit::lrurate::ratelimitServiceRate, must be int and > 0") + } + + if val = opt["ratelimitServiceBurst"]; val == nil { + return errors.New("not found ratelimit::lrurate::ratelimitServiceBurst") + } + + if ratelimitServiceBurst, ok = val.(int); !ok || ratelimitServiceBurst <= 0 { + return errors.New("invalid ratelimit::lrurate::ratelimitServiceBurst, must be int and > 0") + } + + return nil +} + +// 销毁函数 +func (m *LRURate) Destroy() error { + return nil +} + +// 实现CMDB插件接口 +func (m *LRURate) Allow(rateType plugin.RatelimitType, id string) bool { + switch plugin.RatelimitType(rateType) { + case plugin.IPRatelimit: + return allowIP(id) + case plugin.ServiceRatelimit: + return allowService(id) + } + + // 默认允许访问 + return true +} diff --git a/plugin/ratelimit/lrurate/lrurate_test.go b/plugin/ratelimit/lrurate/lrurate_test.go new file mode 100644 index 000000000..e42bf0fd9 --- /dev/null +++ b/plugin/ratelimit/lrurate/lrurate_test.go @@ -0,0 +1,267 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package lrurate + +import ( + "github.com/polarismesh/polaris-server/plugin" + "testing" +) + +/** + * @brief 获取初始化的entry + */ +func getEntry() *plugin.ConfigEntry { + entry := plugin.ConfigEntry{ + Option: make(map[string]interface{}), + } + entry.Option["ratelimitIPLruSize"] = 10 + entry.Option["ratelimitIPRate"] = 10 + entry.Option["ratelimitIPBurst"] = 10 + entry.Option["ratelimitServiceLruSize"] = 10 + entry.Option["ratelimitServiceRate"] = 10 + entry.Option["ratelimitServiceBurst"] = 10 + + return &entry +} + +/** + * @brief 获取未初始化的entry + */ +func getUninitalizedEntry() *plugin.ConfigEntry { + entry := plugin.ConfigEntry{ + Option: make(map[string]interface{}), + } + + return &entry +} + +/** + * @brief 测试错误配置 + */ +func TestInvalidConfig(t *testing.T) { + entry := getUninitalizedEntry() + s := &LRURate{} + + t.Run("InvalidIPLruSize", func(t *testing.T) { + if err := s.Initialize(entry); err == nil { + t.Errorf("failed, shouldn't Initialize") + } + + entry.Option["ratelimitIPLruSize"] = 0 + if err := s.Initialize(entry); err == nil { + t.Errorf("failed, shouldn't Initialize") + } + }) + + t.Run("InvalidIPLruRate", func(t *testing.T) { + entry.Option["ratelimitIPLruSize"] = 10 + if err := s.Initialize(entry); err == nil { + t.Errorf("failed, shouldn't Initialize") + } + + entry.Option["ratelimitIPRate"] = 0 + if err := s.Initialize(entry); err == nil { + t.Errorf("failed, shouldn't Initialize") + } + }) + + t.Run("InvalidIPLruBurst", func(t *testing.T) { + entry.Option["ratelimitIPRate"] = 10 + if err := s.Initialize(entry); err == nil { + t.Errorf("failed, shouldn't Initialize") + } + + entry.Option["ratelimitIPBurst"] = 0 + if err := s.Initialize(entry); err == nil { + t.Errorf("failed, shouldn't Initialize") + } + }) + + t.Run("InvalidServiceLruSize", func(t *testing.T) { + entry.Option["ratelimitIPBurst"] = 10 + if err := s.Initialize(entry); err == nil { + t.Errorf("failed, shouldn't Initialize") + } + + entry.Option["ratelimitServiceLruSize"] = 0 + if err := s.Initialize(entry); err == nil { + t.Errorf("failed, shouldn't Initialize") + } + }) + + t.Run("InvalidServiceLruRate", func(t *testing.T) { + entry.Option["ratelimitServiceLruSize"] = 10 + if err := s.Initialize(entry); err == nil { + t.Errorf("failed, shouldn't Initialize") + } + + entry.Option["ratelimitServiceRate"] = 0 + if err := s.Initialize(entry); err == nil { + t.Errorf("failed, shouldn't Initialize") + } + }) + + t.Run("InvalidServiceLruBurst", func(t *testing.T) { + entry.Option["ratelimitServiceRate"] = 10 + if err := s.Initialize(entry); err == nil { + t.Errorf("failed, shouldn't Initialize") + } + + entry.Option["ratelimitServiceBurst"] = 0 + if err := s.Initialize(entry); err == nil { + t.Errorf("failed, shouldn't Initialize") + } + }) +} + +/** + * @brief 测试正确配置 + */ +func TestValidConfig(t *testing.T) { + t.Run("ValidConfig", func(t *testing.T) { + entry := getEntry() + s := &LRURate{} + if err := s.Initialize(entry); err != nil { + t.Errorf("failed: %s", err) + } else { + t.Logf("pass") + } + }) +} + +/** + * @brief 测试一般函数 + */ +func TestCommon(t *testing.T) { + entry := getEntry() + s := &LRURate{} + if err := s.Initialize(entry); err != nil { + t.Fatalf("failed: %s", err) + } else { + t.Logf("pass") + } + + t.Run("Name", func(t *testing.T) { + if s.Name() != "lrurate" { + t.Errorf("failed, invalid plgin name: %s", s.Name()) + } else { + t.Logf("pass") + } + }) + + t.Run("Destroy", func(t *testing.T) { + if s.Destroy() != nil { + t.Errorf("failed, bad Destroy") + } else { + t.Logf("pass") + } + }) +} + +/** + * @brief 测试限流功能 + */ +func TestRateLimit(t *testing.T) { + ipLruSize := 10 + ipRate := 100 + ipBurst := 200 + + serviceLruSize := 10 + serviceRate := 50 + serviceBurst := 100 + + entry := plugin.ConfigEntry{ + Option: make(map[string]interface{}), + } + entry.Option["ratelimitIPLruSize"] = ipLruSize + entry.Option["ratelimitIPRate"] = ipRate + entry.Option["ratelimitIPBurst"] = ipBurst + entry.Option["ratelimitServiceLruSize"] = serviceLruSize + entry.Option["ratelimitServiceRate"] = serviceRate + entry.Option["ratelimitServiceBurst"] = serviceBurst + + s := LRURate{} + if err := s.Initialize(&entry); err != nil { + t.Errorf("failed: %s", err) + } else { + t.Logf("pass") + } + + t.Run("RateLimit_UNKNOWN", func(t *testing.T) { + count := 0 + total := 2 * ipBurst + for i := 0; i < total; i++ { + if s.Allow(10, "19216811") { + count++ + } + } + + if count != total { + t.Errorf("failed, count: %d not %d", count, total) + } else { + t.Logf("pass") + } + }) + + t.Run("RateLimit_IP", func(t *testing.T) { + count := 0 + total := ipBurst + 10 + for i := 0; i < total; i++ { + if s.Allow(plugin.IPRatelimit, "19216811") { + count++ + } + } + + if count != ipBurst { + t.Errorf("failed, count: %d not %d", count, ipBurst) + } else { + t.Logf("pass") + } + }) + + t.Run("RateLimit_SERVICE_SERVICE", func(t *testing.T) { + count := 0 + total := serviceBurst + 10 + for i := 0; i < total; i++ { + if s.Allow(plugin.ServiceRatelimit, "hello_world") { + count++ + } + } + + if count != serviceBurst { + t.Errorf("failed, count: %d not %d", count, serviceBurst) + } else { + t.Logf("pass") + } + }) + + t.Run("RateLimit_SERVICE_SERVICEID", func(t *testing.T) { + count := 0 + for i := 0; i < serviceBurst+10; i++ { + if s.Allow(plugin.ServiceRatelimit, "helloworld") { + count++ + } + } + + if count != serviceBurst { + t.Errorf("failed, count: %d not %d", count, serviceBurst) + } else { + t.Logf("pass") + } + }) +} diff --git a/plugin/ratelimit/tokenBucket/api_limit.go b/plugin/ratelimit/tokenBucket/api_limit.go new file mode 100644 index 000000000..2536feba9 --- /dev/null +++ b/plugin/ratelimit/tokenBucket/api_limit.go @@ -0,0 +1,179 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package tokenBucket + +import ( + "errors" + "github.com/polarismesh/polaris-server/common/log" + "golang.org/x/time/rate" + "sync" +) + +// 接口限流类 +type apiRatelimit struct { + rules map[string]*BucketRatelimit // 存储规则 + apis *sync.Map // 存储api -> apiLimiter + config *APILimitConfig +} + +// 新建一个接口限流类 +func newAPIRatelimit(config *APILimitConfig) (*apiRatelimit, error) { + art := &apiRatelimit{} + if err := art.initialize(config); err != nil { + return nil, err + } + + return art, nil +} + +// 接口限流具体实现 +func (art *apiRatelimit) initialize(config *APILimitConfig) error { + art.config = config + if config == nil || !config.Open { + log.Infof("[Plugin][%s] api rate limit is not open", PluginName) + return nil + } + + log.Infof("[Plugin][%s] api ratelimit open", PluginName) + if err := art.parseRules(config.Rules); err != nil { + return err + } + if err := art.parseApis(config.Apis); err != nil { + return err + } + return nil +} + +// 解析限流规则 +func (art *apiRatelimit) parseRules(rules []*RateLimitRule) error { + if len(rules) == 0 { + return errors.New("invalid api rate limit config, rules are empty") + } + + art.rules = make(map[string]*BucketRatelimit) + for _, entry := range rules { + if entry.Name == "" { + return errors.New("invalid api rate limit config, some rules name are empty") + } + if entry.Limit == nil { + return errors.New("invalid api rate limit config, some rules limit are null") + } + if entry.Limit.Open && (entry.Limit.Bucket <= 0 || entry.Limit.Rate <= 0) { + return errors.New("invalid api rate limit config, rules bucket or rate is more than 0") + } + art.rules[entry.Name] = entry.Limit + } + + return nil +} + +// 解析每个api的限流 +func (art *apiRatelimit) parseApis(apis []*APILimitInfo) error { + if len(apis) == 0 { + return errors.New("invalid api rate limit config, apis are empty") + } + + art.apis = new(sync.Map) + for _, entry := range apis { + if entry.Name == "" { + return errors.New("invalid api rate limit config, api name is empty") + } + if entry.Rule == "" { + return errors.New("invalid api rate limit config, api rule is empty") + } + limit, ok := art.rules[entry.Rule] + if !ok { + return errors.New("invalid api rate limit config, api rule is not found") + } + art.createLimiter(entry.Name, limit) + } + + return nil +} + +// 创建一个私有limiter +func (art *apiRatelimit) createLimiter(name string, limit *BucketRatelimit) *apiLimiter { + limiter := newAPILimiter(name, limit.Open, limit.Rate, limit.Bucket) + art.apis.Store(name, limiter) + return limiter +} + +// 获取limiter +func (art *apiRatelimit) acquireLimiter(name string) *apiLimiter { + if value, ok := art.apis.Load(name); ok { + return value.(*apiLimiter) + } + + return nil +} + +// 系统是否开启API限流 +func (art *apiRatelimit) isOpen() bool { + return art.config != nil && art.config.Open +} + +// 令牌桶限流 +func (art *apiRatelimit) allow(name string) bool { + // 检查系统是否开启API限流 + // 系统不开启API限流,则返回true通过 + if !art.isOpen() { + return true + } + + limiter := art.acquireLimiter(name) + if limiter == nil { + // 找不到limiter,默认返回true + return true + } + + return limiter.Allow() +} + +// 封装rate.Limiter +// 每个API接口对应一个apiLimiter +type apiLimiter struct { + open bool // 该接口是否开启限流 + name string // 接口名 + *rate.Limiter // 令牌桶对象 +} + +// 新建一个apiLimiter +func newAPILimiter(name string, open bool, r int, b int) *apiLimiter { + limiter := &apiLimiter{ + open: false, + name: name, + Limiter: nil, + } + if !open { + return limiter + } + + limiter.open = true + limiter.Limiter = rate.NewLimiter(rate.Limit(r), b) + return limiter +} + +// 继承rate.Limiter.Allow函数 +func (a *apiLimiter) Allow() bool { + // 当前接口不开启限流 + if !a.open { + return true + } + + return a.Limiter.Allow() +} diff --git a/plugin/ratelimit/tokenBucket/api_limit_test.go b/plugin/ratelimit/tokenBucket/api_limit_test.go new file mode 100644 index 000000000..32183483b --- /dev/null +++ b/plugin/ratelimit/tokenBucket/api_limit_test.go @@ -0,0 +1,205 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package tokenBucket + +import ( + . "github.com/smartystreets/goconvey/convey" + "testing" + "time" +) + +// 校验新建apiRate +func newAPIRateLimitCheck(t *testing.T, config *APILimitConfig) *apiRatelimit { + apiLimit, err := newAPIRatelimit(config) + if err != nil { + t.Fatalf("error: %s", err.Error()) + } + return apiLimit +} + +// 正常场景测试 +func TestAPIRateLimitAllow(t *testing.T) { + config := &APILimitConfig{ + Open: true, + Rules: []*RateLimitRule{ + {Name: "rule-a", Limit: &BucketRatelimit{true, 10, 2}}, + {Name: "rule-b", Limit: &BucketRatelimit{true, 10, 1}}, + {Name: "rule-c", Limit: &BucketRatelimit{Open: false}}, + }, + Apis: []*APILimitInfo{ + {Name: "api1", Rule: "rule-a"}, + {Name: "api2", Rule: "rule-b"}, + {Name: "api3", Rule: "rule-c"}, + }, + } + limiter := newAPIRateLimitCheck(t, config) + Convey("正常请求,令牌桶限流可以生效", t, func() { + allowCnt := 0 + for i := 0; i < limiter.rules["rule-a"].Bucket*2; i++ { + if ok := limiter.allow("api1"); ok { + allowCnt++ + } + } + So(allowCnt, ShouldEqual, limiter.rules["rule-a"].Bucket) + }) + Convey("持续请求,不超过限制,可以一直请求下去", t, func() { + for i := 0; i < 15; i++ { + So(limiter.allow("api2"), ShouldEqual, true) + time.Sleep(time.Millisecond*1 + time.Second) + } + }) + Convey("api不限制,可以随便请求", t, func() { + for i := 0; i < limiter.rules["rule-c"].Bucket*2; i++ { + So(limiter.allow("api3"), ShouldEqual, true) + } + }) + Convey("api不存在rule,不做限制", t, func() { + cnt := 0 + for i := 0; i < 10000; i++ { + cnt++ + } + So(cnt, ShouldEqual, 10000) + }) +} + +// 配置校验 +func TestAPILimitConfig(t *testing.T) { + Convey("api-limit配置为空,可以正常执行", t, func() { + limiter := newAPIRateLimitCheck(t, nil) + So(limiter, ShouldNotBeNil) + So(limiter.isOpen(), ShouldEqual, false) + }) + Convey("可以通过系统open开关,关闭api限流", t, func() { + config := &APILimitConfig{ + Open: false, + Rules: []*RateLimitRule{ + {Name: "rule-1", + Limit: &BucketRatelimit{Open: true, Bucket: 10, Rate: 5}}, + }, + Apis: []*APILimitInfo{{Name: "api-1", Rule: "rule-1"}}, + } + limiter := newAPIRateLimitCheck(t, config) + So(limiter.isOpen(), ShouldEqual, false) + for i := 0; i < 15; i++ { + So(limiter.allow("api-1"), ShouldEqual, true) + } + }) + Convey("rules为空,报错", t, func() { + config := &APILimitConfig{Open: true} + limiter, err := newAPIRatelimit(config) + So(err, ShouldNotBeNil) + So(limiter, ShouldBeNil) + }) + Convey("api为空,报错", t, func() { + config := &APILimitConfig{ + Open: true, + Rules: []*RateLimitRule{ + {Name: "rule-1", + Limit: &BucketRatelimit{Open: true, Bucket: 10, Rate: 5}}, + }, + Apis: []*APILimitInfo{}, + } + _, err := newAPIRatelimit(config) + So(err, ShouldNotBeNil) + }) +} + +// api-limit规则配置测试 +func TestAPILimitConfigRule(t *testing.T) { + config := &APILimitConfig{ + Open: true, + Apis: []*APILimitInfo{{Name: "api-1", Rule: "rule-1"}}, + } + Convey("rules内部参数,name不能为空", t, func() { + config.Rules = []*RateLimitRule{ + {Name: "", + Limit: &BucketRatelimit{Open: true, Bucket: 0, Rate: 5}}, + } + _, err := newAPIRatelimit(config) + So(err, ShouldNotBeNil) + }) + Convey("rules内部参数,limit不能为空", t, func() { + config.Rules = []*RateLimitRule{ + {Name: "rule-1", Limit: nil}, + } + _, err := newAPIRatelimit(config) + So(err, ShouldNotBeNil) + }) + Convey("rules内部参数,open为false,bucket和rate可以是任意值", t, func() { + config.Rules = []*RateLimitRule{ + {Name: "", + Limit: &BucketRatelimit{Open: false}}, + } + _, err := newAPIRatelimit(config) + So(err, ShouldNotBeNil) + }) + Convey("rules内部参数,open的规则,bucket和rate必须大于0", t, func() { + config.Rules = []*RateLimitRule{ + {Name: "rule-1", + Limit: &BucketRatelimit{Open: true, Bucket: 0, Rate: 5}}, + } + _, err := newAPIRatelimit(config) + So(err, ShouldNotBeNil) + t.Logf("%s", err.Error()) + + config.Rules[0].Limit.Bucket = 10 + config.Rules[0].Limit.Rate = 0 + _, err = newAPIRatelimit(config) + So(err, ShouldNotBeNil) + t.Logf("%s", err.Error()) + }) +} + +// apis配置测试 +func TestAPILimitConfigApi(t *testing.T) { + config := &APILimitConfig{ + Open: true, + Rules: []*RateLimitRule{ + {Name: "rule-1", + Limit: &BucketRatelimit{Open: true, Bucket: 10, Rate: 5}}, + }, + } + Convey("apis内部参数,apis为空,返回错误", t, func() { + _, err := newAPIRatelimit(config) + So(err, ShouldNotBeNil) + t.Logf("%s", err.Error()) + }) + Convey("apis内部参数,部分参数为空,返回错误", t, func() { + config.Apis = []*APILimitInfo{{Name: "", Rule: ""}} + _, err := newAPIRatelimit(config) + So(err, ShouldNotBeNil) + t.Logf("%s", err.Error()) + + config.Apis[0].Name = "123" + _, err = newAPIRatelimit(config) + So(err, ShouldNotBeNil) + t.Logf("%s", err.Error()) + + config.Apis[0].Rule = "rule-1" + newAPIRateLimitCheck(t, config) + }) + Convey("api内部参数,rule不存在,返回错误", t, func() { + config.Apis = []*APILimitInfo{{Name: "aaa", Rule: "bbb"}} + _, err := newAPIRatelimit(config) + So(err, ShouldNotBeNil) + t.Logf("%s", err.Error()) + + config.Apis[0].Rule = "rule-1" + newAPIRateLimitCheck(t, config) + }) +} diff --git a/plugin/ratelimit/tokenBucket/config.go b/plugin/ratelimit/tokenBucket/config.go new file mode 100644 index 000000000..cac49b859 --- /dev/null +++ b/plugin/ratelimit/tokenBucket/config.go @@ -0,0 +1,121 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package tokenBucket + +import ( + "fmt" + "github.com/polarismesh/polaris-server/common/log" + "github.com/mitchellh/mapstructure" +) + +// 限流配置类 +type Config struct { + // 是否启用远程配置,默认false。TODO 暂时无远程配置,后续版本补全 + RemoteConf bool `mapstructure:"remote-conf"` + + // IP限流相关配置 + IPLimitConf *ResourceLimitConfig `mapstructure:"ip-limit"` + + // 接口限流相关配置 + APILimitConf *APILimitConfig `mapstructure:"api-limit"` + + // 基于实例的限流配置 + InstanceLimitConf *ResourceLimitConfig `mapstructure:"instance-limit"` +} + +// 针对令牌桶的具体配置 +type BucketRatelimit struct { + // 是否开启限流 + Open bool `mapstructure:"open"` + + // 令牌桶大小 + Bucket int `mapstructure:"bucket"` + + // 每秒加入的令牌数 + Rate int `mapstructure:"rate"` +} + +// 基于资源的限流配置 +// 资源可以是:IP,实例,服务等 +type ResourceLimitConfig struct { + // 是否开启instance限流 + Open bool `mapstructure:"open"` + + // 全局限制规则,只有一条规则 + Global *BucketRatelimit `mapstructure:"global"` + + // 本地缓存最大多少个instance的限制器 + MaxResourceCacheAmount int `mapstructure:"resource-cache-amount"` + + // 白名单 + WhiteList []string `mapstructure:"white-list"` +} + +// api限流配置 +type APILimitConfig struct { + // 系统是否开启API限流 + Open bool `mapstructure:"open"` + + // 配置规则集合 + Rules []*RateLimitRule `mapstructure:"rules"` + + // 每个接口的单独配置 + Apis []*APILimitInfo `mapstructure:"apis"` +} + +// 限流规则 +type RateLimitRule struct { + // 规则名 + Name string `mapstructure:"name"` + + // 规则的限制 + Limit *BucketRatelimit `mapstructure:"limit"` +} + +// 每个接口的单独配置信息 +type APILimitInfo struct { + // 接口名,比如对于HTTP,就是:方法+URL + Name string `mapstructure:"name"` + + // 限制规则名 + Rule string `mapstructure:"rule"` +} + +// 把map解码为Config对象 +func decodeConfig(data map[string]interface{}) (*Config, error) { + if data == nil { + return nil, fmt.Errorf("plugin(%s) option is empty", PluginName) + } + var config Config + if err := mapstructure.Decode(data, &config); err != nil { + log.Errorf("[Plugin][%s] decode config err: %s", PluginName, err.Error()) + return nil, err + } + + /*log.Infof("%+v", config) + log.Infof("%+v", config.IPLimitConf.Global) + log.Infof("%+v", config.APILimitConf) + for _, entry := range config.APILimitConf.Rules { + log.Infof("%s->%+v", entry.Name, entry.Limit) + } + for _, entry := range config.APILimitConf.Apis { + log.Infof("%+v", entry) + }*/ + + return &config, nil +} diff --git a/plugin/ratelimit/tokenBucket/implement.go b/plugin/ratelimit/tokenBucket/implement.go new file mode 100644 index 000000000..bdf18af51 --- /dev/null +++ b/plugin/ratelimit/tokenBucket/implement.go @@ -0,0 +1,72 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package tokenBucket + +import ( + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/plugin" +) + +// 插件初始化函数 +func (tb *tokenBucket) initialize(c *plugin.ConfigEntry) error { + config, err := decodeConfig(c.Option) + if err != nil { + log.Errorf("[Plugin][%s] initialize err: %s", PluginName, err.Error()) + return err + } + + tb.config = config + tb.limiters = make(map[plugin.RatelimitType]limiter) + + // IP限流 + irt, err := newResourceRatelimit(plugin.IPRatelimit, config.IPLimitConf) + if err != nil { + return err + } + tb.limiters[plugin.IPRatelimit] = irt + + // 接口限流 + art, err := newAPIRatelimit(config.APILimitConf) + if err != nil { + return err + } + tb.limiters[plugin.APIRatelimit] = art + + // 操作实例限流 + instance, err := newResourceRatelimit(plugin.InstanceRatelimit, config.InstanceLimitConf) + if err != nil { + return err + } + tb.limiters[plugin.InstanceRatelimit] = instance + + return nil +} + +// 插件的限流实现函数 +func (tb *tokenBucket) allow(typ plugin.RatelimitType, key string) bool { + // key为空,则不作限制 + if key == "" { + return true + } + l, ok := tb.limiters[typ] + if !ok { + return true + } + + return l.allow(key) +} diff --git a/plugin/ratelimit/tokenBucket/invoke.go b/plugin/ratelimit/tokenBucket/invoke.go new file mode 100644 index 000000000..2575a4f2b --- /dev/null +++ b/plugin/ratelimit/tokenBucket/invoke.go @@ -0,0 +1,48 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package tokenBucket + +import ( + "github.com/polarismesh/polaris-server/plugin" +) + +// 实现Plugin接口 +type tokenBucket struct { + config *Config + limiters map[plugin.RatelimitType]limiter +} + +// 实现Plugin接口,Name方法 +func (tb *tokenBucket) Name() string { + return PluginName +} + +// 实现Plugin接口,Initialize方法 +func (tb *tokenBucket) Initialize(c *plugin.ConfigEntry) error { + return tb.initialize(c) +} + +// 实现Plugin接口,Destroy方法 +func (tb *tokenBucket) Destroy() error { + return nil +} + +// 限流接口实现 +func (tb *tokenBucket) Allow(typ plugin.RatelimitType, key string) bool { + return tb.allow(typ, key) +} diff --git a/plugin/ratelimit/tokenBucket/invoke_test.go b/plugin/ratelimit/tokenBucket/invoke_test.go new file mode 100644 index 000000000..bc43d5d46 --- /dev/null +++ b/plugin/ratelimit/tokenBucket/invoke_test.go @@ -0,0 +1,126 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package tokenBucket + +import ( + "github.com/polarismesh/polaris-server/plugin" + . "github.com/smartystreets/goconvey/convey" + "testing" +) + +// 返回一个基础的正常option配置 +func baseConfigOption() map[string]interface{} { + return map[string]interface{}{ + "ip-limit": &ResourceLimitConfig{ + Open: true, + Global: &BucketRatelimit{true, 10, 2}, + MaxResourceCacheAmount: 100, + }, + "api-limit": &APILimitConfig{ + Open: true, + Rules: []*RateLimitRule{{ + Name: "rule-1", + Limit: &BucketRatelimit{true, 5, 1}, + }}, + Apis: []*APILimitInfo{{Name: "api-1", Rule: "rule-1"}}, + }, + } +} + +// 对插件名字接口实现的测试 +func TestTokenBucket_Name(t *testing.T) { + tb := &tokenBucket{} + Convey("返回插件名服务预期", t, func() { + So(tb.Name(), ShouldEqual, PluginName) + }) +} + +// 测试初始化函数 +func TestTokenBucket_Initialize(t *testing.T) { + configEntry := &plugin.ConfigEntry{Name: PluginName} + tb := &tokenBucket{} + Convey("配置option为空,返回失败", t, func() { + So(tb.Initialize(configEntry), ShouldNotBeNil) + }) + Convey("配置字段不对,不影响解析,配置不生效", t, func() { + configEntry.Option = map[string]interface{}{"aaa": 123} + So(tb.Initialize(configEntry), ShouldBeNil) + }) + Convey("无效ip-limit配置,返回失败", t, func() { + configEntry.Option = map[string]interface{}{ + "ip-limit": &ResourceLimitConfig{ + Open: true, + Global: nil, + MaxResourceCacheAmount: 100, + }, + } + So(tb.Initialize(configEntry), ShouldNotBeNil) + }) + Convey("无效api-limit配置,返回失败", t, func() { + configEntry.Option = map[string]interface{}{ + "api-limit": &APILimitConfig{Open: true}, + } + So(tb.Initialize(configEntry), ShouldNotBeNil) + }) + Convey("配置有效,可以正常初始化", t, func() { + configEntry.Option = baseConfigOption() + So(tb.Initialize(configEntry), ShouldBeNil) + So(tb.limiters[plugin.IPRatelimit], ShouldNotBeNil) + So(tb.limiters[plugin.APIRatelimit], ShouldNotBeNil) + }) +} + +// 测试Allow函数 +func TestTokenBucket_Allow(t *testing.T) { + configEntry := &plugin.ConfigEntry{Name: PluginName} + configEntry.Option = baseConfigOption() + tb := &tokenBucket{} + if err := tb.Initialize(configEntry); err != nil { + t.Fatalf("error: %s", err.Error()) + } + ipLimiter := tb.limiters[plugin.IPRatelimit].(*resourceRatelimit) + apiLimiter := tb.limiters[plugin.APIRatelimit].(*apiRatelimit) + Convey("IP正常限流", t, func() { + cnt := 0 + for i := 0; i < ipLimiter.config.Global.Bucket*2; i++ { + if ok := tb.Allow(plugin.IPRatelimit, "1.2.3.4"); ok { + cnt++ + } + } + So(cnt, ShouldEqual, ipLimiter.config.Global.Bucket) + // 其他IP可以正常通过 + So(tb.Allow(plugin.IPRatelimit, "2.3.4.5"), ShouldEqual, true) + }) + Convey("api正常限流", t, func() { + cnt := 0 + for i := 0; i < apiLimiter.rules["rule-1"].Bucket*2; i++ { + if ok := tb.Allow(plugin.APIRatelimit, "api-1"); ok { + cnt++ + } + } + So(cnt, ShouldEqual, apiLimiter.rules["rule-1"].Bucket) + // 其他接口没有限流的可以通过 + So(tb.Allow(plugin.APIRatelimit, "api-2"), ShouldEqual, true) + }) + Convey("空的key,正常限流", t, func() { + So(tb.Allow(plugin.APIRatelimit, ""), ShouldEqual, true) + }) + Convey("非法的限制类型,直接通过", t, func() { + So(tb.Allow(plugin.RatelimitType(100), "123"), ShouldEqual, true) + }) +} diff --git a/plugin/ratelimit/tokenBucket/limiter.go b/plugin/ratelimit/tokenBucket/limiter.go new file mode 100644 index 000000000..2d364dd10 --- /dev/null +++ b/plugin/ratelimit/tokenBucket/limiter.go @@ -0,0 +1,23 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package tokenBucket + +// 限制器 +type limiter interface { + allow(key string) bool +} diff --git a/plugin/ratelimit/tokenBucket/register.go b/plugin/ratelimit/tokenBucket/register.go new file mode 100644 index 000000000..a08075861 --- /dev/null +++ b/plugin/ratelimit/tokenBucket/register.go @@ -0,0 +1,29 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package tokenBucket + +import "github.com/polarismesh/polaris-server/plugin" + +const ( + PluginName = "token-bucket" +) + +// 插件入口注册函数 +func init() { + plugin.RegisterPlugin(PluginName, &tokenBucket{}) +} diff --git a/plugin/ratelimit/tokenBucket/resource_limiter.go b/plugin/ratelimit/tokenBucket/resource_limiter.go new file mode 100644 index 000000000..b5f5a3b1a --- /dev/null +++ b/plugin/ratelimit/tokenBucket/resource_limiter.go @@ -0,0 +1,116 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package tokenBucket + +import ( + "fmt" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/plugin" + lru "github.com/hashicorp/golang-lru" + "golang.org/x/time/rate" +) + +// 资源限制器 +type resourceRatelimit struct { + typStr string + resources *lru.Cache + whiteList map[string]bool + config *ResourceLimitConfig +} + +// 新建资源限制器 +func newResourceRatelimit(typ plugin.RatelimitType, config *ResourceLimitConfig) (*resourceRatelimit, error) { + r := &resourceRatelimit{typStr: plugin.RatelimitStr[typ]} + if err := r.initialize(config); err != nil { + return nil, err + } + + return r, nil +} + +// initialize +func (r *resourceRatelimit) initialize(config *ResourceLimitConfig) error { + r.config = config + if config == nil || config.Open == false { + log.Infof("[Plugin][%s] resource(%s) ratelimit is not open", PluginName, r.typStr) + return nil + } + + if config.Global == nil { + return fmt.Errorf("resource(%s) global ratelimit rule is empty", r.typStr) + } + if config.Global.Bucket <= 0 || config.Global.Rate <= 0 { + return fmt.Errorf("resource(%s) ratelimit global bucket or rate invalid", r.typStr) + } + if config.MaxResourceCacheAmount <= 0 { + return fmt.Errorf("resource(%s) max resource amount is invalid", r.typStr) + } + + cache, err := lru.New(config.MaxResourceCacheAmount) + if err != nil { + log.Errorf("[Plugin][%s] resource(%s) ratelimit create new lru cache err: %s", + PluginName, r.typStr, err.Error()) + return err + } + r.resources = cache + + r.whiteList = make(map[string]bool) + for _, item := range config.WhiteList { + r.whiteList[item] = true + } + + log.Infof("[Plugin][%s] resource(%s) ratelimit open", PluginName, r.typStr) + return nil +} + +// 限流是否开启 +func (r *resourceRatelimit) isOpen() bool { + return r.config != nil && r.config.Open +} + +// 检查是否属于白名单,属于的话,则不限流 +func (r *resourceRatelimit) isWhiteList(key string) bool { + _, ok := r.whiteList[key] + return ok +} + +// 实现limiter +func (r *resourceRatelimit) allow(key string) bool { + if ok := r.isOpen(); !ok { + return true + } + if ok := r.isWhiteList(key); ok { + return true + } + + value, ok := r.resources.Get(key) + if !ok { + r.resources.ContainsOrAdd(key, + rate.NewLimiter(rate.Limit(r.config.Global.Rate), r.config.Global.Bucket)) + // 上面已经加了value,这里正常情况会有value + value, ok = r.resources.Get(key) + if !ok { + // 还找不到,打印日志,返回true + log.Warnf("[Plugin][%s] not found the resources(%s) key(%s) in the cache", + PluginName, r.typStr, key) + return true + } + } + + return value.(*rate.Limiter).Allow() +} diff --git a/plugin/ratelimit/tokenBucket/resource_limiter_test.go b/plugin/ratelimit/tokenBucket/resource_limiter_test.go new file mode 100644 index 000000000..4c1bcba7f --- /dev/null +++ b/plugin/ratelimit/tokenBucket/resource_limiter_test.go @@ -0,0 +1,166 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package tokenBucket + +import ( + "fmt" + "github.com/polarismesh/polaris-server/plugin" + . "github.com/smartystreets/goconvey/convey" + "testing" + "time" +) + +// 测试新建 +func TestNewResourceRatelimit(t *testing.T) { + Convey("测试新建一个资源限制器", t, func() { + Convey("config为空", func() { + limiter, err := newResourceRatelimit(plugin.InstanceRatelimit, nil) + So(limiter, ShouldNotBeNil) + So(err, ShouldBeNil) + }) + Convey("不开启限制器", func() { + limiter, err := newResourceRatelimit(plugin.InstanceRatelimit, &ResourceLimitConfig{ + Open: false, + }) + So(limiter, ShouldNotBeNil) + So(err, ShouldBeNil) + So(limiter.allow("11111"), ShouldBeTrue) + }) + Convey("开启了限制器,global为空", func() { + limiter, err := newResourceRatelimit(plugin.InstanceRatelimit, &ResourceLimitConfig{ + Open: true, + }) + So(limiter, ShouldBeNil) + So(err, ShouldNotBeNil) + t.Logf("%s", err.Error()) + }) + Convey("开启了限制器,global其他参数不合法", func() { + limiter, err := newResourceRatelimit(plugin.InstanceRatelimit, &ResourceLimitConfig{ + Open: true, + Global: &BucketRatelimit{}, + }) + So(limiter, ShouldBeNil) + So(err, ShouldNotBeNil) + + limiter, err = newResourceRatelimit(plugin.InstanceRatelimit, &ResourceLimitConfig{ + Open: true, + Global: &BucketRatelimit{true, 10, 10}, + }) + So(limiter, ShouldBeNil) + So(err, ShouldNotBeNil) + + limiter, err = newResourceRatelimit(plugin.InstanceRatelimit, &ResourceLimitConfig{ + Open: true, + Global: &BucketRatelimit{true, 10, 10}, + MaxResourceCacheAmount: -1, + }) + So(limiter, ShouldBeNil) + So(err, ShouldNotBeNil) + }) + Convey("正常新建限制器", func() { + limiter, err := newResourceRatelimit(plugin.InstanceRatelimit, &ResourceLimitConfig{ + Open: true, + Global: &BucketRatelimit{true, 10, 5}, + MaxResourceCacheAmount: 10, + }) + So(limiter, ShouldNotBeNil) + So(err, ShouldBeNil) + }) + Convey("白名单正常解析", func() { + limiter, err := newResourceRatelimit(plugin.InstanceRatelimit, &ResourceLimitConfig{ + Open: true, + Global: &BucketRatelimit{true, 10, 5}, + MaxResourceCacheAmount: 10, + WhiteList: []string{"1", "2", "3"}, + }) + So(limiter, ShouldNotBeNil) + So(err, ShouldBeNil) + So(len(limiter.whiteList), ShouldEqual, 3) + }) + }) +} + +// 测试allow +func TestResourceAllow(t *testing.T) { + Convey("测试allow", t, func() { + Convey("正常限流", func() { + limiter, err := newResourceRatelimit(plugin.InstanceRatelimit, &ResourceLimitConfig{ + Open: true, + Global: &BucketRatelimit{true, 5, 5}, + MaxResourceCacheAmount: 2, + }) + So(err, ShouldBeNil) + cnt := 0 + for i := 0; i <= limiter.config.Global.Rate*2; i++ { + if ok := limiter.allow("12345"); ok { + cnt++ + } + } + So(cnt, ShouldEqual, limiter.config.Global.Rate) + // 其他key,可以通过 + So(limiter.allow("67890"), ShouldBeTrue) + + // 1秒之后,12345又可以通过 + time.Sleep(time.Second + time.Millisecond*10) + So(limiter.allow("12345"), ShouldBeTrue) + So(limiter.allow("67890"), ShouldBeTrue) + So(limiter.allow("13579"), ShouldBeTrue) + }) + Convey("max-resource测试", func() { + limiter, err := newResourceRatelimit(plugin.InstanceRatelimit, &ResourceLimitConfig{ + Open: true, + Global: &BucketRatelimit{true, 5, 5}, + MaxResourceCacheAmount: 2, + }) + So(err, ShouldBeNil) + cnt := 0 + for i := 0; i < limiter.config.Global.Rate*20; i++ { + if ok := limiter.allow(fmt.Sprintf("key-%d", i)); ok { + cnt++ + } + } + // 不同key,全部通过 + So(cnt, ShouldEqual, limiter.config.Global.Rate*20) + }) + Convey("白名单测试", func() { + limiter, err := newResourceRatelimit(plugin.InstanceRatelimit, &ResourceLimitConfig{ + Open: true, + Global: &BucketRatelimit{true, 5, 5}, + MaxResourceCacheAmount: 1024, + WhiteList: []string{"1000", "1001", "1002"}, + }) + So(err, ShouldBeNil) + + cnt := 0 + for i := 0; i < limiter.config.Global.Rate*3; i++ { + if ok := limiter.allow("1003"); ok { + cnt++ + } + } + So(cnt, ShouldEqual, limiter.config.Global.Rate) + + cnt = 0 + for i := 0; i < limiter.config.Global.Rate*30; i++ { + if ok := limiter.allow("1002"); ok { + cnt++ + } + } + So(cnt, ShouldEqual, limiter.config.Global.Rate*30) + }) + }) +} diff --git a/plugin/statis.go b/plugin/statis.go new file mode 100644 index 000000000..7f6314a68 --- /dev/null +++ b/plugin/statis.go @@ -0,0 +1,59 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package plugin + +import ( + "os" + "sync" + + "github.com/polarismesh/polaris-server/common/log" +) + +var ( + statisOnce = &sync.Once{} +) + +/** + * @brief 统计插件接口 + */ +type Statis interface { + Plugin + + AddAPICall(api string, code int, duration int64) error +} + +/** + * @brief 获取统计插件 + */ +func GetStatis() Statis { + c := &config.Statis + + plugin, exist := pluginSet[c.Name] + if !exist { + return nil + } + + statisOnce.Do(func() { + if err := plugin.Initialize(c); err != nil { + log.Errorf("plugin init err: %s", err.Error()) + os.Exit(-1) + } + }) + + return plugin.(Statis) +} diff --git a/plugin/statis/local/README.md b/plugin/statis/local/README.md new file mode 100644 index 000000000..92f2a059e --- /dev/null +++ b/plugin/statis/local/README.md @@ -0,0 +1 @@ +# 统计打印到本地日志 diff --git a/plugin/statis/local/apicall.go b/plugin/statis/local/apicall.go new file mode 100644 index 000000000..56e00887f --- /dev/null +++ b/plugin/statis/local/apicall.go @@ -0,0 +1,110 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package local + +import ( + "fmt" + + "go.uber.org/zap" +) + +/** + * @brief 接口调用 + */ +type APICall struct { + api string + code int + duration int64 +} + +/** + * @brief 接口调用统计条目 + */ +type APICallStatisItem struct { + api string + code int + count int64 + accTime int64 + minTime int64 + maxTime int64 +} + +/** + * @brief 接口调用统计 + */ +type APICallStatis struct { + statis map[string]*APICallStatisItem + + logger *zap.Logger +} + +/** + * @brief 添加接口调用数据 + */ +func (a *APICallStatis) add(ac *APICall) { + index := fmt.Sprintf("%v-%v", ac.api, ac.code) + + item, exist := a.statis[index] + if exist { + item.count++ + + item.accTime += ac.duration + if ac.duration < item.minTime { + item.minTime = ac.duration + } + if ac.duration > item.maxTime { + item.maxTime = ac.duration + } + } else { + a.statis[index] = &APICallStatisItem{ + api: ac.api, + code: ac.code, + count: 1, + accTime: ac.duration, + minTime: ac.duration, + maxTime: ac.duration, + } + } +} + +/** + * @brief 打印接口调用统计 + */ +func (a *APICallStatis) log() { + if len(a.statis) == 0 { + a.logger.Info("Statis: No API Call\n") + return + } + + msg := "Statis:\n" + + msg += fmt.Sprintf("%-48v|%12v|%12v|%12v|%12v|%12v|\n", "", "Code", "Count", "Min(ms)", "Max(ms)", "Avg(ms)") + + for _, item := range a.statis { + msg += fmt.Sprintf("%-48v|%12v|%12v|%12.3f|%12.3f|%12.3f|\n", + item.api, item.code, item.count, + float32(item.minTime)/1e6, + float32(item.maxTime)/1e6, + float32(item.accTime)/float32(item.count)/1e6, + ) + } + + a.logger.Info(msg) + + a.statis = make(map[string]*APICallStatisItem) +} diff --git a/plugin/statis/local/local.go b/plugin/statis/local/local.go new file mode 100644 index 000000000..5c0ad1849 --- /dev/null +++ b/plugin/statis/local/local.go @@ -0,0 +1,108 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package local + +import ( + "time" + + "github.com/polarismesh/polaris-server/plugin" +) + +/** + * @brief 注册统计插件 + */ +func init() { + s := &StatisWorker{} + plugin.RegisterPlugin(s.Name(), s) +} + +/** + * @brief 本地统计插件 + */ +type StatisWorker struct { + interval time.Duration + + acc chan *APICall + acs *APICallStatis +} + +/** + * @brief 获取统计插件名称 + */ +func (s *StatisWorker) Name() string { + return "local" +} + +/** + * @brief 初始化统计插件 + */ +func (s *StatisWorker) Initialize(conf *plugin.ConfigEntry) error { + // 设置统计打印周期 + interval := conf.Option["interval"].(int) + s.interval = time.Duration(interval) * time.Second + + outputPath := conf.Option["outputPath"].(string) + + // 初始化接口调用统计 + s.acc = make(chan *APICall, 1024) + s.acs = &APICallStatis{ + statis: make(map[string]*APICallStatisItem), + logger: newLogger(outputPath + "/" + "apicall.log"), + } + + go s.Run() + + return nil +} + +/** + * @brief 销毁统计插件 + */ +func (s *StatisWorker) Destroy() error { + return nil +} + +/** + * @brief 上报请求 + */ +func (s *StatisWorker) AddAPICall(api string, code int, duration int64) error { + s.acc <- &APICall{ + api: api, + code: code, + duration: duration, + } + + return nil +} + +/** + * @brief 主流程 + */ +func (s *StatisWorker) Run() { + ticker := time.NewTicker(s.interval) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + s.acs.log() + case ac := <-s.acc: + s.acs.add(ac) + } + } +} diff --git a/plugin/statis/local/logger.go b/plugin/statis/local/logger.go new file mode 100644 index 000000000..1fc1ff8b5 --- /dev/null +++ b/plugin/statis/local/logger.go @@ -0,0 +1,52 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package local + +import ( + "github.com/natefinch/lumberjack" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +/** + * @brief 创建日志打印器 + */ +func newLogger(file string) *zap.Logger { + encCfg := zapcore.EncoderConfig{ + MessageKey: "msg", + //LevelKey: "level", + TimeKey: "time", + NameKey: "name", + CallerKey: "caller", + StacktraceKey: "stacktrace", + LineEnding: zapcore.DefaultLineEnding, + EncodeLevel: zapcore.LowercaseLevelEncoder, + EncodeTime: zapcore.ISO8601TimeEncoder, + EncodeDuration: zapcore.StringDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + } + + w := zapcore.AddSync(&lumberjack.Logger{ + Filename: file, + MaxSize: 100, // MB + MaxBackups: 10, + MaxAge: 7, // days + }) + + return zap.New(zapcore.NewCore(zapcore.NewConsoleEncoder(encCfg), w, zap.InfoLevel)) +} diff --git a/polaris-server.yaml b/polaris-server.yaml new file mode 100644 index 000000000..a1ee95d19 --- /dev/null +++ b/polaris-server.yaml @@ -0,0 +1,182 @@ +# server启动引导配置 +bootstrap: + # 全局日志 + logger: + RotateOutputPath: log/polaris-server.log + RotationMaxSize: 500 + RotationMaxAge: 30 + RotationMaxBackups: 100 + level: info + # 按顺序启动server + startInOrder: + open: true # 是否开启,默认是关闭 + key: sz # 全局锁 + # 注册为北极星服务 + polaris_service: + probe_address: 119.91.74.45:3306 + enable_register: false + isolated: true + services: + - name: polaris-server + namespace: Polaris + protocols: + - grpcserver + - httpserver + - l5pbserver +# apiserver配置 +apiservers: + - name: httpserver # 协议名,全局唯一 + option: + listenIP: "0.0.0.0" + listenPort: 8090 + enablePprof: false # debug pprof + connLimit: + openConnLimit: false + maxConnPerHost: 128 + maxConnLimit: 5120 + whiteList: 127.0.0.1 + purgeCounterInterval: 10s + purgeCounterExpired: 5s + api: + admin: + enable: true + console: + enable: true + include: [default] + client: + enable: true + include: [discover, register, healthcheck] + - name: grpcserver + option: + listenIP: "0.0.0.0" + listenPort: 8091 + connLimit: + openConnLimit: false + maxConnPerHost: 128 + maxConnLimit: 5120 + api: + client: + enable: true + include: [discover, register, healthcheck] + - name: l5pbserver + option: + listenIP: 0.0.0.0 + listenPort: 7779 + clusterName: cl5.discover +# 核心逻辑的配置 +naming: + # 鉴权配置 + auth: + # 是否开启鉴权 + open: false + # 健康检查 + healthcheck: + open: true +# kvConnNum: 50 +# kvServiceName: polarisHeartbeatCkv +# kvNamespace: Polaris +# kvPasswd: polaris@2021 + slotNum: 30 + maxIdle: 20 + idleTimeout: 120 + # 批量控制器 + batch: + register: + open: true + queueSize: 10240 + waitTime: 32ms + maxBatchCount: 32 + concurrency: 64 + deregister: + open: true + queueSize: 10240 + waitTime: 32ms + maxBatchCount: 32 + concurrency: 64 +# 缓存配置 +cache: + open: true + resources: + - name: service # 加载服务数据 + option: + disableBusiness: false # 不加载业务服务 + needMeta: true # 加载服务元数据 + - name: instance # 加载实例数据 + option: + disableBusiness: false # 不加载业务服务实例 + needMeta: true # 加载实例元数据 + - name: routingConfig # 加载路由数据 + - name: rateLimitConfig # 加载限流数据 + - name: circuitBreakerConfig # 加载熔断数据 + - name: l5 # 加载l5数据 +# 存储配置 +store: + name: defaultStore + option: + master: + dbType: mysql + dbUser: root + dbPwd: "Polaris@2021" + dbAddr: 119.91.74.45:3306 + dbName: polaris_server + maxOpenConns: 64 + maxIdleConns: 2 + connMaxLifetime: 300 # 单位秒 + txIsolationLevel: 2 #LevelReadCommitted +# 插件配置 +plugin: + history: + name: HistoryLogger + discoverStatis: + name: discoverLocal + option: + interval: 60 # 统计间隔,单位为秒 + outputPath: ./discover-statis + statis: + name: local + option: + interval: 60 # 统计间隔,单位为秒 + outputPath: ./statis + ratelimit: + name: token-bucket + option: + remote-conf: false # 是否使用远程配置 + ip-limit: # ip级限流,全局 + open: true # 系统是否开启ip级限流 + global: + open: true + bucket: 300 # 最高峰值 + rate: 200 # 平均一个IP每秒的请求数 + resource-cache-amount: 1024 # 最大缓存的IP个数 + white-list: [127.0.0.1] + instance-limit: + open: true + global: + bucket: 2 + rate: 2 + resource-cache-amount: 1024 + api-limit: # 接口级限流 + open: false # 是否开启接口限流,全局开关,只有为true,才代表系统的限流开启。默认关闭 + rules: + - name: store-read + limit: + open: true # 接口的全局配置,如果在api子项中,不配置,则该接口依据global来做限制 + bucket: 2000 # 令牌桶最大值 + rate: 1000 # 每秒产生的令牌数 + - name: store-write + limit: + open: true + bucket: 1000 + rate: 500 + apis: + - name: "POST:/v1/naming/services" + rule: store-write + - name: "PUT:/v1/naming/services" + rule: store-write + - name: "POST:/v1/naming/services/delete" + rule: store-write + - name: "GET:/v1/naming/services" + rule: store-read + - name: "GET:/v1/naming/services/count" + rule: store-read + - name: "" \ No newline at end of file diff --git a/store/api.go b/store/api.go new file mode 100644 index 000000000..2a44032b6 --- /dev/null +++ b/store/api.go @@ -0,0 +1,406 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package store + +import ( + "time" + + "github.com/polarismesh/polaris-server/common/model" +) + +/** + * @brief 通用存储接口 + */ +type Store interface { + // 存储层的名字 + Name() string + + // 存储的初始化函数 + Initialize(c *Config) error + + // 存储的析构函数 + Destroy() error + + CreateTransaction() (Transaction, error) + + // 服务命名空间接口 + NamespaceStore + + // 服务业务集接口 + BusinessStore + + // 服务接口 + ServiceStore + + // 实例接口 + InstanceStore + + // 路由配置接口 + RoutingConfigStore + + // L5扩展接口 + L5Store + + // 限流规则接口 + RateLimitStore + + // 熔断规则接口 + CircuitBreakerStore + + // 平台信息接口 + PlatformStore +} + +/** + * @brief 命名空间存储接口 + */ +type NamespaceStore interface { + // 保存一个命名空间 + AddNamespace(namespace *model.Namespace) error + + // 更新命名空间 + UpdateNamespace(namespace *model.Namespace) error + + // 更新命名空间token + UpdateNamespaceToken(name string, token string) error + + // 查询owner下所有的命名空间 + ListNamespaces(owner string) ([]*model.Namespace, error) + + // 根据name获取命名空间的详情 + GetNamespace(name string) (*model.Namespace, error) + + // 从数据库查询命名空间 + GetNamespaces(filter map[string][]string, offset, limit int) ([]*model.Namespace, uint32, error) + + // 获取增量数据 + GetMoreNamespaces(mtime time.Time) ([]*model.Namespace, error) +} + +/** + * @brief 业务集存储接口 + */ +type BusinessStore interface { + // 增加一个业务集 + AddBusiness(business *model.Business) error + + // 删除一个业务集 + DeleteBusiness(bid string) error + + // 更新业务集 + UpdateBusiness(business *model.Business) error + + // 更新业务集token + UpdateBusinessToken(bid string, token string) error + + // 查询owner下业务集 + ListBusiness(owner string) ([]*model.Business, error) + + // 根据业务集ID获取业务集详情 + GetBusinessByID(id string) (*model.Business, error) + + // 根据mtime获取增量数据 + GetMoreBusiness(mtime time.Time) ([]*model.Business, error) +} + +/** + * @brief 服务存储接口 + */ +type ServiceStore interface { + // 保存一个服务 + AddService(service *model.Service) error + + // 删除服务 + DeleteService(id, serviceName, namespaceName string) error + + // 删除服务别名 + DeleteServiceAlias(name string, namespace string) error + + // 修改服务别名 + UpdateServiceAlias(alias *model.Service, needUpdateOwner bool) error + + // 更新服务 + UpdateService(service *model.Service, needUpdateOwner bool) error + + // 更新服务token + UpdateServiceToken(serviceID string, token string, revision string) error + + // 获取源服务的token信息 + GetSourceServiceToken(name string, namespace string) (*model.Service, error) + + // 根据服务名和命名空间获取服务的详情 + GetService(name string, namespace string) (*model.Service, error) + + // 根据服务ID查询服务详情 + GetServiceByID(id string) (*model.Service, error) + + // 根据相关条件查询对应服务及数目 + GetServices(serviceFilters, serviceMetas map[string]string, instanceFilters *InstanceArgs, offset, limit uint32) ( + uint32, []*model.Service, error) + + // 获取所有服务总数 + GetServicesCount() (uint32, error) + + // 获取增量services + GetMoreServices(mtime time.Time, firstUpdate, disableBusiness, needMeta bool) (map[string]*model.Service, error) + + // 获取服务别名列表 + GetServiceAliases(filter map[string]string, offset uint32, limit uint32) (uint32, []*model.ServiceAlias, error) + + // 获取系统服务 + GetSystemServices() ([]*model.Service, error) + + // 批量获取服务id、负责人等信息 + GetServicesBatch(services []*model.Service) ([]*model.Service, error) +} + +/** + * @brief 实例存储接口 + */ +type InstanceStore interface { + // 增加一个实例 + AddInstance(instance *model.Instance) error + + // 增加多个实例 + BatchAddInstances(instances []*model.Instance) error + + // 更新实例 + UpdateInstance(instance *model.Instance) error + + // 删除一个实例,实际是把valid置为false + DeleteInstance(instanceID string) error + + // 批量删除实例,flag=1 + BatchDeleteInstances(ids []interface{}) error + + // 清空一个实例,真正删除 + CleanInstance(instanceID string) error + + // 检查ID是否存在,并且返回所有ID的查询结果 + CheckInstancesExisted(ids map[string]bool) (map[string]bool, error) + + // 获取实例关联的token + GetInstancesBrief(ids map[string]bool) (map[string]*model.Instance, error) + + // 查询一个实例的详情,只返回有效的数据 + GetInstance(instanceID string) (*model.Instance, error) + + // 获取有效的实例总数 + GetInstancesCount() (uint32, error) + + // 根据服务和Host获取实例(不包括metadata) + GetInstancesMainByService(serviceID, host string) ([]*model.Instance, error) + + // 根据过滤条件查看实例详情及对应数目 + GetExpandInstances( + filter, metaFilter map[string]string, offset uint32, limit uint32) (uint32, []*model.Instance, error) + + // 根据mtime获取增量instances,返回所有store的变更信息 + GetMoreInstances(mtime time.Time, firstUpdate, needMeta bool, serviceID []string) (map[string]*model.Instance, error) + + // 设置实例的健康状态 + SetInstanceHealthStatus(instanceID string, flag int, revision string) error + + // 批量修改实例的隔离状态 + BatchSetInstanceIsolate(ids []interface{}, isolate int, revision string) error +} + +/** + * @brief L5扩展存储接口 + */ +type L5Store interface { + // 获取扩展数据 + GetL5Extend(serviceID string) (map[string]interface{}, error) + + // 设置meta里保存的扩展数据,并返回剩余的meta + SetL5Extend(serviceID string, meta map[string]interface{}) (map[string]interface{}, error) + + // 获取module + GenNextL5Sid(layoutID uint32) (string, error) + + // 获取增量数据 + GetMoreL5Extend(mtime time.Time) (map[string]map[string]interface{}, error) + + // 获取Route增量数据 + GetMoreL5Routes(flow uint32) ([]*model.Route, error) + + // 获取Policy增量数据 + GetMoreL5Policies(flow uint32) ([]*model.Policy, error) + + //获取Section增量数据 + GetMoreL5Sections(flow uint32) ([]*model.Section, error) + + //获取IP Config增量数据 + GetMoreL5IPConfigs(flow uint32) ([]*model.IPConfig, error) +} + +/** + * @brief 路由配置表的存储接口 + */ +type RoutingConfigStore interface { + // 新增一个路由配置 + CreateRoutingConfig(conf *model.RoutingConfig) error + + // 更新一个路由配置 + UpdateRoutingConfig(conf *model.RoutingConfig) error + + // 删除一个路由配置 + DeleteRoutingConfig(serviceID string) error + + // 通过mtime拉取增量的路由配置信息 + GetRoutingConfigsForCache(mtime time.Time, firstUpdate bool) ([]*model.RoutingConfig, error) + + // 根据服务名和命名空间拉取路由配置 + GetRoutingConfigWithService(name string, namespace string) (*model.RoutingConfig, error) + + // 根据服务ID拉取路由配置 + GetRoutingConfigWithID(id string) (*model.RoutingConfig, error) + + // 查询路由配置列表 + GetRoutingConfigs(filter map[string]string, offset uint32, limit uint32) (uint32, []*model.ExtendRoutingConfig, error) +} + +/** + * @brief 限流规则的存储接口 + */ +type RateLimitStore interface { + // 新增限流规则 + CreateRateLimit(limiting *model.RateLimit) error + + // 更新限流规则 + UpdateRateLimit(limiting *model.RateLimit) error + + // 删除限流规则 + DeleteRateLimit(limiting *model.RateLimit) error + + // 根据过滤条件拉取限流规则 + GetExtendRateLimits(query map[string]string, offset uint32, limit uint32) (uint32, []*model.ExtendRateLimit, error) + + // 根据限流ID拉取限流规则 + GetRateLimitWithID(id string) (*model.RateLimit, error) + + // 根据修改时间拉取增量限流规则及最新版本号 + GetRateLimitsForCache(mtime time.Time, firstUpdate bool) ([]*model.RateLimit, []*model.RateLimitRevision, error) +} + +/** + * @brief 熔断规则的存储接口 + */ +type CircuitBreakerStore interface { + // 新增熔断规则 + CreateCircuitBreaker(circuitBreaker *model.CircuitBreaker) error + + // 标记熔断规则 + TagCircuitBreaker(circuitBreaker *model.CircuitBreaker) error + + // 发布熔断规则 + ReleaseCircuitBreaker(circuitBreakerRelation *model.CircuitBreakerRelation) error + + // 解绑熔断规则 + UnbindCircuitBreaker(serviceID, ruleID, ruleVersion string) error + + // 删除已标记熔断规则 + DeleteTagCircuitBreaker(id string, version string) error + + // 删除master熔断规则 + DeleteMasterCircuitBreaker(id string) error + + // 修改熔断规则 + UpdateCircuitBreaker(circuitBraker *model.CircuitBreaker) error + + // 获取熔断规则 + GetCircuitBreaker(id, version string) (*model.CircuitBreaker, error) + + // 获取熔断规则的所有版本 + GetCircuitBreakerVersions(id string) ([]string, error) + + // 获取熔断规则master版本的绑定关系 + GetCircuitBreakerMasterRelation(ruleID string) ([]*model.CircuitBreakerRelation, error) + + // 获取已标记熔断规则的绑定关系 + GetCircuitBreakerRelation(ruleID, ruleVersion string) ([]*model.CircuitBreakerRelation, error) + + // 根据修改时间拉取增量熔断规则 + GetCircuitBreakerForCache(mtime time.Time, firstUpdate bool) ([]*model.ServiceWithCircuitBreaker, error) + + // 获取master熔断规则 + ListMasterCircuitBreakers(filters map[string]string, offset uint32, limit uint32) ( + *model.CircuitBreakerDetail, error) + + // 获取已发布规则 + ListReleaseCircuitBreakers(filters map[string]string, offset, limit uint32) ( + *model.CircuitBreakerDetail, error) + + // 根据服务获取熔断规则 + GetCircuitBreakersByService(name string, namespace string) (*model.CircuitBreaker, error) +} + +/** + * @brief 平台信息的存储接口 + */ +type PlatformStore interface { + // 新增平台信息 + CreatePlatform(platform *model.Platform) error + + // 更新平台信息 + UpdatePlatform(platform *model.Platform) error + + // 删除平台信息 + DeletePlatform(id string) error + + // 查询平台信息 + GetPlatformById(id string) (*model.Platform, error) + + // 根据过滤条件查询平台信息 + GetPlatforms(query map[string]string, offset uint32, limit uint32) (uint32, []*model.Platform, error) +} + +/** + * @brief 事务接口 + */ +type Transaction interface { + // 提交事务 + Commit() error + + // 启动锁,限制Server启动的并发数 + LockBootstrap(key string, server string) error + + // 排它锁namespace + LockNamespace(name string) (*model.Namespace, error) + + // 共享锁namespace + RLockNamespace(name string) (*model.Namespace, error) + + // 删除namespace + DeleteNamespace(name string) error + + // 排它锁service + LockService(name string, namespace string) (*model.Service, error) + + // 共享锁service + RLockService(name string, namespace string) (*model.Service, error) + + // 批量锁住service,只需返回valid/bool,增加速度 + BatchRLockServices(ids map[string]bool) (map[string]bool, error) + + // 删除service + DeleteService(name string, namespace string) error + + // 删除源服服务下的所有别名 + DeleteAliasWithSourceID(sourceServiceID string) error +} \ No newline at end of file diff --git a/store/defaultStore/base_db.go b/store/defaultStore/base_db.go new file mode 100644 index 000000000..7d01aaa82 --- /dev/null +++ b/store/defaultStore/base_db.go @@ -0,0 +1,192 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package defaultStore + +import ( + "context" + "database/sql" + "fmt" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/plugin" + "strings" + "time" +) + +// db抛出的异常,需要重试的字符串组 +var errMsg = []string{"Deadlock", "bad connection", "invalid connection"} + +// 对sql.DB的封装 +type BaseDB struct { + *sql.DB + cfg *dbConfig + isolationLevel sql.IsolationLevel + parsePwd plugin.ParsePassword +} + +/** + * @brief store的配置 + */ +type dbConfig struct { + dbType string + dbUser string + dbPwd string + dbAddr string + dbName string + maxOpenConns int + maxIdleConns int + connMaxLifetime int + txIsolationLevel int +} + +// 新建一个BaseDB +func NewBaseDB(cfg *dbConfig, parsePwd plugin.ParsePassword) (*BaseDB, error) { + baseDb := &BaseDB{cfg: cfg, parsePwd: parsePwd} + if cfg.txIsolationLevel > 0 { + baseDb.isolationLevel = sql.IsolationLevel(cfg.txIsolationLevel) + log.Infof("[Store][database] use isolation level: %s", baseDb.isolationLevel.String()) + } + + if err := baseDb.openDatabase(); err != nil { + return nil, err + } + + return baseDb, nil +} + +// 与数据库进行连接 +func (b *BaseDB) openDatabase() error { + c := b.cfg + + // 使用密码解析插件 + if b.parsePwd != nil { + pwd, err := b.parsePwd.ParsePassword(c.dbPwd) + if err != nil { + log.Errorf("[Store][database][ParsePwdPlugin] parse password err: %s", err.Error()) + return err + } + c.dbPwd = pwd + } + + dns := fmt.Sprintf("%s:%s@tcp(%s)/%s", c.dbUser, c.dbPwd, c.dbAddr, c.dbName) + db, err := sql.Open(c.dbType, dns) + if err != nil { + log.Errorf("[Store][database] sql open err: %s", err.Error()) + return err + } + if pingErr := db.Ping(); pingErr != nil { + log.Errorf("[Store][database] database ping err: %s", pingErr.Error()) + return pingErr + } + if c.maxOpenConns > 0 { + log.Infof("[Store][database] db set max open conns: %d", c.maxOpenConns) + db.SetMaxOpenConns(c.maxOpenConns) + } + if c.maxIdleConns > 0 { + log.Infof("[Store][database] db set max idle conns: %d", c.maxIdleConns) + db.SetMaxIdleConns(c.maxIdleConns) + } + if c.connMaxLifetime > 0 { + log.Infof("[Store][database] db set conn max life time: %d", c.connMaxLifetime) + db.SetConnMaxLifetime(time.Second * time.Duration(c.connMaxLifetime)) + } + + b.DB = db + return nil +} + +// 重写db.Exec函数 +// 提供重试功能 +func (b *BaseDB) Exec(query string, args ...interface{}) (sql.Result, error) { + var result sql.Result + var err error + Retry("exec "+query, func() error { + result, err = b.DB.Exec(query, args...) + return err + }) + + return result, err +} + +// 重写db.Query函数 +func (b *BaseDB) Query(query string, args ...interface{}) (*sql.Rows, error) { + var rows *sql.Rows + var err error + Retry("query "+query, func() error { + rows, err = b.DB.Query(query, args...) + return err + }) + + return rows, err +} + +// 重写db.Begin +func (b *BaseDB) Begin() (*BaseTx, error) { + var tx *sql.Tx + var err error + var option *sql.TxOptions + if b.isolationLevel > 0 { + option = &sql.TxOptions{Isolation: sql.IsolationLevel(b.isolationLevel)} + } + Retry("begin", func() error { + tx, err = b.DB.BeginTx(context.Background(), option) + return err + }) + + return &BaseTx{Tx: tx}, err +} + +// 对sql.Tx的封装 +type BaseTx struct { + *sql.Tx +} + +// 重试主函数 +// 最多重试20次,每次等待5ms*重试次数 +func Retry(label string, handle func() error) { + var err error + maxTryTimes := 20 + for i := 1; i <= maxTryTimes; i++ { + err = handle() + if err == nil { + return + } + + repeated := false // 是否重试 + for _, msg := range errMsg { + if strings.Contains(err.Error(), msg) { + log.Warnf("[Store][database][%s] get error msg: %s. Repeated doing(%d)", label, err.Error(), i) + time.Sleep(time.Millisecond * 5 * time.Duration(i)) + repeated = true + break + } + } + if !repeated { + return + } + } +} + +// 事务重试 +func RetryTransaction(label string, handle func() error) error { + var err error + Retry(label, func() error { + err = handle() + return err + }) + return err +} diff --git a/store/defaultStore/base_db_test.go b/store/defaultStore/base_db_test.go new file mode 100644 index 000000000..087c31246 --- /dev/null +++ b/store/defaultStore/base_db_test.go @@ -0,0 +1,146 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package defaultStore + +import ( + "errors" + "fmt" + . "github.com/smartystreets/goconvey/convey" + "testing" + "time" +) + +// 测试retry +func TestRetry(t *testing.T) { + Convey("重试可以成功", t, func() { + var err error + Retry("retry", func() error { + err = errors.New("retry error") + return err + }) + So(err, ShouldNotBeNil) + + start := time.Now() + count := 0 + Retry("retry", func() error { + count++ + if count <= 10 { + err = errors.New("invalid connection") + return err + } + err = nil + return nil + }) + sub := time.Now().Sub(start) + So(err, ShouldBeNil) + So(sub, ShouldBeGreaterThan, time.Millisecond*100) + }) + Convey("只捕获固定的错误", t, func() { + for _, msg := range errMsg { + var err error + start := time.Now() + Retry(fmt.Sprintf("retry-%s", msg), func() error { + err = fmt.Errorf("my-error: %s", msg) + return err + }) + So(err, ShouldNotBeNil) + So(time.Now().Sub(start), ShouldBeGreaterThan, time.Millisecond*100) + } + }) +} + +// 测试retryTransaction +func TestRetryTransaction(t *testing.T) { + Convey("handle错误可以正常捕获", t, func() { + err := RetryTransaction("test-handle", func() error { + t.Logf("handle ok") + return nil + }) + So(err, ShouldBeNil) + + start := time.Now() + err = RetryTransaction("test-handle", func() error { + return errors.New("Deadlock") + }) + So(err, ShouldNotBeNil) + So(err.Error(), ShouldEqual, "Deadlock") + sub := time.Now().Sub(start) + t.Logf("%v", sub) + So(sub, ShouldBeGreaterThan, time.Millisecond*100) + + start = time.Now() + err = RetryTransaction("test-handle", func() error { + return errors.New("other error") + }) + So(err, ShouldNotBeNil) + sub = time.Now().Sub(start) + So(sub, ShouldBeLessThan, time.Millisecond*5) + }) +} + +// 测试BatchOperation +func TestBatchOperation(t *testing.T) { + Convey("data为nil", t, func() { + err := BatchOperation("data为nil", nil, func(objects []interface{}) error { + return nil + }) + So(err, ShouldBeNil) + }) + Convey("data大小为1", t, func() { + data := make([]interface{}, 1) + num := 0 + err := BatchOperation("data为1", data, func(objects []interface{}) error { + num++ + return nil + }) + So(err, ShouldBeNil) + So(num, ShouldEqual, 1) + }) + Convey("data大小为101", t, func() { + data := make([]interface{}, 101) + num := 0 + err := BatchOperation("data为101", data, func(objects []interface{}) error { + num++ + return nil + }) + So(err, ShouldBeNil) + So(num, ShouldEqual, 2) + }) + + Convey("data大小为100", t, func() { + data := make([]interface{}, 100, 100) + num := 0 + err := BatchOperation("data为100", data, func(objects []interface{}) error { + num++ + return nil + }) + So(err, ShouldBeNil) + So(num, ShouldEqual, 1) + }) + + Convey("data大小为0", t, func() { + data := make([]interface{}, 0, 0) + num := 0 + err := BatchOperation("data为100", data, func(objects []interface{}) error { + num++ + return nil + }) + So(err, ShouldBeNil) + So(num, ShouldEqual, 0) + }) +} diff --git a/store/defaultStore/business.go b/store/defaultStore/business.go new file mode 100644 index 000000000..2d194789e --- /dev/null +++ b/store/defaultStore/business.go @@ -0,0 +1,202 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package defaultStore + +import ( + "database/sql" + "fmt" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" + "time" +) + +/** + * @brief 实现BusinessStore接口 + */ +type businessStore struct { + db *BaseDB +} + +/** + * @brief 增加业务集 + */ +func (bs *businessStore) AddBusiness(b *model.Business) error { + if b.ID == "" || b.Name == "" || b.Token == "" || b.Owner == "" { + log.Errorf("[Store][database] add business missing some params: %+v", b) + return fmt.Errorf("Add Business missing some params") + } + + str := `insert into business(id, name, token, owner, ctime, mtime) + values(?, ?, ?, ?, sysdate(), sysdate())` + _, err := bs.db.Exec(str, b.ID, b.Name, b.Token, b.Owner) + + return err +} + +/** + * @brief 删除业务集 + */ +func (bs *businessStore) DeleteBusiness(bid string) error { + if bid == "" { + log.Errorf("[Store][database] delete business missing id") + return fmt.Errorf("Add Business missing some params") + } + + // 删除操作把对应的数据flag修改 + str := "update business set flag = 1, mtime = sysdate() where id = ?" + _, err := bs.db.Exec(str, bid) + + return err +} + +/** + * @brief 更新业务集 + */ +func (bs *businessStore) UpdateBusiness(b *model.Business) error { + if b.ID == "" || b.Name == "" || b.Owner == "" { + log.Errorf("[Store][database] update business missing some params") + return fmt.Errorf("Update Business missing some params") + } + + str := "update business set name = ?, owner = ?, mtime = sysdate() where id = ?" + _, err := bs.db.Exec(str, b.Name, b.Owner, b.ID) + + return err +} + +/** + * @brief 更新业务集token + */ +func (bs *businessStore) UpdateBusinessToken(bid string, token string) error { + if bid == "" || token == "" { + log.Errorf("[Store][business] update business token missing some params") + return fmt.Errorf("Update Business Token missing some params") + } + + str := "update business set token = ?, mtime = sysdate() where id = ?" + _, err := bs.db.Exec(str, token, bid) + + return err +} + +/** + * @brief 获取owner下所有的业务集 + */ +func (bs *businessStore) ListBusiness(owner string) ([]*model.Business, error) { + if owner == "" { + log.Errorf("[Store][business] list business missing owner") + return nil, fmt.Errorf("List Business Mising param owner") + } + + str := genBusinessSelectSQL() + " where owner like '%?%'" + rows, err := bs.db.Query(str, owner) + if err != nil { + log.Errorf("[Store][database] list all business err: %s", err.Error()) + return nil, err + } + + return businessFetchRows(rows) + +} + +/** + * @brief 根据业务集ID获取业务集详情 + */ +func (bs *businessStore) GetBusinessByID(id string) (*model.Business, error) { + if id == "" { + log.Errorf("[Store][business] get business missing id") + return nil, fmt.Errorf("Get Business missing param id") + } + + str := genBusinessSelectSQL() + " where id = ?" + rows, err := bs.db.Query(str, id) + if err != nil { + log.Errorf("[Store][database] get business by id query err: %s", err.Error()) + return nil, err + } + + out, err := businessFetchRows(rows) + if err != nil { + return nil, err + } + + if len(out) == 0 { + return nil, nil + } + return out[0], nil +} + +/** + * @brief 根据mtime获取增量数据 + */ +func (bs *businessStore) GetMoreBusiness(mtime time.Time) ([]*model.Business, error) { + str := genBusinessSelectSQL() + " where UNIX_TIMESTAMP(mtime) >= ?" + rows, err := bs.db.Query(str, mtime.Unix()) + if err != nil { + log.Errorf("[Store][database] get more business err: %s", err.Error()) + return nil, err + } + + return businessFetchRows(rows) +} + +// 生成business查询语句 +func genBusinessSelectSQL() string { + str := `select id, name, token, owner, flag, + UNIX_TIMESTAMP(ctime), UNIX_TIMESTAMP(mtime) + from business ` + return str +} + +/** + * @brief 取出rows的数据 + */ +func businessFetchRows(rows *sql.Rows) ([]*model.Business, error) { + if rows == nil { + return nil, nil + } + defer rows.Close() + + var ctime, mtime int64 + var out []*model.Business + var flag int + for rows.Next() { + var tmp model.Business + err := rows.Scan( + &tmp.ID, &tmp.Name, &tmp.Token, + &tmp.Owner, &flag, &ctime, &mtime) + if err != nil { + return nil, err + } + + tmp.CreateTime = time.Unix(ctime, 0) + tmp.ModifyTime = time.Unix(mtime, 0) + tmp.Valid = true + if flag == 1 { + tmp.Valid = false + } + + out = append(out, &tmp) + } + + if err := rows.Err(); err != nil { + return nil, err + } + + return out, nil +} diff --git a/store/defaultStore/circuitbreaker_config.go b/store/defaultStore/circuitbreaker_config.go new file mode 100644 index 000000000..ddf714fdb --- /dev/null +++ b/store/defaultStore/circuitbreaker_config.go @@ -0,0 +1,686 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package defaultStore + +import ( + "database/sql" + "fmt" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/store" + "time" +) + +/** + * @brief CircuitBreakerStore的实现 + */ +type circuitBreakerStore struct { + master *BaseDB + slave *BaseDB +} + +/** + * @brief 创建一个新的熔断规则 + */ +func (c *circuitBreakerStore) CreateCircuitBreaker(cb *model.CircuitBreaker) error { + if err := c.cleanCircuitBreaker(cb.ID, cb.Version); err != nil { + log.Errorf("[Store][circuitBreaker] clean master for circuit breaker(%s, %s) err: %s", + cb.ID, cb.Version, err.Error()) + return store.Error(err) + } + + str := `insert into circuitbreaker_rule + (id, version, name, namespace, business, department, comment, inbounds, + outbounds, token, owner, revision, flag, ctime, mtime) + values(?,?,?,?,?,?,?,?,?,?,?,?,?,sysdate(),sysdate())` + if _, err := c.master.Exec(str, cb.ID, cb.Version, cb.Name, cb.Namespace, cb.Business, cb.Department, + cb.Comment, cb.Inbounds, cb.Outbounds, cb.Token, cb.Owner, cb.Revision, 0); err != nil { + log.Errorf("[Store][circuitBreaker] create circuit breaker(%s, %s, %s) err: %s", + cb.ID, cb.Name, cb.Version, err.Error()) + return store.Error(err) + } + + return nil +} + +/** + * @brief 给master熔断规则打一个version tag + */ +func (c *circuitBreakerStore) TagCircuitBreaker(cb *model.CircuitBreaker) error { + if err := c.cleanCircuitBreaker(cb.ID, cb.Version); err != nil { + log.Errorf("[Store][circuitBreaker] clean tag for circuit breaker(%s, %s) err: %s", + cb.ID, cb.Version, err.Error()) + return store.Error(err) + } + + if err := c.tagCircuitBreaker(cb); err != nil { + log.Errorf("[Store][circuitBreaker] create tag for circuit breaker(%s, %s) err: %s", + cb.ID, cb.Version, err.Error()) + return store.Error(err) + } + + return nil +} + +/** + * @brief 给master熔断规则打一个version tag的内部函数 + */ +func (c *circuitBreakerStore) tagCircuitBreaker(cb *model.CircuitBreaker) error { + // 需要保证master规则存在 + str := `insert into circuitbreaker_rule + (id, version, name, namespace, business, department, comment, inbounds, + outbounds, token, owner, revision, ctime, mtime) + select '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', + '%s', '%s', '%s', '%s', sysdate(), sysdate() from circuitbreaker_rule + where id = ? and version = 'master'` + str = fmt.Sprintf(str, cb.ID, cb.Version, cb.Name, cb.Namespace, cb.Business, cb.Department, cb.Comment, + cb.Inbounds, cb.Outbounds, cb.Token, cb.Owner, cb.Revision) + result, err := c.master.Exec(str, cb.ID) + if err != nil { + log.Errorf("[Store][CircuitBreaker] exec create tag sql(%s) err: %s", str, err.Error()) + return err + } + + if err := checkDataBaseAffectedRows(result, 1); err != nil { + if store.Code(err) == store.AffectedRowsNotMatch { + return store.NewStatusError(store.NotFoundMasterConfig, "not found master config") + } + log.Errorf("[Store][CircuitBreaker] tag rule affected rows err: %s", err.Error()) + return err + } + + return nil +} + +/** + * @brief 发布熔断规则 + */ +func (c *circuitBreakerStore) ReleaseCircuitBreaker(cbr *model.CircuitBreakerRelation) error { + if err := c.cleanCircuitBreakerRelation(cbr); err != nil { + return store.Error(err) + } + + if err := c.releaseCircuitBreaker(cbr); err != nil { + log.Errorf("[Store][CircuitBreaker] release rule err: %s", err.Error()) + return store.Error(err) + } + + return nil +} + +/** + * @brief 发布熔断规则的内部函数 + * @note 可能存在服务的规则,由旧的更新到新的场景 + */ +func (c *circuitBreakerStore) releaseCircuitBreaker(cbr *model.CircuitBreakerRelation) error { + // 发布规则时,需要保证规则已经被标记 + str := `insert into circuitbreaker_rule_relation(service_id, rule_id, rule_version, flag, ctime, mtime) + select '%s', '%s', '%s', 0, sysdate(), sysdate() from service, circuitbreaker_rule + where service.id = ? and service.flag = 0 + and circuitbreaker_rule.id = ? and circuitbreaker_rule.version = ? + and circuitbreaker_rule.flag = 0 + on DUPLICATE key update + rule_id = ?, rule_version = ?, flag = 0, mtime = sysdate()` + str = fmt.Sprintf(str, cbr.ServiceID, cbr.RuleID, cbr.RuleVersion) + log.Infof("[Store][CircuitBreaker] exec release sql(%s)", str) + result, err := c.master.Exec(str, cbr.ServiceID, cbr.RuleID, cbr.RuleVersion, cbr.RuleID, cbr.RuleVersion) + if err != nil { + log.Errorf("[Store][CircuitBreaker] release exec sql(%s) err: %s", str, err.Error()) + return err + } + if err := checkDataBaseAffectedRows(result, 1, 2); err != nil { + if store.Code(err) == store.AffectedRowsNotMatch { + return store.NewStatusError(store.NotFoundTagConfigOrService, "not found tag config or service") + } + log.Errorf("[Store][CircuitBreaker] release rule affected rows err: %s", err.Error()) + return err + } + + return nil +} + +/** + * @brief 解绑熔断规则 + */ +func (c *circuitBreakerStore) UnbindCircuitBreaker(serviceID, ruleID, ruleVersion string) error { + str := `update circuitbreaker_rule_relation set flag = 1, mtime = sysdate() where service_id = ? and rule_id = ? + and rule_version = ?` + if _, err := c.master.Exec(str, serviceID, ruleID, ruleVersion); err != nil { + log.Errorf("[Store][CircuitBreaker] delete relation(%s) err: %s", serviceID, err.Error()) + return err + } + + return nil +} + +/** + * @brief 删除非master熔断规则 + */ +func (c *circuitBreakerStore) DeleteTagCircuitBreaker(id string, version string) error { + // 需要保证规则无绑定服务 + str := `update circuitbreaker_rule set flag = 1, mtime = sysdate() + where id = ? and version = ? + and id not in + (select DISTINCT(rule_id) from circuitbreaker_rule_relation + where rule_id = ? and rule_version = ? and flag = 0)` + log.Infof("[Store][circuitBreaker] delete rule id(%s) version(%s), sql(%s)", id, version, str) + if _, err := c.master.Exec(str, id, version, id, version); err != nil { + log.Errorf("[Store][CircuitBreaker] delete tag rule(%s, %s) exec err: %s", id, version, err.Error()) + return err + } + + return nil +} + +/** + * @brief 删除master熔断规则 + */ +func (c *circuitBreakerStore) DeleteMasterCircuitBreaker(id string) error { + // 需要保证所有已标记的规则无绑定服务 + str := `update circuitbreaker_rule set flag = 1, mtime = sysdate() + where id = ? + and id not in + (select DISTINCT(rule_id) from circuitbreaker_rule_relation + where rule_id = ? and flag = 0)` + log.Infof("[Store][CircuitBreaker] delete master rule(%s) sql(%s)", id, str) + if _, err := c.master.Exec(str, id, id); err != nil { + log.Errorf("[Store][CircuitBreaker] delete master rule(%s) exec err: %s", id, err.Error()) + return err + } + + return nil +} + +/** + * @brief 修改熔断规则 + * @note 只允许修改master熔断规则 + */ +func (c *circuitBreakerStore) UpdateCircuitBreaker(cb *model.CircuitBreaker) error { + str := `update circuitbreaker_rule set business = ?, department = ?, comment = ?, + inbounds = ?, outbounds = ?, token = ?, owner = ?, revision = ?, mtime = sysdate() + where id = ? and version = ?` + + if _, err := c.master.Exec(str, cb.Business, cb.Department, cb.Comment, cb.Inbounds, + cb.Outbounds, cb.Token, cb.Owner, cb.Revision, cb.ID, cb.Version); err != nil { + log.Errorf("[Store][CircuitBreaker] update rule(%s,%s) exec err: %s", cb.ID, cb.Version, err.Error()) + return err + } + + return nil +} + +/** + * @brief 获取熔断规则 + */ +func (c *circuitBreakerStore) GetCircuitBreaker(id, version string) (*model.CircuitBreaker, error) { + str := `select id, version, name, namespace, IFNULL(business, ""), IFNULL(department, ""), IFNULL(comment, ""), + inbounds, outbounds, token, owner, revision, flag, unix_timestamp(ctime), unix_timestamp(mtime) + from circuitbreaker_rule + where id = ? and version = ? and flag = 0` + rows, err := c.master.Query(str, id, version) + if err != nil { + log.Errorf("[Store][CircuitBreaker] query circuitbreaker_rule with id(%s) and version(%s) err: %s", + id, version, err.Error()) + return nil, err + } + + out, err := fetchCircuitBreakerRows(rows) + if err != nil { + return nil, err + } + + if len(out) == 0 { + return nil, nil + } + + return out[0], nil +} + +/** + * @brief 获取已标记熔断规则的绑定关系 + */ +func (c *circuitBreakerStore) GetCircuitBreakerRelation(ruleID, ruleVersion string) ( + []*model.CircuitBreakerRelation, error) { + str := genQueryCircuitBreakerRelation() + str += `where rule_id = ? and rule_version = ? and flag = 0` + rows, err := c.master.Query(str, ruleID, ruleVersion) + if err != nil { + log.Errorf("[Store][CircuitBreaker] query circuitbreaker_rule_relation "+ + "with rule_id(%s) and rule_version(%s) err: %s", + ruleID, ruleVersion, err.Error()) + return nil, err + } + + out, err := fetchCircuitBreakerRelationRows(rows) + if err != nil { + return nil, err + } + + return out, nil +} + +/** + * @brief 获取熔断规则master版本的绑定关系 + */ +func (c *circuitBreakerStore) GetCircuitBreakerMasterRelation(ruleID string) ( + []*model.CircuitBreakerRelation, error) { + str := genQueryCircuitBreakerRelation() + str += `where rule_id = ? and flag = 0` + rows, err := c.master.Query(str, ruleID) + if err != nil { + log.Errorf("[Store][CircuitBreaker] query circuitbreaker_rule_relation with rule_id(%s) err: %s", + ruleID, err.Error()) + return nil, err + } + + out, err := fetchCircuitBreakerRelationRows(rows) + if err != nil { + return nil, err + } + + return out, nil +} + +/** + * @brief 根据修改时间拉取增量熔断规则 + */ +func (c *circuitBreakerStore) GetCircuitBreakerForCache(mtime time.Time, firstUpdate bool) ( + []*model.ServiceWithCircuitBreaker, error) { + str := genQueryCircuitBreakerWithServiceID() + str += `where circuitbreaker_rule_relation.mtime > ? and rule_id = id and rule_version = version + and circuitbreaker_rule.flag = 0` + if firstUpdate { + str += ` and circuitbreaker_rule_relation.flag != 1` + } + rows, err := c.slave.Query(str, time2String(mtime)) + if err != nil { + log.Errorf("[Store][CircuitBreaker] query circuitbreaker_rule_relation with mtime err: %s", + err.Error()) + return nil, err + } + circuitBreakers, err := fetchCircuitBreakerAndServiceRows(rows) + if err != nil { + return nil, err + } + return circuitBreakers, nil +} + +/** + * @brief 获取熔断规则的所有版本 + */ +func (c *circuitBreakerStore) GetCircuitBreakerVersions(id string) ([]string, error) { + str := `select version from circuitbreaker_rule where id = ? and flag = 0 order by mtime desc` + rows, err := c.master.Query(str, id) + if err != nil { + log.Errorf("[Store][CircuitBreaker] get circuit breaker(%s) versions query err: %s", id, err.Error()) + return nil, err + } + + var versions []string + var version string + for rows.Next() { + if err := rows.Scan(&version); err != nil { + log.Errorf("[Store][CircuitBreaker] get circuit breaker(%s) versions scan err: %s", id, err.Error()) + return nil, err + } + + versions = append(versions, version) + } + if err := rows.Err(); err != nil { + log.Errorf("[Store][CircuitBreaker] get circuit breaker(%s) versions next err: %s", id, err.Error()) + return nil, err + } + + return versions, nil +} + +/** + * @brief 获取master熔断规则 + */ +func (c *circuitBreakerStore) ListMasterCircuitBreakers(filters map[string]string, offset uint32, limit uint32) ( + *model.CircuitBreakerDetail, error) { + // 获取master熔断规则 + selectStr := `select rule.id, rule.version, rule.name, rule.namespace, IFNULL(rule.business, ""), + IFNULL(rule.department, ""), IFNULL(rule.comment, ""), rule.inbounds, rule.outbounds, + rule.owner, rule.revision, + unix_timestamp(rule.ctime), unix_timestamp(rule.mtime) from circuitbreaker_rule as rule ` + countStr := `select count(*) from circuitbreaker_rule as rule ` + whereStr := "where rule.version = 'master' and rule.flag = 0 " + orderStr := "order by rule.mtime desc " + pageStr := "limit ?, ? " + + var args []interface{} + filterStr, filterArgs := genRuleFilterSQL("rule", filters) + if filterStr != "" { + whereStr += "and " + filterStr + args = append(args, filterArgs...) + } + + out := &model.CircuitBreakerDetail{ + Total: 0, + CircuitBreakerInfos: make([]*model.CircuitBreakerInfo, 0), + } + err := c.master.QueryRow(countStr+whereStr, args...).Scan(&out.Total) + switch { + case err == sql.ErrNoRows: + out.Total = 0 + return out, nil + case err != nil: + log.Errorf("[Store][CircuitBreaker] list master circuitbreakers query count err: %s", err.Error()) + return nil, err + default: + } + + args = append(args, offset) + args = append(args, limit) + + rows, err := c.master.Query(selectStr+whereStr+orderStr+pageStr, args...) + if err != nil { + log.Errorf("[Store][CircuitBreaker] list master circuitbreaker query err: %s", err.Error()) + return nil, err + } + defer func() { _ = rows.Close() }() + + var ctime, mtime int64 + for rows.Next() { + var entry model.CircuitBreaker + if err := rows.Scan(&entry.ID, &entry.Version, &entry.Name, &entry.Namespace, &entry.Business, + &entry.Department, &entry.Comment, &entry.Inbounds, &entry.Outbounds, &entry.Owner, &entry.Revision, + &ctime, &mtime); err != nil { + log.Errorf("[Store][CircuitBreaker] list master circuitbreakers rows scan err: %s", err.Error()) + return nil, err + } + + entry.CreateTime = time.Unix(ctime, 0) + entry.ModifyTime = time.Unix(mtime, 0) + cbEntry := &model.CircuitBreakerInfo{CircuitBreaker: &entry} + out.CircuitBreakerInfos = append(out.CircuitBreakerInfos, cbEntry) + } + if err := rows.Err(); err != nil { + log.Errorf("[Store][CircuitBreaker] list master circuitbreakers rows next err: %s", err.Error()) + return nil, err + } + + return out, nil +} + +/** + * @brief 获取已发布规则及服务 + */ +func (c *circuitBreakerStore) ListReleaseCircuitBreakers(filters map[string]string, offset, limit uint32) ( + *model.CircuitBreakerDetail, error) { + selectStr := `select rule_id, rule_version, unix_timestamp(relation.ctime), unix_timestamp(relation.mtime), + name, namespace, service.owner from circuitbreaker_rule_relation as relation, service ` + whereStr := `where relation.flag = 0 and relation.service_id = service.id ` + orderStr := "order by relation.mtime desc " + pageStr := "limit ?, ?" + + countStr := `select count(*) from circuitbreaker_rule_relation as relation where relation.flag = 0 ` + + var args []interface{} + filterStr, filterArgs := genRuleFilterSQL("relation", filters) + if filterStr != "" { + countStr += "and " + filterStr + whereStr += "and " + filterStr + args = append(args, filterArgs...) + } + + out := &model.CircuitBreakerDetail{ + Total: 0, + CircuitBreakerInfos: make([]*model.CircuitBreakerInfo, 0), + } + + err := c.master.QueryRow(countStr, args...).Scan(&out.Total) + switch { + case err == sql.ErrNoRows: + out.Total = 0 + return out, nil + case err != nil: + log.Errorf("[Store][CircuitBreaker] list tag circuitbreakers query count err: %s", err.Error()) + return nil, err + default: + } + + args = append(args, offset) + args = append(args, limit) + + rows, err := c.master.Query(selectStr+whereStr+orderStr+pageStr, args...) + if err != nil { + log.Errorf("[Store][CircuitBreaker] list tag circuitBreakers query err: %s", err.Error()) + return nil, err + } + defer func() { rows.Close() }() + + var ctime, mtime int64 + for rows.Next() { + var entry model.CircuitBreaker + var service model.Service + if err := rows.Scan(&entry.ID, &entry.Version, &ctime, &mtime, &service.Name, &service.Namespace, + &service.Owner); err != nil { + log.Errorf("[Store][CircuitBreaker] list tag circuitBreakers scan err: %s", err.Error()) + return nil, err + } + + service.CreateTime = time.Unix(ctime, 0) + service.ModifyTime = time.Unix(mtime, 0) + + info := &model.CircuitBreakerInfo{ + CircuitBreaker: &entry, + Services: []*model.Service{ + &service, + }, + } + + out.CircuitBreakerInfos = append(out.CircuitBreakerInfos, info) + } + + return out, nil +} + +/** + * @brief 根据服务获取熔断规则 + */ +func (c *circuitBreakerStore) GetCircuitBreakersByService(name string, namespace string) ( + *model.CircuitBreaker, error) { + str := `select rule.id, rule.version, rule.name, rule.namespace, IFNULL(rule.business, ""), + IFNULL(rule.comment, ""), IFNULL(rule.department, ""), + rule.inbounds, rule.outbounds, rule.owner, rule.revision, + unix_timestamp(rule.ctime), unix_timestamp(rule.mtime) + from circuitbreaker_rule as rule, circuitbreaker_rule_relation as relation, service + where service.id = relation.service_id + and relation.rule_id = rule.id and relation.rule_version = rule.version + and relation.flag = 0 and service.flag = 0 and rule.flag = 0 + and service.name = ? and service.namespace = ?` + var breaker model.CircuitBreaker + var ctime, mtime int64 + err := c.master.QueryRow(str, name, namespace).Scan(&breaker.ID, &breaker.Version, &breaker.Name, + &breaker.Namespace, &breaker.Business, &breaker.Comment, &breaker.Department, + &breaker.Inbounds, &breaker.Outbounds, &breaker.Owner, &breaker.Revision, &ctime, &mtime) + switch { + case err == sql.ErrNoRows: + return nil, nil + case err != nil: + log.Errorf("[Store][CircuitBreaker] get tag circuitbreaker with service(%s, %s) err: %s", + name, namespace, err.Error()) + return nil, err + default: + breaker.CreateTime = time.Unix(ctime, 0) + breaker.ModifyTime = time.Unix(mtime, 0) + return &breaker, nil + } +} + +/** + * @brief 清理无效的熔断规则关系 + */ +func (c *circuitBreakerStore) cleanCircuitBreakerRelation(cbr *model.CircuitBreakerRelation) error { + log.Infof("[Store][CircuitBreaker] clean relation for service(%s)", cbr.ServiceID) + str := `delete from circuitbreaker_rule_relation where service_id = ? and flag = 1` + if _, err := c.master.Exec(str, cbr.ServiceID); err != nil { + log.Errorf("[Store][CircuitBreaker] clean relation service(%s) err: %s", + cbr.ServiceID, err.Error()) + return err + } + + return nil +} + +/** + * @brief 彻底清理熔断规则 + */ +func (c *circuitBreakerStore) cleanCircuitBreaker(id string, version string) error { + str := `delete from circuitbreaker_rule where id = ? and version = ? and flag = 1` + if _, err := c.master.Exec(str, id, version); err != nil { + log.Errorf("[Store][database] clean circuit breaker(%s) err: %s", id, err.Error()) + return store.Error(err) + } + return nil +} + +/** + * @brief 读取circuitbreaker_rule的数据 + */ +func fetchCircuitBreakerRows(rows *sql.Rows) ([]*model.CircuitBreaker, error) { + defer rows.Close() + var out []*model.CircuitBreaker + for rows.Next() { + var entry model.CircuitBreaker + var flag int + var ctime, mtime int64 + err := rows.Scan(&entry.ID, &entry.Version, &entry.Name, &entry.Namespace, &entry.Business, &entry.Department, + &entry.Comment, &entry.Inbounds, &entry.Outbounds, &entry.Token, &entry.Owner, &entry.Revision, + &flag, &ctime, &mtime) + if err != nil { + log.Errorf("[Store][CircuitBreaker] fetch circuitbreaker_rule scan err: %s", err.Error()) + return nil, err + } + + entry.CreateTime = time.Unix(ctime, 0) + entry.ModifyTime = time.Unix(mtime, 0) + entry.Valid = true + if flag == 1 { + entry.Valid = false + } + + out = append(out, &entry) + } + if err := rows.Err(); err != nil { + log.Errorf("[Store][CircuitBreaker] fetch circuitbreaker_rule next err: %s", err.Error()) + return nil, err + } + + return out, nil +} + +/** + * @brief 读取circuitbreaker_rule_relation的数据 + */ +func fetchCircuitBreakerRelationRows(rows *sql.Rows) ([]*model.CircuitBreakerRelation, error) { + defer rows.Close() + var out []*model.CircuitBreakerRelation + for rows.Next() { + var entry model.CircuitBreakerRelation + var flag int + var ctime, mtime int64 + err := rows.Scan(&entry.ServiceID, &entry.RuleID, &entry.RuleVersion, &flag, &ctime, &mtime) + if err != nil { + log.Errorf("[Store][CircuitBreaker] fetch circuitbreaker_rule_relation scan err: %s", err.Error()) + return nil, err + } + + entry.CreateTime = time.Unix(ctime, 0) + entry.ModifyTime = time.Unix(mtime, 0) + entry.Valid = true + if flag == 1 { + entry.Valid = false + } + + out = append(out, &entry) + } + + if err := rows.Err(); err != nil { + log.Errorf("[Store][CircuitBreaker] fetch circuitbreaker_rule_relation next err: %s", err.Error()) + return nil, err + } + + return out, nil +} + +/** + * @brief 读取circuitbreaker_rule和circuitbreaker_rule_relation的数据 + */ +func fetchCircuitBreakerAndServiceRows(rows *sql.Rows) ([]*model.ServiceWithCircuitBreaker, error) { + defer rows.Close() + var out []*model.ServiceWithCircuitBreaker + for rows.Next() { + var entry model.ServiceWithCircuitBreaker + var rule model.CircuitBreaker + var relationFlag, ruleFlag int + var relationCtime, relationMtime, ruleCtime, ruleMtime int64 + err := rows.Scan(&entry.ServiceID, &rule.ID, &rule.Version, &relationFlag, &relationCtime, &relationMtime, + &rule.Name, &rule.Namespace, &rule.Business, &rule.Department, &rule.Comment, &rule.Inbounds, &rule.Outbounds, + &rule.Token, &rule.Owner, &rule.Revision, &ruleFlag, &ruleCtime, &ruleMtime) + if err != nil { + log.Errorf("[Store][CircuitBreaker] fetch circuitbreaker_rule and relation scan err: %s", + err.Error()) + return nil, err + } + entry.CreateTime = time.Unix(relationCtime, 0) + entry.ModifyTime = time.Unix(relationMtime, 0) + entry.Valid = true + if relationFlag == 1 { + entry.Valid = false + } + rule.CreateTime = time.Unix(ruleCtime, 0) + rule.ModifyTime = time.Unix(ruleMtime, 0) + rule.Valid = true + if ruleFlag == 1 { + rule.Valid = false + } + entry.CircuitBreaker = &rule + out = append(out, &entry) + } + if err := rows.Err(); err != nil { + log.Errorf("[Store][CircuitBreaker] fetch circuitbreaker_rule and relation next err: %s", err.Error()) + return nil, err + } + + return out, nil +} + +/** + * @brief 查询熔断规则绑定关系表的语句 + */ +func genQueryCircuitBreakerRelation() string { + str := `select service_id, rule_id, rule_version, flag, unix_timestamp(ctime), unix_timestamp(mtime) + from circuitbreaker_rule_relation ` + return str +} + +/** + * @brief 根据服务id查询熔断规则的查询语句 + */ +func genQueryCircuitBreakerWithServiceID() string { + str := `select service_id, rule_id, rule_version, circuitbreaker_rule_relation.flag, + unix_timestamp(circuitbreaker_rule_relation.ctime), unix_timestamp(circuitbreaker_rule_relation.mtime), + name, namespace, IFNULL(business, ""), IFNULL(department, ""), IFNULL(comment, ""), inbounds, outbounds, + token, owner, revision, circuitbreaker_rule.flag, + unix_timestamp(circuitbreaker_rule.ctime), unix_timestamp(circuitbreaker_rule.mtime) + from circuitbreaker_rule_relation, circuitbreaker_rule ` + return str +} diff --git a/store/defaultStore/common.go b/store/defaultStore/common.go new file mode 100644 index 000000000..731fd40cd --- /dev/null +++ b/store/defaultStore/common.go @@ -0,0 +1,153 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package defaultStore + +import ( + "database/sql" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/store" +) + +// query +type QueryHandler func(query string, args ...interface{}) (*sql.Rows, error) + +// 批量查询数据的回调函数 +type BatchHandler func(objects []interface{}) error + +// 批量查询数据的对外接口 +// 每次最多查询200个 +func BatchQuery(label string, data []interface{}, handler BatchHandler) error { + //start := time.Now() + maxCount := 200 + beg := 0 + remain := len(data) + if remain == 0 { + return nil + } + + progress := 0 + for { + if remain > maxCount { + if err := handler(data[beg : beg+maxCount]); err != nil { + return err + } + + beg += maxCount + remain -= maxCount + progress += maxCount + if progress%20000 == 0 { + log.Infof("[Store][database][Batch] query (%s) progress(%d / %d)", label, progress, len(data)) + } + } else { + if err := handler(data[beg : beg+remain]); err != nil { + return err + } + break + } + } + //log.Infof("[Store][database][Batch] consume time: %v", time.Now().Sub(start)) + return nil +} + +/** + * @brief 批量操作 + * @note 每次最多操作100个 + */ +func BatchOperation(label string, data []interface{}, handler BatchHandler) error { + if data == nil { + return nil + } + maxCount := 100 + progress := 0 + for begin := 0; begin < len(data); begin += maxCount { + end := begin + maxCount + if end > len(data) { + end = len(data) + } + if err := handler(data[begin:end]); err != nil { + return err + } + progress += end - begin + if progress%maxCount == 0 { + log.Infof("[Store][database][Batch] operation (%s) progress(%d/%d)", label, progress, len(data)) + } + } + return nil +} + +// 单独查询count个数的执行函数 +func queryEntryCount(conn *BaseDB, str string, args []interface{}) (uint32, error) { + var count uint32 + var err error + Retry("queryRow", func() error { + err = conn.QueryRow(str, args...).Scan(&count) + return err + }) + switch { + case err == sql.ErrNoRows: + log.Errorf("[Store][database] not found any entry(%s)", str) + return 0, err + case err != nil: + log.Errorf("[Store][database] query entry count(%s) err: %s", str, err.Error()) + return 0, err + default: + return count, nil + } +} + +// 别名查询转换 +var aliasFilter2Where = map[string]string{ + "service": "source.name", + "namespace": "source.namespace", + "alias": "alias.name", + "owner": "alias.owner", +} + +// 别名查询字段转换函数 +func serviceAliasFilter2Where(filter map[string]string) map[string]string { + out := make(map[string]string) + for k, v := range filter { + if d, ok := aliasFilter2Where[k]; ok { + out[d] = v + } else { + out[k] = v + } + } + + return out +} + +/** + * @brief 检查数据库处理返回的行数 + */ +func checkDataBaseAffectedRows(result sql.Result, counts ...int64) error { + n, err := result.RowsAffected() + if err != nil { + log.Errorf("[Store][Database] get rows affected err: %s", err.Error()) + return err + } + + for _, c := range counts { + if n == c { + return nil + } + } + + log.Errorf("[Store][Database] get rows affected result(%d) is not match expect(%+v)", n, counts) + return store.NewStatusError(store.AffectedRowsNotMatch, "affected rows not matched") +} diff --git a/store/defaultStore/default.go b/store/defaultStore/default.go new file mode 100644 index 000000000..6e8683ba8 --- /dev/null +++ b/store/defaultStore/default.go @@ -0,0 +1,245 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package defaultStore + +import ( + "errors" + "fmt" + "time" + + "github.com/polarismesh/polaris-server/plugin" + + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/store" + + // 使用mysql库 + _ "github.com/go-sql-driver/mysql" +) + +const ( + SystemNamespace = "Polaris" + STORENAME = "defaultStore" + DefaultConnMaxLifetime = 60 * 30 // 默认是30分钟 +) + +/** + * @brief 自动引入包初始化函数 + */ +func init() { + s := &stableStore{} + _ = store.RegisterStore(s) +} + +/** + * @brief 实现了Store接口 + */ +type stableStore struct { + *namespaceStore + *businessStore + *serviceStore + *instanceStore + *routingConfigStore + *l5Store + *rateLimitStore + *circuitBreakerStore + *platformStore + + // 主数据库,可以进行读写 + master *BaseDB + // 对主数据库的事务操作,可读写 + masterTx *BaseDB + // 备数据库,提供只读 + slave *BaseDB + start bool + metaTask *TaskManager +} + +/** + * @brief 实现Name函数 + */ +func (s *stableStore) Name() string { + return STORENAME +} + +/** + * @brief 初始化函数 + */ +func (s *stableStore) Initialize(conf *store.Config) error { + if s.start { + return errors.New("store has been Initialize") + } + + masterConfig, slaveConfig, err := parseDatabaseConf(conf.Option) + if err != nil { + return err + } + master, err := NewBaseDB(masterConfig, plugin.GetParsePassword()) + if err != nil { + return err + } + s.master = master + + masterTx, err := NewBaseDB(masterConfig, plugin.GetParsePassword()) + if err != nil { + return err + } + s.masterTx = masterTx + + if slaveConfig != nil { + log.Infof("[Store][database] use slave database config: %+v", slaveConfig) + slave, err := NewBaseDB(slaveConfig, plugin.GetParsePassword()) + if err != nil { + return err + } + s.slave = slave + } + // 如果slave为空,意味着slaveConfig为空,用master数据库替代 + if s.slave == nil { + s.slave = s.master + } + + log.Infof("[Store][database] connect the database successfully") + + s.start = true + s.newStore() + return nil +} + +// return slave, master, error +func parseDatabaseConf(opt map[string]interface{}) (*dbConfig, *dbConfig, error) { + // 必填 + masterEnter, ok := opt["master"] + if !ok || masterEnter == nil { + return nil, nil, errors.New("database master db config is missing") + } + masterConfig, err := parseStoreConfig(masterEnter) + if err != nil { + return nil, nil, err + } + + // 只读数据库可选 + slaveEntry, ok := opt["slave"] + if !ok || slaveEntry == nil { + return masterConfig, nil, nil + } + slaveConfig, err := parseStoreConfig(slaveEntry) + if err != nil { + return nil, nil, err + } + + return masterConfig, slaveConfig, nil +} + +// 解析store的配置 +func parseStoreConfig(opts interface{}) (*dbConfig, error) { + obj, ok := opts.(map[interface{}]interface{}) + if !ok { + return nil, errors.New("database config is error") + } + dbType, _ := obj["dbType"].(string) + dbUser, _ := obj["dbUser"].(string) + dbPwd, _ := obj["dbPwd"].(string) + dbAddr, _ := obj["dbAddr"].(string) + dbName, _ := obj["dbName"].(string) + if dbType == "" || dbUser == "" || dbPwd == "" || dbAddr == "" || dbName == "" { + return nil, fmt.Errorf("Config Plugin %s missing database param", STORENAME) + } + + c := &dbConfig{ + dbType: dbType, + dbUser: dbUser, + dbPwd: dbPwd, + dbAddr: dbAddr, + dbName: dbName, + } + if maxOpenConns, _ := obj["maxOpenConns"].(int); maxOpenConns > 0 { + c.maxOpenConns = maxOpenConns + } + if maxIdleConns, _ := obj["maxIdleConns"].(int); maxIdleConns > 0 { + c.maxIdleConns = maxIdleConns + } + c.connMaxLifetime = DefaultConnMaxLifetime + if connMaxLifetime, _ := obj["connMaxLifetime"].(int); connMaxLifetime > 0 { + c.connMaxLifetime = connMaxLifetime + } + + if isolationLevel, _ := obj["txIsolationLevel"].(int); isolationLevel > 0 { + c.txIsolationLevel = isolationLevel + } + return c, nil +} + +/** + * @brief 退出函数 + */ +func (s *stableStore) Destroy() error { + if s.master != nil { + _ = s.master.Close() + } + if s.masterTx != nil { + _ = s.masterTx.Close() + } + if s.slave != nil { + _ = s.slave.Close() + } + + return nil +} + +/** + * @brief 创建一个事务 + */ +func (s *stableStore) CreateTransaction() (store.Transaction, error) { + // 每次创建事务前,还是需要ping一下 + _ = s.masterTx.Ping() + + nt := &transaction{} + tx, err := s.masterTx.Begin() + if err != nil { + log.Errorf("[Store][database] database begin err: %s", err.Error()) + return nil, err + } + + nt.tx = tx + return nt, nil +} + +// 初始化子类 +func (s *stableStore) newStore() { + s.namespaceStore = &namespaceStore{db: s.master} + s.businessStore = &businessStore{db: s.master} + + s.serviceStore = &serviceStore{master: s.master, slave: s.slave} + + s.instanceStore = &instanceStore{master: s.master, slave: s.slave} + + s.routingConfigStore = &routingConfigStore{master: s.master, slave: s.slave} + + s.l5Store = &l5Store{db: s.master} + + s.rateLimitStore = &rateLimitStore{db: s.master} + + s.circuitBreakerStore = &circuitBreakerStore{master: s.master, slave: s.slave} + + s.platformStore = &platformStore{master: s.master} +} + +// time.Time转为字符串时间 +func time2String(t time.Time) string { + return t.Format("2006-01-02 15:04:05") +} diff --git a/store/defaultStore/instance.go b/store/defaultStore/instance.go new file mode 100644 index 000000000..4005ceb18 --- /dev/null +++ b/store/defaultStore/instance.go @@ -0,0 +1,1154 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package defaultStore + +import ( + "database/sql" + "errors" + v1 "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/store" + "time" + + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" +) + +/** + * @brief 实现了InstanceStore接口 + */ +type instanceStore struct { + master *BaseDB // 大部分操作都用主数据库 + slave *BaseDB // 缓存相关的读取,请求到slave +} + +/** + * @brief 添加实例 + */ +func (ins *instanceStore) AddInstance(instance *model.Instance) error { + // 新增数据之前,必须先清理老数据 + if err := ins.CleanInstance(instance.ID()); err != nil { + return err + } + + err := RetryTransaction("addInstance", func() error { + return ins.addInstance(instance) + }) + return store.Error(err) +} + +// addInstance +func (ins *instanceStore) addInstance(instance *model.Instance) error { + tx, err := ins.master.Begin() + if err != nil { + log.Errorf("[Store][database] add instance tx begin err: %s", err.Error()) + return err + } + defer func() { _ = tx.Rollback() }() + + // 先对服务加锁 + revision, err := rlockServiceWithID(tx.QueryRow, instance.ServiceID) + if err != nil { + log.Errorf("[Store][database] rlock service(%s) err: %s", instance.ServiceID, err.Error()) + return err + } + if revision == "" { + log.Errorf("[Store][database] not found service(%s)", instance.ServiceID) + return store.NewStatusError(store.NotFoundService, "not found service") + } + + if err := addMainInstance(tx, instance); err != nil { + log.Errorf("[Store][database] add instance main insert err: %s", err.Error()) + return err + } + + if err := addInstanceCheck(tx, instance); err != nil { + return err + } + + if err := addInstanceMeta(tx, instance.ID(), instance.Metadata()); err != nil { + log.Errorf("[Store][database] add instance meta err: %s", err.Error()) + return err + } + + if err := tx.Commit(); err != nil { + log.Errorf("[Store][database] add instance commit tx err: %s", err.Error()) + return err + } + + return nil +} + +// 批量增加实例 +func (ins *instanceStore) BatchAddInstances(instances []*model.Instance) error { + // 直接清理所有的老数据 + if err := ins.BatchClearInstances(instances); err != nil { + log.Errorf("[Store][database] batch clear instances err: %s", err.Error()) + return err + } + + err := RetryTransaction("batchAddInstances", func() error { + return ins.batchAddInstances(instances) + }) + return store.Error(err) +} + +// batch add instances +func (ins *instanceStore) batchAddInstances(instances []*model.Instance) error { + tx, err := ins.master.Begin() + if err != nil { + log.Errorf("[Store][database] batch add instances begin tx err: %s", err.Error()) + return err + } + defer func() { _ = tx.Rollback() }() + + if err := batchAddMainInstances(tx, instances); err != nil { + log.Errorf("[Store][database] batch add main instances err: %s", err.Error()) + return err + } + if err := batchAddInstanceCheck(tx, instances); err != nil { + log.Errorf("[Store][database] batch add instance check err: %s", err.Error()) + return err + } + if err := batchAddInstanceMeta(tx, instances); err != nil { + log.Errorf("[Store][database] batch add instance metadata err: %s", err.Error()) + return err + } + + if err := tx.Commit(); err != nil { + log.Errorf("[Store][database] batch add instance commit tx err: %s", err.Error()) + return err + } + + return nil +} + +// 批量清理实例信息 +// 注意:依赖instance表修改结果,id外键修改为删除级联 +func (ins *instanceStore) BatchClearInstances(instances []*model.Instance) error { + if len(instances) == 0 { + return nil + } + + ids := make([]interface{}, 0, len(instances)) + var paramStr string + first := true + for _, entry := range instances { + if first { + paramStr = "(?" + first = false + } else { + paramStr += ", ?" + } + ids = append(ids, entry.ID()) + } + paramStr += ")" + + log.Infof("[Store][database] clean instance(%+v)", ids) // 先打印日志 + mainStr := "delete from instance where flag = 1 and id in " + paramStr + if _, err := ins.master.Exec(mainStr, ids...); err != nil { + log.Errorf("[Store][database] clean instance main err: %s", err.Error()) + return err + } + + return nil +} + +/** + * @brief 更新实例 + */ +func (ins *instanceStore) UpdateInstance(instance *model.Instance) error { + err := RetryTransaction("updateInstance", func() error { + return ins.updateInstance(instance) + }) + if err == nil { + return nil + } + + serr := store.Error(err) + if store.Code(serr) == store.DuplicateEntryErr { + serr = store.NewStatusError(store.DataConflictErr, err.Error()) + } + return serr +} + +// update instance +func (ins *instanceStore) updateInstance(instance *model.Instance) error { + tx, err := ins.master.Begin() + if err != nil { + log.Errorf("[Store][database] update instance tx begin err: %s", err.Error()) + return err + } + defer func() { _ = tx.Rollback() }() + + // 更新main表 + if err := updateInstanceMain(tx, instance); err != nil { + log.Errorf("[Store][database] update instance main err: %s", err.Error()) + return err + } + // 更新health check表 + if err := updateInstanceCheck(tx, instance); err != nil { + log.Errorf("[Store][database] update instance check err: %s", err.Error()) + return err + } + // 更新meta表 + if err := updateInstanceMeta(tx, instance); err != nil { + log.Errorf("[Store][database] update instance meta err: %s", err.Error()) + return err + } + + if err := tx.Commit(); err != nil { + log.Errorf("[Store][database] update instance commit tx err: %s", err.Error()) + return err + } + + return nil +} + +// 清理数据 +// 后续修改instance表,id外键删除级联,那么可以执行一次delete操作 +func (ins *instanceStore) CleanInstance(instanceID string) error { + log.Infof("[Store][database] clean instance(%s)", instanceID) + mainStr := "delete from instance where id = ? and flag = 1" + if _, err := ins.master.Exec(mainStr, instanceID); err != nil { + log.Errorf("[Store][database] clean instance(%s), err: %s", instanceID, err.Error()) + return store.Error(err) + } + return nil +} + +/** + * @brief 删除一个实例,删除实例实际上是把flag置为1 + */ +func (ins *instanceStore) DeleteInstance(instanceID string) error { + if instanceID == "" { + return errors.New("Delete Instance Missing instance id") + } + + str := "update instance set flag = 1, mtime = sysdate() where `id` = ?" + _, err := ins.master.Exec(str, instanceID) + return store.Error(err) +} + +// 批量删除实例 +func (ins *instanceStore) BatchDeleteInstances(ids []interface{}) error { + return BatchOperation("delete-instance", ids, func(objects []interface{}) error { + if len(objects) == 0 { + return nil + } + str := `update instance set flag = 1, mtime = sysdate() where id in ( ` + PlaceholdersN(len(objects)) + `)` + _, err := ins.master.Exec(str, objects...) + return store.Error(err) + }) +} + +/** + * @brief 获取单个实例详情,只返回有效的数据 + */ +func (ins *instanceStore) GetInstance(instanceID string) (*model.Instance, error) { + instance, err := ins.getInstance(instanceID) + if err != nil { + return nil, err + } + + // 如果实例无效,则不返回 + if instance != nil && !instance.Valid { + return nil, nil + } + + return instance, nil +} + +// 检查实例是否存在 +func (ins *instanceStore) CheckInstancesExisted(ids map[string]bool) (map[string]bool, error) { + if len(ids) == 0 { + return nil, nil + } + + str := "select id from instance where flag = 0 and id in(" + PlaceholdersN(len(ids)) + ")" + args := make([]interface{}, 0, len(ids)) + for key := range ids { + args = append(args, key) + } + + rows, err := ins.master.Query(str, args...) + if err != nil { + log.Errorf("[Store][database] check instances existed query err: %s", err.Error()) + return nil, err + } + var idx string + for rows.Next() { + if err := rows.Scan(&idx); err != nil { + log.Errorf("[Store][database] check instances existed scan err: %s", err.Error()) + return nil, err + } + ids[idx] = true + } + + return ids, nil +} + +// 批量获取实例的serviceID +func (ins *instanceStore) GetInstancesBrief(ids map[string]bool) (map[string]*model.Instance, error) { + if len(ids) == 0 { + return nil, nil + } + + str := `select instance.id, host, port, name, namespace, token, IFNULL(platform_id,"") from service, instance + where instance.flag = 0 and service.flag = 0 + and service.id = instance.service_id and instance.id in (` + PlaceholdersN(len(ids)) + ")" + args := make([]interface{}, 0, len(ids)) + for key := range ids { + args = append(args, key) + } + + rows, err := ins.master.Query(str, args...) + if err != nil { + log.Errorf("[Store][database] get instances service token query err: %s", err.Error()) + return nil, err + } + + out := make(map[string]*model.Instance, len(ids)) + var item model.ExpandInstanceStore + var instance model.InstanceStore + item.ServiceInstance = &instance + for rows.Next() { + if err := rows.Scan(&instance.ID, &instance.Host, &instance.Port, + &item.ServiceName, &item.Namespace, &item.ServiceToken, &item.ServicePlatformID); err != nil { + log.Errorf("[Store][database] get instances service token scan err: %s", err.Error()) + return nil, err + } + + out[instance.ID] = model.ExpandStore2Instance(&item) + } + + return out, nil +} + +// 获取有效的实例总数 +func (ins *instanceStore) GetInstancesCount() (uint32, error) { + countStr := "select count(*) from instance where flag = 0" + var count uint32 + var err error + Retry("query-instance-row", func() error { + err = ins.master.QueryRow(countStr).Scan(&count) + return err + }) + switch { + case err == sql.ErrNoRows: + return 0, nil + case err != nil: + log.Errorf("[Store][database] get instances count scan err: %s", err.Error()) + return 0, err + default: + } + + return count, nil +} + +/** + * @brief 根据服务和host获取实例 + * @note 不包括metadata + */ +func (ins *instanceStore) GetInstancesMainByService(serviceID, host string) ([]*model.Instance, error) { + // 只查询有效的服务实例 + str := genInstanceSelectSQL() + " where service_id = ? and host = ? and flag = 0" + rows, err := ins.master.Query(str, serviceID, host) + if err != nil { + log.Errorf("[Store][database] get instances main query err: %s", err.Error()) + return nil, err + } + + var out []*model.Instance + err = callFetchInstanceRows(rows, func(entry *model.InstanceStore) (b bool, e error) { + out = append(out, model.Store2Instance(entry)) + return true, nil + }) + if err != nil { + log.Errorf("[Store][database] call fetch instance rows err: %s", err.Error()) + return nil, err + } + + return out, nil +} + +/** + * @brief 根据过滤条件查看对应服务实例及数目 + */ +func (ins *instanceStore) GetExpandInstances(filter, metaFilter map[string]string, offset uint32, + limit uint32) (uint32, []*model.Instance, error) { + // 只查询有效的实例列表 + filter["instance.flag"] = "0" + + out, err := ins.getExpandInstances(filter, metaFilter, offset, limit) + if err != nil { + return 0, nil, err + } + + num, err := ins.getExpandInstancesCount(filter, metaFilter) + if err != nil { + return 0, nil, err + } + return num, out, err +} + +/** + * @brief 根据过滤条件查看对应服务实例 + */ +func (ins *instanceStore) getExpandInstances(filter, metaFilter map[string]string, offset uint32, + limit uint32) ([]*model.Instance, error) { + // 这种情况意味着,不需要详细的数据,可以不用query了 + if limit == 0 { + return make([]*model.Instance, 0), nil + } + _, isName := filter["name"] + _, isNamespace := filter["namespace"] + _, isHost := filter["host"] + needForceIndex := isName || isNamespace || isHost + + str := genExpandInstanceSelectSQL(needForceIndex) + order := &Order{"instance.mtime", "desc"} + str, args := genWhereSQLAndArgs(str, filter, metaFilter, order, offset, limit) + + rows, err := ins.master.Query(str, args...) + if err != nil { + log.Errorf("[Store][database] get instance by filters query err: %s, str: %s, args: %v", err.Error(), str, args) + return nil, err + } + + out, err := ins.getRowExpandInstances(rows) + if err != nil { + log.Errorf("[Store][database] get row instances err: %s", err.Error()) + return nil, err + } + + return out, nil +} + +/** + * @brief 根据过滤条件查看对应服务实例的数目 + */ +func (ins *instanceStore) getExpandInstancesCount(filter, metaFilter map[string]string) (uint32, error) { + str := `select count(*) from instance ` + // 查询条件是否有service表中的字段 + _, isName := filter["name"] + _, isNamespace := filter["namespace"] + if isName || isNamespace { + str += `inner join service on instance.service_id = service.id ` + } + str, args := genWhereSQLAndArgs(str, filter, metaFilter, nil, 0, 1) + + var count uint32 + var err error + Retry("query-instance-row", func() error { + err = ins.master.QueryRow(str, args...).Scan(&count) + return err + }) + switch { + case err == sql.ErrNoRows: + log.Errorf("[Store][database] no row with this expand instance filter") + return count, err + case err != nil: + log.Errorf("[Store][database] get expand instance count by filter err: %s", err.Error()) + return count, err + default: + return count, nil + } +} + +/** + * @brief 根据mtime获取增量修改数据 +* 这里会返回所有的数据的,包括valid=false的数据 +* 对于首次拉取,firstUpdate=true,只会拉取flag!=1的数据 +*/ +func (ins *instanceStore) GetMoreInstances(mtime time.Time, firstUpdate, needMeta bool, serviceID []string) ( + map[string]*model.Instance, error) { + if needMeta { + instances, err := ins.getMoreInstancesMainWithMeta(mtime, firstUpdate, serviceID) + if err != nil { + return nil, err + } + return instances, nil + } else { + instances, err := ins.getMoreInstancesMain(mtime, firstUpdate, serviceID) + if err != nil { + return nil, err + } + return instances, nil + } +} + +/** + * @brief 根据实例ID获取实例的metadata + */ +func (ins *instanceStore) GetInstanceMeta(instanceID string) (map[string]string, error) { + str := "select `mkey`, `mvalue` from instance_metadata where id = ?" + rows, err := ins.master.Query(str, instanceID) + if err != nil { + log.Errorf("[Store][database] query instance meta err: %s", err.Error()) + return nil, err + } + defer rows.Close() + + out := make(map[string]string) + var key, value string + for rows.Next() { + if err := rows.Scan(&key, &value); err != nil { + log.Errorf("[Store][database] get instance meta rows scan err: %s", err.Error()) + return nil, err + } + + out[key] = value + } + if err := rows.Err(); err != nil { + log.Errorf("[Store][database] get instance meta rows next err: %s", err.Error()) + return nil, err + } + + return out, nil +} + +/** + * @brief 设置实例健康状态 + */ +func (ins *instanceStore) SetInstanceHealthStatus(instanceID string, flag int, revision string) error { + str := "update instance set health_status = ?, revision = ?, mtime = sysdate() where `id` = ?" + _, err := ins.master.Exec(str, flag, revision, instanceID) + return store.Error(err) +} + +/** + * @brief 批量设置实例隔离状态 + */ +func (ins *instanceStore) BatchSetInstanceIsolate(ids []interface{}, isolate int, revision string) error { + return BatchOperation("set-instance-isolate", ids, func(objects []interface{}) error { + if len(objects) == 0 { + return nil + } + str := "update instance set isolate = ?, revision = ?, mtime = sysdate() where id in " + str += "(" + PlaceholdersN(len(objects)) + ")" + args := make([]interface{}, 0, len(objects)+2) + args = append(args, isolate) + args = append(args, revision) + args = append(args, objects...) + _, err := ins.master.Exec(str, args...) + return store.Error(err) + }) +} + +// 内部获取instance函数,根据instanceID,直接读取元数据,不做其他过滤 +func (ins *instanceStore) getInstance(instanceID string) (*model.Instance, error) { + str := genInstanceSelectSQL() + " where instance.id = ?" + rows, err := ins.master.Query(str, instanceID) + if err != nil { + log.Errorf("[Store][database] get instance query err: %s", err.Error()) + return nil, err + } + + out, err := fetchInstanceRows(rows) + if err != nil { + return nil, err + } + + if len(out) == 0 { + return nil, err + } + + meta, err := ins.GetInstanceMeta(out[0].ID()) + if err != nil { + return nil, err + } + out[0].MallocProto() + out[0].Proto.Metadata = meta + + return out[0], nil +} + +/** + * @brief 获取增量instance+healthcheck+meta内容 + * @note ro库有多个实例,且主库到ro库各实例的同步时间不一致。为避免获取不到meta,需要采用一条sql语句获取全部数据 + */ +func (ins *instanceStore) getMoreInstancesMainWithMeta(mtime time.Time, firstUpdate bool, serviceID []string) ( + map[string]*model.Instance, error) { + // 首次拉取 + if firstUpdate { + // 获取全量服务实例 + instances, err := ins.getMoreInstancesMain(mtime, firstUpdate, serviceID) + if err != nil { + log.Errorf("[Store][database] get more instance main err: %s", err.Error()) + return nil, err + } + // 获取全量服务实例元数据 + str := "select id, mkey, mvalue from instance_metadata" + rows, err := ins.slave.Query(str) + if err != nil { + log.Errorf("[Store][database] acquire instances meta query err: %s", err.Error()) + return nil, err + } + if err := fetchInstanceMetaRows(instances, rows); err != nil { + return nil, err + } + return instances, nil + } + + // 非首次拉取 + str := genCompleteInstanceSelectSQL() + " where instance.mtime >= ?" + args := make([]interface{}, 0, len(serviceID)+1) + args = append(args, time2String(mtime)) + + if len(serviceID) > 0 { + str += " and service_id in (" + PlaceholdersN(len(serviceID)) + str += ")" + } + for _, id := range serviceID { + args = append(args, id) + } + + rows, err := ins.slave.Query(str, args...) + if err != nil { + log.Errorf("[Store][database] get more instance query err: %s", err.Error()) + return nil, err + } + return fetchInstanceWithMetaRows(rows) +} + +/** + * @brief 获取instance main+health_check+instance_metadata rows里面的数据 + */ +func fetchInstanceWithMetaRows(rows *sql.Rows) (map[string]*model.Instance, error) { + if rows == nil { + return nil, nil + } + defer rows.Close() + + out := make(map[string]*model.Instance) + var item model.InstanceStore + var id, mKey, mValue string + progress := 0 + for rows.Next() { + progress++ + if progress%100000 == 0 { + log.Infof("[Store][database] instance+meta fetch rows progress: %d", progress) + } + if err := rows.Scan(&item.ID, &item.ServiceID, &item.VpcID, &item.Host, &item.Port, &item.Protocol, + &item.Version, &item.HealthStatus, &item.Isolate, &item.Weight, &item.EnableHealthCheck, + &item.LogicSet, &item.Region, &item.Zone, &item.Campus, &item.Priority, &item.Revision, + &item.Flag, &item.CheckType, &item.TTL, &id, &mKey, &mValue, + &item.CreateTime, &item.ModifyTime); err != nil { + log.Errorf("[Store][database] fetch instance+meta rows err: %s", err.Error()) + return nil, err + } + + if _, ok := out[item.ID]; !ok { + out[item.ID] = model.Store2Instance(&item) + } + // 实例存在meta + if id != "" { + if out[item.ID].Proto.Metadata == nil { + out[item.ID].Proto.Metadata = make(map[string]string) + } + out[item.ID].Proto.Metadata[mKey] = mValue + } + } + if err := rows.Err(); err != nil { + log.Errorf("[Store][database] fetch instance+metadata rows next err: %s", err.Error()) + return nil, err + } + return out, nil +} + +// 获取增量instances 主表内容,health_check内容 +func (ins *instanceStore) getMoreInstancesMain(mtime time.Time, firstUpdate bool, serviceID []string) ( + map[string]*model.Instance, error) { + str := genInstanceSelectSQL() + " where instance.mtime >= ?" + args := make([]interface{}, 0, len(serviceID)+1) + args = append(args, time2String(mtime)) + + if firstUpdate { + str += " and flag != 1" // nolint + } + + if len(serviceID) > 0 { + str += " and service_id in (" + PlaceholdersN(len(serviceID)) + str += ")" + } + for _, id := range serviceID { + args = append(args, id) + } + + rows, err := ins.slave.Query(str, args...) + if err != nil { + log.Errorf("[Store][database] get more instance query err: %s", err.Error()) + return nil, err + } + + out := make(map[string]*model.Instance) + err = callFetchInstanceRows(rows, func(entry *model.InstanceStore) (b bool, e error) { + out[entry.ID] = model.Store2Instance(entry) + return true, nil + }) + if err != nil { + log.Errorf("[Store][database] call fetch instance rows err: %s", err.Error()) + return nil, err + } + + return out, nil +} + +// 根据rows获取对应expandInstance +func (ins *instanceStore) getRowExpandInstances(rows *sql.Rows) ([]*model.Instance, error) { + out, err := fetchExpandInstanceRows(rows) + if err != nil { + return nil, err + } + + data := make([]interface{}, 0, len(out)) + for idx := range out { + data = append(data, out[idx].Proto) + } + + err = BatchQuery("expand-instance-metadata", data, func(objects []interface{}) error { + return ins.batchAcquireInstanceMetadata(objects) + }) + if err != nil { + log.Errorf("[Store][database] get expand instances metadata err: %s", err.Error()) + return nil, err + } + + return out, nil +} + +// 批量获取instance的metadata信息 +// web端获取实例的数据的时候使用 +func (ins *instanceStore) batchAcquireInstanceMetadata(instances []interface{}) error { + rows, err := batchQueryMetadata(ins.master.Query, instances) + if err != nil { + return err + } + if rows == nil { + return nil + } + defer rows.Close() + + out := make(map[string]map[string]string) + var id, key, value string + for rows.Next() { + if err := rows.Scan(&id, &key, &value); err != nil { + log.Errorf("[Store][database] multi query instance metadata rows scan err: %s", err.Error()) + return err + } + if _, ok := out[id]; !ok { + out[id] = make(map[string]string) + } + out[id][key] = value + } + if err := rows.Err(); err != nil { + log.Errorf("[Store][database] multi query instance metadata rows next err: %s", err.Error()) + return err + } + + // 把接收到的metadata,设置到instance中 + // 这里会有两层循环,后续可以优化 TODO + for id, meta := range out { + for _, ele := range instances { + if id == ele.(*v1.Instance).GetId().GetValue() { + ele.(*v1.Instance).Metadata = meta + break + } + } + } + + return nil +} + +// 批量查找metadata +func batchQueryMetadata(queryHandler QueryHandler, instances []interface{}) (*sql.Rows, error) { + if len(instances) == 0 { + return nil, nil + } + + str := "select `id`, `mkey`, `mvalue` from instance_metadata where id in(" + first := true + args := make([]interface{}, 0, len(instances)) + for _, ele := range instances { + if first { + str += "?" + first = false + } else { + str += ",?" + } + args = append(args, ele.(*v1.Instance).GetId().GetValue()) + } + str += ")" + + rows, err := queryHandler(str, args...) + if err != nil { + log.Errorf("[Store][database] multi query instance metadata err: %s", err.Error()) + return nil, err + } + + return rows, nil +} + +// 往instance主表中增加数据 +func addMainInstance(tx *BaseTx, instance *model.Instance) error { + // #lizard forgives + str := `insert into instance(id, service_id, vpc_id, host, port, protocol, version, health_status, isolate, + weight, enable_health_check, logic_set, cmdb_region, cmdb_zone, cmdb_idc, priority, revision, ctime, mtime) + values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, sysdate(), sysdate())` + _, err := tx.Exec(str, instance.ID(), instance.ServiceID, instance.VpcID(), instance.Host(), instance.Port(), + instance.Protocol(), instance.Version(), instance.Healthy(), instance.Isolate(), instance.Weight(), + instance.EnableHealthCheck(), instance.LogicSet(), instance.Location().GetRegion().GetValue(), + instance.Location().GetZone().GetValue(), instance.Location().GetCampus().GetValue(), + instance.Priority(), instance.Revision()) + return err +} + +// 批量增加main instance数据 +func batchAddMainInstances(tx *BaseTx, instances []*model.Instance) error { + str := `insert into instance(id, service_id, vpc_id, host, port, protocol, version, health_status, isolate, + weight, enable_health_check, logic_set, cmdb_region, cmdb_zone, cmdb_idc, priority, revision, ctime, mtime) + values` + first := true + args := make([]interface{}, 0) + for _, entry := range instances { + if !first { + str += "," + } + str += "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, sysdate(), sysdate())" + first = false + args = append(args, entry.ID(), entry.ServiceID, entry.VpcID(), entry.Host(), entry.Port()) + args = append(args, entry.Protocol(), entry.Version(), entry.Healthy(), entry.Isolate(), + entry.Weight()) + args = append(args, entry.EnableHealthCheck(), entry.LogicSet(), + entry.Location().GetRegion().GetValue(), entry.Location().GetZone().GetValue(), + entry.Location().GetCampus().GetValue(), entry.Priority(), entry.Revision()) + } + + _, err := tx.Exec(str, args...) + return err +} + +// 往health_check加入健康检查信息 +func addInstanceCheck(tx *BaseTx, instance *model.Instance) error { + check := instance.HealthCheck() + if check == nil { + return nil + } + + str := "insert into health_check(`id`, `type`, `ttl`) values(?, ?, ?)" + _, err := tx.Exec(str, instance.ID(), check.GetType(), + check.GetHeartbeat().GetTtl().GetValue()) + return err +} + +// 批量增加healthCheck数据 +func batchAddInstanceCheck(tx *BaseTx, instances []*model.Instance) error { + str := "insert into health_check(`id`, `type`, `ttl`) values" + first := true + args := make([]interface{}, 0) + for _, entry := range instances { + if entry.HealthCheck() == nil { + continue + } + if !first { + str += "," + } + str += "(?,?,?)" + first = false + args = append(args, entry.ID(), entry.HealthCheck().GetType(), + entry.HealthCheck().GetHeartbeat().GetTtl().GetValue()) + } + // 不存在健康检查信息,直接返回 + if first { + return nil + } + + _, err := tx.Exec(str, args...) + return err + +} + +// 往表中加入instance meta数据 +func addInstanceMeta(tx *BaseTx, id string, meta map[string]string) error { + if len(meta) == 0 { + return nil + } + + str := "insert into instance_metadata(`id`, `mkey`, `mvalue`, `ctime`, `mtime`) values " + args := make([]interface{}, 0, len(meta)*3) + cnt := 0 + for key, value := range meta { + cnt++ + if cnt == len(meta) { + str += "(?, ?, ?, sysdate(), sysdate())" // nolint + } else { + str += "(?, ?, ?, sysdate(), sysdate()), " + } + + args = append(args, id) + args = append(args, key) + args = append(args, value) + } + + _, err := tx.Exec(str, args...) + return err +} + +// 批量增加metadata数据 +func batchAddInstanceMeta(tx *BaseTx, instances []*model.Instance) error { + str := "insert into instance_metadata(`id`, `mkey`, `mvalue`, `ctime`, `mtime`) values" + args := make([]interface{}, 0) + first := true + for _, entry := range instances { + if entry.Metadata() == nil || len(entry.Metadata()) == 0 { + continue + } + + for key, value := range entry.Metadata() { + if !first { + str += "," + } + str += "(?, ?, ?, sysdate(), sysdate())" // nolint + first = false + args = append(args, entry.ID(), key, value) + } + } + // 不存在metadata,直接返回 + if first { + return nil + } + + _, err := tx.Exec(str, args...) + return err +} + +// 更新instance的meta表 +func updateInstanceMeta(tx *BaseTx, instance *model.Instance) error { + // 只有metadata为nil的时候,则不用处理。 + // 如果metadata不为nil,但是len(metadata) == 0,则代表删除metadata + meta := instance.Metadata() + if meta == nil { + return nil + } + + deleteStr := "delete from instance_metadata where id = ?" + if _, err := tx.Exec(deleteStr, instance.ID()); err != nil { + return err + } + return addInstanceMeta(tx, instance.ID(), meta) +} + +// 更新instance的check表 +func updateInstanceCheck(tx *BaseTx, instance *model.Instance) error { + // healthCheck为空,则删除 + check := instance.HealthCheck() + if check == nil { + return deleteInstanceCheck(tx, instance.ID()) + } + + str := "replace into health_check(id, type, ttl) values(?, ?, ?)" + _, err := tx.Exec(str, instance.ID(), check.GetType(), + check.GetHeartbeat().GetTtl().GetValue()) + return err +} + +// 更新instance主表 +func updateInstanceMain(tx *BaseTx, instance *model.Instance) error { + str := `update instance set protocol = ?, + version = ?, health_status = ?, isolate = ?, weight = ?, enable_health_check = ?, logic_set = ?, + cmdb_region = ?, cmdb_zone = ?, cmdb_idc = ?, priority = ?, revision = ?, mtime = sysdate() where id = ?` + + _, err := tx.Exec(str, instance.Protocol(), instance.Version(), instance.Healthy(), instance.Isolate(), + instance.Weight(), instance.EnableHealthCheck(), instance.LogicSet(), + instance.Location().GetRegion().GetValue(), instance.Location().GetZone().GetValue(), + instance.Location().GetCampus().GetValue(), instance.Priority(), + instance.Revision(), instance.ID()) + + return err +} + +// 删除healthCheck数据 +func deleteInstanceCheck(tx *BaseTx, id string) error { + str := "delete from health_check where id = ?" + _, err := tx.Exec(str, id) + return err +} + +// 获取instance rows的内容 +func fetchInstanceRows(rows *sql.Rows) ([]*model.Instance, error) { + var out []*model.Instance + err := callFetchInstanceRows(rows, func(entry *model.InstanceStore) (b bool, e error) { + out = append(out, model.Store2Instance(entry)) + return true, nil + }) + if err != nil { + log.Errorf("[Store][database] call fetch instance rows err: %s", err.Error()) + return nil, err + } + + return out, nil +} + +// 带回调的fetch instance +func callFetchInstanceRows(rows *sql.Rows, callback func(entry *model.InstanceStore) (bool, error)) error { + if rows == nil { + return nil + } + defer rows.Close() + var item model.InstanceStore + progress := 0 + for rows.Next() { + progress++ + if progress%100000 == 0 { + log.Infof("[Store][database] instance fetch rows progress: %d", progress) + } + err := rows.Scan(&item.ID, &item.ServiceID, &item.VpcID, &item.Host, &item.Port, &item.Protocol, + &item.Version, &item.HealthStatus, &item.Isolate, &item.Weight, &item.EnableHealthCheck, + &item.LogicSet, &item.Region, &item.Zone, &item.Campus, &item.Priority, &item.Revision, + &item.Flag, &item.CheckType, &item.TTL, &item.CreateTime, &item.ModifyTime) + if err != nil { + log.Errorf("[Store][database] fetch instance rows err: %s", err.Error()) + return err + } + ok, err := callback(&item) + if err != nil { + return err + } + if !ok { + return nil + } + } + if err := rows.Err(); err != nil { + log.Errorf("[Store][database] instance rows catch err: %s", err.Error()) + return err + } + + return nil +} + +// 获取expandInstance rows的内容 +func fetchExpandInstanceRows(rows *sql.Rows) ([]*model.Instance, error) { + if rows == nil { + return nil, nil + } + defer rows.Close() + + var out []*model.Instance + var item model.ExpandInstanceStore + var instance model.InstanceStore + item.ServiceInstance = &instance + progress := 0 + for rows.Next() { + progress++ + if progress%50000 == 0 { + log.Infof("[Store][database] expand instance fetch rows progress: %d", progress) + } + err := rows.Scan(&instance.ID, &instance.ServiceID, &instance.VpcID, &instance.Host, &instance.Port, + &instance.Protocol, &instance.Version, &instance.HealthStatus, &instance.Isolate, + &instance.Weight, &instance.EnableHealthCheck, &instance.LogicSet, &instance.Region, + &instance.Zone, &instance.Campus, &instance.Priority, &instance.Revision, &instance.Flag, + &instance.CheckType, &instance.TTL, &item.ServiceName, &item.Namespace, + &instance.CreateTime, &instance.ModifyTime) + if err != nil { + log.Errorf("[Store][database] fetch instance rows err: %s", err.Error()) + return nil, err + } + out = append(out, model.ExpandStore2Instance(&item)) + } + + if err := rows.Err(); err != nil { + log.Errorf("[Store][database] instance rows catch err: %s", err.Error()) + return nil, err + } + + return out, nil +} + +// 解析获取instance metadata +func fetchInstanceMetaRows(instances map[string]*model.Instance, rows *sql.Rows) error { + if rows == nil { + return nil + } + defer rows.Close() + var id, key, value string + progress := 0 + for rows.Next() { + progress++ + if progress%500000 == 0 { + log.Infof("[Store][database] fetch instance meta progress: %d", progress) + } + if err := rows.Scan(&id, &key, &value); err != nil { + log.Errorf("[Store][database] fetch instance metadata rows scan err: %s", err.Error()) + return err + } + // 不在目标列表,不存储 + if _, ok := instances[id]; !ok { + continue + } + if instances[id].Proto.Metadata == nil { + instances[id].Proto.Metadata = make(map[string]string) + } + instances[id].Proto.Metadata[key] = value + } + if err := rows.Err(); err != nil { + log.Errorf("[Store][database] fetch instance metadata rows next err: %s", err.Error()) + return err + } + + return nil + +} + +// 生成instance的select sql语句 +func genInstanceSelectSQL() string { + str := `select instance.id, service_id, IFNULL(vpc_id,""), host, port, IFNULL(protocol, ""), IFNULL(version, ""), + health_status, isolate, weight, enable_health_check, IFNULL(logic_set, ""), IFNULL(cmdb_region, ""), + IFNULL(cmdb_zone, ""), IFNULL(cmdb_idc, ""), priority, revision, flag, IFNULL(health_check.type, -1), + IFNULL(health_check.ttl, 0), UNIX_TIMESTAMP(instance.ctime), UNIX_TIMESTAMP(instance.mtime) + from instance left join health_check + on instance.id = health_check.id ` + return str +} + +// 生成完整instance(主表+health_check+metadata)的sql语句 +func genCompleteInstanceSelectSQL() string { + str := `select instance.id, service_id, IFNULL(vpc_id,""), host, port, IFNULL(protocol, ""), IFNULL(version, ""), + health_status, isolate, weight, enable_health_check, IFNULL(logic_set, ""), IFNULL(cmdb_region, ""), + IFNULL(cmdb_zone, ""), IFNULL(cmdb_idc, ""), priority, revision, flag, IFNULL(health_check.type, -1), + IFNULL(health_check.ttl, 0), IFNULL(instance_metadata.id, ""), IFNULL(mkey, ""), IFNULL(mvalue, ""), + UNIX_TIMESTAMP(instance.ctime), UNIX_TIMESTAMP(instance.mtime) + from instance + left join health_check on instance.id = health_check.id + left join instance_metadata on instance.id = instance_metadata.id ` + return str +} + +// 生成expandInstance的select sql语句 +func genExpandInstanceSelectSQL(needForceIndex bool) string { + str := `select instance.id, service_id, IFNULL(vpc_id,""), host, port, IFNULL(protocol, ""), IFNULL(version, ""), + health_status, isolate, weight, enable_health_check, IFNULL(logic_set, ""), IFNULL(cmdb_region, ""), + IFNULL(cmdb_zone, ""), IFNULL(cmdb_idc, ""), priority, instance.revision, instance.flag, + IFNULL(health_check.type, -1), IFNULL(health_check.ttl, 0), service.name, service.namespace, + UNIX_TIMESTAMP(instance.ctime), UNIX_TIMESTAMP(instance.mtime) + from (service inner join instance ` + if needForceIndex { + str += `force index(service_id, host) ` + } + str += `on service.id = instance.service_id) left join health_check on instance.id = health_check.id ` + return str +} diff --git a/store/defaultStore/l5.go b/store/defaultStore/l5.go new file mode 100644 index 000000000..aa3832b36 --- /dev/null +++ b/store/defaultStore/l5.go @@ -0,0 +1,365 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package defaultStore + +import ( + "database/sql" + "fmt" + "time" + + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" +) + +/** + * @brief 实现了L5Store + */ +type l5Store struct { + db *BaseDB +} + +/** + * @brief 获取L5扩展数据 + */ +func (l5 *l5Store) GetL5Extend(serviceID string) (map[string]interface{}, error) { + return nil, nil +} + +/** + * @brief 保存L5扩展数据 + */ +func (l5 *l5Store) SetL5Extend(serviceID string, meta map[string]interface{}) (map[string]interface{}, error) { + return nil, nil +} + +// 获取下一个sid +func (l5 *l5Store) GenNextL5Sid(layoutID uint32) (string, error) { + var sid string + var err error + + err = RetryTransaction("genNextL5Sid", func() error { + sid, err = l5.genNextL5Sid(layoutID) + return nil + }) + + return sid, err +} + +// genNextL5Sid +func (l5 *l5Store) genNextL5Sid(layoutID uint32) (string, error) { + tx, err := l5.db.Begin() + if err != nil { + log.Errorf("[Store][database] get next l5 sid tx begin err: %s", err.Error()) + return "", err + } + defer func() { _ = tx.Rollback() }() + + getStr := "select module_id, interface_id, range_num from cl5_module limit 0, 1 for update" + var mid, iid, rnum uint32 + if err := tx.QueryRow(getStr).Scan(&mid, &iid, &rnum); err != nil { + log.Errorf("[Store][database] get next l5 sid err: %s", err.Error()) + return "", err + } + + rnum++ + if rnum >= 65536 { + rnum = 0 + iid++ + } + if iid >= 4096 { + iid = 1 + mid++ + } + + updateStr := "update cl5_module set module_id = ?, interface_id = ?, range_num = ?" + if _, err := tx.Exec(updateStr, mid, iid, rnum); err != nil { + log.Errorf("[Store][database] get next l5 sid, update module err: %s", err.Error()) + return "", err + } + // 更新完数据库之后,可以直接提交tx + if err := tx.Commit(); err != nil { + log.Errorf("[Store][database] get next l5 sid tx commit err: %s", err.Error()) + return "", err + } + + // 数据表已经更改,生成sid的元素说明是唯一的,可以组合sid了 + modID := mid<<6 + layoutID + cmdID := iid<<16 + rnum + return fmt.Sprintf("%d:%d", modID, cmdID), nil +} + +/** + * @brief 获取更多的增量数据 + */ +func (l5 *l5Store) GetMoreL5Extend(mtime time.Time) (map[string]map[string]interface{}, error) { + return nil, nil +} + +/** + * @brief 获取更多的L5 Route信息 + */ +func (l5 *l5Store) GetMoreL5Routes(flow uint32) ([]*model.Route, error) { + str := getL5RouteSelectSQL() + " where Fflow > ?" + rows, err := l5.db.Query(str, flow) + if err != nil { + log.Errorf("[Store][database] get more l5 route query err: %s", err.Error()) + return nil, err + } + + return l5RouteFetchRows(rows) +} + +/** + * @brief 获取更多的L5 Policy信息 + */ +func (l5 *l5Store) GetMoreL5Policies(flow uint32) ([]*model.Policy, error) { + str := getL5PolicySelectSQL() + " where Fflow > ?" + rows, err := l5.db.Query(str, flow) + if err != nil { + log.Errorf("[Store][database] get more l5 policy query err: %s", err.Error()) + return nil, err + } + + return l5PolicyFetchRows(rows) +} + +/** + * @brief 获取更多的L5 Section信息 + */ +func (l5 *l5Store) GetMoreL5Sections(flow uint32) ([]*model.Section, error) { + str := getL5SectionSelectSQL() + " where Fflow > ?" + rows, err := l5.db.Query(str, flow) + if err != nil { + log.Errorf("[Store][database] get more l5 section query err: %s", err.Error()) + return nil, err + } + + return l5SectionFetchRows(rows) +} + +/** + * @brief 获取更多的L5 IPConfig信息 + */ +func (l5 *l5Store) GetMoreL5IPConfigs(flow uint32) ([]*model.IPConfig, error) { + str := getL5IPConfigSelectSQL() + " where Fflow > ?" + rows, err := l5.db.Query(str, flow) + if err != nil { + log.Errorf("[Store][database] get more l5 ip config query err: %s", err.Error()) + return nil, err + } + + return l5IPConfigFetchRows(rows) +} + +/** + * @brief 生成L5 Route的select sql语句 + */ +func getL5RouteSelectSQL() string { + str := `select Fip, FmodId, FcmdId, FsetId, IFNULL(Fflag, 0), Fflow from t_route` + return str +} + +/** + * @brief 生成L5 Policy的select sql语句 + */ +func getL5PolicySelectSQL() string { + str := `select FmodId, Fdiv, Fmod, IFNULL(Fflag, 0), Fflow from t_policy` + return str +} + +/** + * @brief 生成L5 Section的select sql语句 + */ +func getL5SectionSelectSQL() string { + str := `select FmodId, Ffrom, Fto, Fxid, IFNULL(Fflag, 0), Fflow from t_section` + return str +} + +/** + * @brief 生成L5 IPConfig的select sql语句 + */ +func getL5IPConfigSelectSQL() string { + str := `select Fip, FareaId, FcityId, FidcId, IFNULL(Fflag, 0), Fflow from t_ip_config` + return str +} + +/** + * @brief 从route中取出rows的数据 + */ +func l5RouteFetchRows(rows *sql.Rows) ([]*model.Route, error) { + if rows == nil { + return nil, nil + } + defer rows.Close() + + var out []*model.Route + var flag int + + progress := 0 + for rows.Next() { + progress++ + if progress%100000 == 0 { + log.Infof("[Store][database] load cl5 route progress: %d", progress) + } + space := &model.Route{} + err := rows.Scan( + &space.IP, + &space.ModID, + &space.CmdID, + &space.SetID, + &flag, + &space.Flow) + if err != nil { + log.Errorf("[Store][database] fetch l5 route rows scan err: %s", err.Error()) + return nil, err + } + + space.Valid = true + if flag == 1 { + space.Valid = false + } + + out = append(out, space) + } + + if err := rows.Err(); err != nil { + log.Errorf("[Store][database] fetch l5 route rows next err: %s", err.Error()) + return nil, err + } + return out, nil +} + +/** + * @brief 从policy中取出rows的数据 + */ +func l5PolicyFetchRows(rows *sql.Rows) ([]*model.Policy, error) { + if rows == nil { + return nil, nil + } + defer rows.Close() + + var out []*model.Policy + var flag int + + for rows.Next() { + space := &model.Policy{} + err := rows.Scan( + &space.ModID, + &space.Div, + &space.Mod, + &flag, + &space.Flow) + if err != nil { + log.Errorf("[Store][database] fetch l5 policy rows scan err: %s", err.Error()) + return nil, err + } + + space.Valid = true + if flag == 1 { + space.Valid = false + } + + out = append(out, space) + } + if err := rows.Err(); err != nil { + log.Errorf("[Store][database] fetch l5 policy rows next err: %s", err.Error()) + return nil, err + } + return out, nil +} + +/** + * @brief 从section中取出rows的数据 + */ +func l5SectionFetchRows(rows *sql.Rows) ([]*model.Section, error) { + if rows == nil { + return nil, nil + } + defer rows.Close() + + var out []*model.Section + var flag int + + for rows.Next() { + space := &model.Section{} + err := rows.Scan( + &space.ModID, + &space.From, + &space.To, + &space.Xid, + &flag, + &space.Flow) + if err != nil { + log.Errorf("[Store][database] fetch section rows scan err: %s", err.Error()) + return nil, err + } + + space.Valid = true + if flag == 1 { + space.Valid = false + } + + out = append(out, space) + } + if err := rows.Err(); err != nil { + log.Errorf("[Store][database] fetch section rows next err: %s", err.Error()) + return nil, err + } + + return out, nil +} + +/** + * @brief 从ip config中取出rows的数据 + */ +func l5IPConfigFetchRows(rows *sql.Rows) ([]*model.IPConfig, error) { + if rows == nil { + return nil, nil + } + defer rows.Close() + + var out []*model.IPConfig + var flag int + for rows.Next() { + space := &model.IPConfig{} + err := rows.Scan( + &space.IP, + &space.AreaID, + &space.CityID, + &space.IdcID, + &flag, + &space.Flow) + if err != nil { + log.Errorf("[Store][database] fetch ip config rows scan err: %s", err.Error()) + return nil, err + } + + space.Valid = true + if flag == 1 { + space.Valid = false + } + + out = append(out, space) + } + if err := rows.Err(); err != nil { + log.Errorf("[Store][database] fetch ip config rows next err: %s", err.Error()) + return nil, err + } + + return out, nil +} diff --git a/store/defaultStore/meta_task.go b/store/defaultStore/meta_task.go new file mode 100644 index 000000000..09b9e8946 --- /dev/null +++ b/store/defaultStore/meta_task.go @@ -0,0 +1,227 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package defaultStore + +import ( + "errors" + "github.com/polarismesh/polaris-server/common/log" + "sync" + "time" + + "context" +) + +// 用户处理函数 +type Handler func(item interface{}) error + +// 任务传输结构体 +type Future struct { + item interface{} // 需要处理的参数 + handler Handler // 处理函数 + resp *ResponseFuture // 任务返回future +} + +// 处理任务返回的结构体 +type ResponseFuture struct { + finishCh chan struct{} // 任务执行成功的反馈chan + errCh chan error // 任务执行失败的反馈chan + total int // 本次任务总数 + label string // 任务的标签 + doneCnt int // 记录收到多少个task回复的 + notifyCnt int // 记录分发了多少个task +} + +// 等待任务执行 +func (r *ResponseFuture) wait() error { + //defer log.Infof("[TaskManager][%s] finish all task(%d)", r.label, r.total) + + ticker := time.NewTicker(time.Second * 5) + defer ticker.Stop() + + for { + select { + case err := <-r.errCh: + // 无论成功还失败,收到回复都要增加doneCnt + r.doneCnt++ + return err + case <-r.finishCh: + r.doneCnt++ + if r.doneCnt == r.total { + return nil + } + case <-ticker.C: + log.Infof("[TaskManager][%s] wait for task count(%d) response, finish progress(%d / %d)", + r.label, r.total, r.doneCnt, r.notifyCnt) + } + } +} + +// 返回函数 +func (r *ResponseFuture) reply(err error) { + if err != nil { + r.errCh <- err + } else { + r.finishCh <- struct{}{} + } +} + +// 每个任务集需要释放资源 +func (r *ResponseFuture) release() { + // 如果收到回复的个数==任务分发的个数,那么可以退出 + if r.doneCnt >= r.notifyCnt { + return + } + + waitCnt := 0 + ticker := time.NewTicker(time.Second * 5) // TODO + defer ticker.Stop() + + for { + select { + case <-r.errCh: + waitCnt++ + case <-r.finishCh: + waitCnt++ + case <-ticker.C: + log.Infof("[TaskManager][%s] response release progress(%d / %d)", + r.label, waitCnt, r.notifyCnt-r.doneCnt) + } + + if waitCnt+r.doneCnt == r.notifyCnt { + close(r.errCh) + close(r.finishCh) + return + } + } +} + +// 任务管理器,全局可以有多个,不过尽量全局只有一个 +type TaskManager struct { + recvCh chan *Future + concurrence int + exitCh chan struct{} +} + +// 新建任务管理器 +func NewTaskManager(concurrence int) (*TaskManager, error) { + if concurrence <= 0 { + return nil, errors.New("max channel count is error") + } + + t := &TaskManager{ + recvCh: make(chan *Future, concurrence), + concurrence: concurrence, + exitCh: make(chan struct{}), + } + + return t, nil +} + +// 执行任务集 +func (t *TaskManager) Do(label string, data []interface{}, handler Handler) error { + if len(data) == 0 { + return nil + } + + count := len(data) + maxCount := 64 + if count > maxCount { + count = maxCount + } + + resp := &ResponseFuture{ + finishCh: make(chan struct{}, count), + errCh: make(chan error, count), + total: len(data), + label: label, + doneCnt: 0, + notifyCnt: 0, + } + + ctx, cancel := context.WithCancel(context.Background()) + var wg sync.WaitGroup + + wg.Add(1) + go func(recvCh chan *Future) { + defer wg.Done() + for i := range data { + select { + case <-ctx.Done(): + return + default: + } + + resp.notifyCnt++ + future := &Future{ + item: data[i], + handler: handler, + resp: resp, + } + recvCh <- future + + if resp.notifyCnt%10000 == 0 { + log.Infof("[TaskManager][%s] task notify progress(%d / %d)", label, resp.notifyCnt, len(data)) + } + } + }(t.recvCh) + + // 先到等待任务执行,有可能执行失败,有可能全部执行完 + err := resp.wait() + + // 触发分发协程退出 + cancel() + + // 等待分发协程彻底退出 + wg.Wait() + + // 回收资源 + resp.release() + + return err +} + +// 开启运行任务管理器 +func (t *TaskManager) Start() { + log.Infof("[TaskManager] goroutine count(%d) will start", t.concurrence) + for i := 0; i < t.concurrence; i++ { + go t.worker(i) + } +} + +// 回收任务管理的资源,销毁任务管理器 +func (t *TaskManager) Release() { + close(t.exitCh) +} + +// 任务管理器的工作协程 +func (t *TaskManager) worker(index int) { + //log.Infof("[TaskManager] reading metadata worker(%d) running", index) + defer log.Infof("[TaskManager] reading metadata worker(%d) exit", index) + + for { + select { + case future := <-t.recvCh: + // 收到一个任务,执行任务设置的handler函数 + err := future.handler(future.item) + // 处理回复,保证每个回复都要发送 + future.resp.reply(err) + case <-t.exitCh: + return + } + } +} diff --git a/store/defaultStore/namespace.go b/store/defaultStore/namespace.go new file mode 100644 index 000000000..1943f5e25 --- /dev/null +++ b/store/defaultStore/namespace.go @@ -0,0 +1,292 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package defaultStore + +import ( + "database/sql" + "errors" + "fmt" + "github.com/polarismesh/polaris-server/store" + "time" + + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" +) + +/** + * @brief 实现了NamespaceStore + */ +type namespaceStore struct { + db *BaseDB +} + +/** + * @brief 添加命名空间 + */ +func (ns *namespaceStore) AddNamespace(namespace *model.Namespace) error { + if namespace.Name == "" || namespace.Owner == "" || namespace.Token == "" { + return errors.New("store add namespace some param are empty") + } + + // 先删除无效数据,再添加新数据 + if err := ns.cleanNamespace(namespace.Name); err != nil { + return err + } + + str := "insert into namespace(name, comment, token, owner, ctime, mtime) values(?,?,?,?,sysdate(),sysdate())" + _, err := ns.db.Exec(str, namespace.Name, namespace.Comment, namespace.Token, namespace.Owner) + return store.Error(err) +} + +/** + * @brief 更新命名空间,目前只更新owner + */ +func (ns *namespaceStore) UpdateNamespace(namespace *model.Namespace) error { + if namespace.Name == "" || namespace.Owner == "" { + return errors.New("store update namespace some param are empty") + } + + str := "update namespace set owner = ?, comment = ?,mtime = sysdate() where name = ?" + _, err := ns.db.Exec(str, namespace.Owner, namespace.Comment, namespace.Name) + return store.Error(err) +} + +/** + * @brief 更新命名空间token + */ +func (ns *namespaceStore) UpdateNamespaceToken(name string, token string) error { + if name == "" || token == "" { + return fmt.Errorf("Update Namespace Token missing some params") + } + + str := "update namespace set token = ?, mtime = sysdate() where name = ?" + _, err := ns.db.Exec(str, token, name) + return store.Error(err) +} + +/** + * @brief 展示owner下所有的命名空间 TODO + */ +func (ns *namespaceStore) ListNamespaces(owner string) ([]*model.Namespace, error) { + if owner == "" { + return nil, errors.New("store lst namespaces owner is empty") + } + + str := genNamespaceSelectSQL() + " where owner like '%?%'" + rows, err := ns.db.Query(str, owner) + if err != nil { + return nil, err + } + + return namespaceFetchRows(rows) +} + +/** + * @brief 根据名字获取命名空间详情,只返回有效的 + */ +func (ns *namespaceStore) GetNamespace(name string) (*model.Namespace, error) { + namespace, err := ns.getNamespace(name) + if err != nil { + return nil, err + } + + if namespace != nil && !namespace.Valid { + return nil, nil + } + + return namespace, nil +} + +/** + * @brief 根据过滤条件查询命名空间及数目 + */ +func (ns *namespaceStore) GetNamespaces(filter map[string][]string, offset, limit int) ( + []*model.Namespace, uint32, error) { + // 只查询有效数据 + filter["flag"] = []string{"0"} + + num, err := ns.getNamespacesCount(filter) + if err != nil { + return nil, 0, err + } + + out, err := ns.getNamespaces(filter, offset, limit) + if err != nil { + return nil, 0, err + } + + return out, num, nil +} + +/** + * @brief 根据mtime获取命名空间 + */ +func (ns *namespaceStore) GetMoreNamespaces(mtime time.Time) ([]*model.Namespace, error) { + str := genNamespaceSelectSQL() + " where UNIX_TIMESTAMP(mtime) >= ?" + rows, err := ns.db.Query(str, mtime.Unix()) + if err != nil { + log.Errorf("[Store][database] get more namespace query err: %s", err.Error()) + return nil, err + } + + return namespaceFetchRows(rows) +} + +/** + * @brief 根据相关条件查询对应命名空间数目 + */ +func (ns *namespaceStore) getNamespacesCount(filter map[string][]string) (uint32, error) { + str := `select count(*) from namespace ` + str, args := genNamespaceWhereSQLAndArgs(str, filter, nil, 0, 1) + + var count uint32 + err := ns.db.QueryRow(str, args...).Scan(&count) + switch { + case err == sql.ErrNoRows: + log.Errorf("[Store][database] no row with this namespace filter") + return count, err + case err != nil: + log.Errorf("[Store][database] get namespace count by filter err: %s", err.Error()) + return count, err + default: + return count, err + } +} + +/** + * @brief 根据相关条件查询对应命名空间 + */ +func (ns *namespaceStore) getNamespaces(filter map[string][]string, offset, limit int) ([]*model.Namespace, error) { + str := genNamespaceSelectSQL() + order := &Order{"mtime", "desc"} + str, args := genNamespaceWhereSQLAndArgs(str, filter, order, offset, limit) + + rows, err := ns.db.Query(str, args...) + if err != nil { + log.Errorf("[Store][database] get namespaces by filter query err: %s", err.Error()) + return nil, err + } + + return namespaceFetchRows(rows) +} + +// 获取namespace的内部函数,从数据库中拉取数据 +func (ns *namespaceStore) getNamespace(name string) (*model.Namespace, error) { + if name == "" { + return nil, errors.New("store get namespace name is empty") + } + + str := genNamespaceSelectSQL() + " where name = ?" + rows, err := ns.db.Query(str, name) + if err != nil { + log.Errorf("[Store][database] get namespace query err: %s", err.Error()) + return nil, err + } + + out, err := namespaceFetchRows(rows) + if err != nil { + return nil, err + } + + if len(out) == 0 { + return nil, nil + } + return out[0], nil +} + +// clean真实的数据,只有flag=1的数据才可以清除 +func (ns *namespaceStore) cleanNamespace(name string) error { + str := "delete from namespace where name = ? and flag = 1" + // 必须打印日志说明 + log.Infof("[Store][database] clean namespace(%s)", name) + if _, err := ns.db.Exec(str, name); err != nil { + log.Infof("[Store][database] clean namespace(%s) err: %s", name, err.Error()) + return err + } + + return nil +} + +// rlock namespace +func rlockNamespace(queryRow func(query string, args ...interface{}) *sql.Row, namespace string) ( + string, error) { + str := "select name from namespace where name = ? and flag != 1 lock in share mode" + + var name string + err := queryRow(str, namespace).Scan(&name) + switch { + case err == sql.ErrNoRows: + return "", nil + case err != nil: + return "", err + default: + return name, nil + } +} + +// 生成namespace的查询语句 +func genNamespaceSelectSQL() string { + str := `select name, IFNULL(comment, ""), token, owner, flag, UNIX_TIMESTAMP(ctime), UNIX_TIMESTAMP(mtime) + from namespace ` + return str +} + +/** + * @brief 取出rows的数据 + */ +func namespaceFetchRows(rows *sql.Rows) ([]*model.Namespace, error) { + if rows == nil { + return nil, nil + } + defer rows.Close() + + var out []*model.Namespace + var ctime, mtime int64 + var flag int + + for rows.Next() { + space := &model.Namespace{} + err := rows.Scan( + &space.Name, + &space.Comment, + &space.Token, + &space.Owner, + &flag, + &ctime, + &mtime) + if err != nil { + log.Errorf("[Store][database] fetch namespace rows scan err: %s", err.Error()) + return nil, err + } + + space.CreateTime = time.Unix(ctime, 0) + space.ModifyTime = time.Unix(mtime, 0) + space.Valid = true + if flag == 1 { + space.Valid = false + } + + out = append(out, space) + } + if err := rows.Err(); err != nil { + log.Errorf("[Store][database] fetch namespace rows next err: %s", err.Error()) + return nil, err + } + + return out, nil +} diff --git a/store/defaultStore/platform.go b/store/defaultStore/platform.go new file mode 100644 index 000000000..17dac6e28 --- /dev/null +++ b/store/defaultStore/platform.go @@ -0,0 +1,231 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package defaultStore + +import ( + "database/sql" + "errors" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/store" + "time" +) + +/** + * @brief platformStore的实现 + */ +type platformStore struct { + master *BaseDB +} + +/** + * @brief 创建平台 + */ +func (p *platformStore) CreatePlatform(platform *model.Platform) error { + if platform.ID == "" { + return errors.New("create platform missing id") + } + + if err := p.cleanPlatform(platform.ID); err != nil { + return store.Error(err) + } + + str := `insert into platform + (id, name, domain, qps, token, owner, department, comment, flag, ctime, mtime) + values(?,?,?,?,?,?,?,?,?,sysdate(),sysdate())` + if _, err := p.master.Exec(str, platform.ID, platform.Name, platform.Domain, platform.QPS, platform.Token, + platform.Owner, platform.Department, platform.Comment, 0); err != nil { + log.Errorf("[Store][platform] create platform(%s) err: %s", platform.ID, err.Error()) + return store.Error(err) + } + + return nil +} + +/** + * @brief 删除平台信息 + */ +func (p *platformStore) DeletePlatform(id string) error { + if id == "" { + return errors.New("delete platform missing id") + } + + str := `update platform set flag = 1, mtime = sysdate() where id = ?` + if _, err := p.master.Exec(str, id); err != nil { + log.Errorf("[Store][platform] delete platform(%s) err: %s", id, err.Error()) + return store.Error(err) + } + + return nil +} + +/** + * @brief 修改平台信息 + */ +func (p *platformStore) UpdatePlatform(platform *model.Platform) error { + str := `update platform set name = ?, domain = ?, qps = ?, token = ?, owner = ?, department = ?, comment = ?, + mtime = sysdate() where id = ?` + if _, err := p.master.Exec(str, platform.Name, platform.Domain, platform.QPS, platform.Token, platform.Owner, + platform.Department, platform.Comment, platform.ID); err != nil { + log.Errorf("[Store][platform] update platform(%+v) err: %s", platform, err.Error()) + return store.Error(err) + } + return nil +} + +/** + * @brief 查询平台信息 + */ +func (p *platformStore) GetPlatformById(id string) (*model.Platform, error) { + if id == "" { + return nil, errors.New("get platform by id missing id") + } + + str := genSelectPlatformSQL() + " where id = ? and flag = 0" + + rows, err := p.master.Query(str, id) + if err != nil { + log.Errorf("[Store][platform] get platform by id(%s) err: %s", id, err.Error()) + return nil, err + } + + out, err := fetchPlatformRows(rows) + if err != nil { + return nil, err + } + if len(out) == 0 { + return nil, nil + } + return out[0], nil +} + +/** + * @brief 根据过滤条件查询平台信息及总数 + */ +func (p *platformStore) GetPlatforms(filter map[string]string, offset uint32, limit uint32) ( + uint32, []*model.Platform, error) { + out, err := p.getPlatforms(filter, offset, limit) + if err != nil { + return 0, nil, err + } + num, err := p.getPlatformsCount(filter) + if err != nil { + return 0, nil, err + } + return num, out, nil +} + +/** + * @brief 根据过滤条件查询平台信息 + */ +func (p *platformStore) getPlatforms(filter map[string]string, offset uint32, limit uint32) ( + []*model.Platform, error) { + // 不查询任何内容,直接返回空数组 + if limit == 0 { + return make([]*model.Platform, 0), nil + } + str := genSelectPlatformSQL() + " where flag = 0" + filterStr, args := genFilterSQL(filter) + if filterStr != "" { + str += " and " + filterStr + } + + order := &Order{"mtime", "desc"} + page := &Page{offset, limit} + opStr, opArgs := genOrderAndPage(order, page) + + str += opStr + args = append(args, opArgs...) + + rows, err := p.master.Query(str, args...) + if err != nil { + log.Errorf("[Store][platform] get platforms by filter query(%s) err: %s", str, err.Error()) + return nil, err + } + + out, err := fetchPlatformRows(rows) + if err != nil { + return nil, err + } + + return out, nil +} + +/** + * @brief 根据过滤条件获取平台总数 + */ +func (p *platformStore) getPlatformsCount(filter map[string]string) (uint32, error) { + str := `select count(*) from platform where flag = 0 ` + filterStr, args := genFilterSQL(filter) + if filterStr != "" { + str += " and " + filterStr + } + return queryEntryCount(p.master, str, args) +} + +/** + * @brief 读取平台信息数据 + */ +func fetchPlatformRows(rows *sql.Rows) ([]*model.Platform, error) { + defer rows.Close() + var out []*model.Platform + for rows.Next() { + var platform model.Platform + var flag int + var ctime, mtime int64 + err := rows.Scan(&platform.ID, &platform.Name, &platform.Domain, &platform.QPS, &platform.Token, + &platform.Owner, &platform.Department, &platform.Comment, &flag, &ctime, &mtime) + if err != nil { + log.Errorf("[Store][platform] fetch platform scan err: %s", err.Error()) + return nil, err + } + platform.CreateTime = time.Unix(ctime, 0) + platform.ModifyTime = time.Unix(mtime, 0) + platform.Valid = true + if flag == 1 { + platform.Valid = false + } + out = append(out, &platform) + } + if err := rows.Err(); err != nil { + log.Errorf("[Store][platform] fetch platform next err: %s", err.Error()) + return nil, err + } + return out, nil +} + +/** + * @brief 彻底删除平台信息 + */ +func (p *platformStore) cleanPlatform(id string) error { + str := `delete from platform where id = ? and flag = 1` + if _, err := p.master.Exec(str, id); err != nil { + log.Errorf("[Store][platform] clean platform (%s) err: %s", id, err.Error()) + return err + } + return nil +} + +/** + * @brief 查询平台信息sql + */ +func genSelectPlatformSQL() string { + str := `select id, name, domain, qps, token, owner, IFNULL(department, ""), IFNULL(comment, ""), flag, + unix_timestamp(ctime), unix_timestamp(mtime) from platform ` + return str +} diff --git a/store/defaultStore/polaris_server.sql b/store/defaultStore/polaris_server.sql new file mode 100644 index 000000000..16864bb43 --- /dev/null +++ b/store/defaultStore/polaris_server.sql @@ -0,0 +1,584 @@ +-- phpMyAdmin SQL Dump +-- version 4.6.4 +-- https://www.phpmyadmin.net/ +-- +-- Host: 127.0.0.1 +-- Generation Time: 2019-09-30 03:19:00 +-- 服务器版本: 5.7.14 +-- PHP Version: 5.6.25 + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +SET time_zone = "+00:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; + +-- +-- Database: `polaris_server` +-- +CREATE DATABASE IF NOT EXISTS `polaris_server` DEFAULT CHARACTER SET utf8 COLLATE utf8_bin; +USE `polaris_server`; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `business` +-- + +CREATE TABLE `business` ( + `id` varchar(32) COLLATE utf8_bin NOT NULL, + `name` varchar(64) COLLATE utf8_bin NOT NULL, + `token` varchar(64) COLLATE utf8_bin NOT NULL, + `owner` varchar(1024) COLLATE utf8_bin NOT NULL, + `flag` tinyint(4) NOT NULL DEFAULT '0', + `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `mtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `instance` +-- + +CREATE TABLE `instance` ( + `id` varchar(40) COLLATE utf8_bin NOT NULL, + `service_id` varchar(32) COLLATE utf8_bin NOT NULL, + `vpc_id` varchar(64) COLLATE utf8_bin DEFAULT NULL, + `host` varchar(128) COLLATE utf8_bin NOT NULL, + `port` int(11) NOT NULL, + `protocol` varchar(32) COLLATE utf8_bin DEFAULT NULL, + `version` varchar(32) COLLATE utf8_bin DEFAULT NULL, + `health_status` tinyint(4) NOT NULL DEFAULT '1', + `isolate` tinyint(4) NOT NULL DEFAULT '0', + `weight` smallint(6) NOT NULL DEFAULT '100', + `enable_health_check` tinyint(4) NOT NULL DEFAULT '0', + `logic_set` varchar(128) COLLATE utf8_bin DEFAULT NULL, + `cmdb_region` varchar(128) COLLATE utf8_bin DEFAULT NULL, + `cmdb_zone` varchar(128) COLLATE utf8_bin DEFAULT NULL, + `cmdb_idc` varchar(128) COLLATE utf8_bin DEFAULT NULL, + `priority` tinyint(4) NOT NULL DEFAULT '0', + `revision` varchar(32) COLLATE utf8_bin NOT NULL, + `flag` tinyint(4) NOT NULL DEFAULT '0', + `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `mtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `service_id` (`service_id`), + KEY `mtime` (`mtime`), + KEY `host` (`host`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `health_check` +-- + +CREATE TABLE `health_check` ( + `id` varchar(40) COLLATE utf8_bin NOT NULL, + `type` tinyint(4) NOT NULL DEFAULT '0', + `ttl` int(11) NOT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `health_check_ibfk_1` FOREIGN KEY (`id`) REFERENCES `instance` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `instance_metadata` +-- + +CREATE TABLE `instance_metadata` ( + `id` varchar(40) COLLATE utf8_bin NOT NULL, + `mkey` varchar(128) COLLATE utf8_bin NOT NULL, + `mvalue` varchar(4096) COLLATE utf8_bin NOT NULL, + `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `mtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`,`mkey`), + KEY `mkey` (`mkey`), + CONSTRAINT `instance_metadata_ibfk_1` FOREIGN KEY (`id`) REFERENCES `instance` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `namespace` +-- + +CREATE TABLE `namespace` ( + `name` varchar(64) COLLATE utf8_bin NOT NULL, + `comment` varchar(1024) COLLATE utf8_bin DEFAULT NULL, + `token` varchar(64) COLLATE utf8_bin NOT NULL, + `owner` varchar(1024) COLLATE utf8_bin NOT NULL, + `flag` tinyint(4) NOT NULL DEFAULT '0', + `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `mtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +-- +-- 转存表中的数据 `namespace` +-- + +INSERT INTO `namespace` (`name`, `comment`, `token`, `owner`, `flag`, `ctime`, `mtime`) VALUES +('Polaris', 'Polaris-server', '2d1bfe5d12e04d54b8ee69e62494c7fd', 'polaris', 0, '2019-09-06 07:55:07', '2019-09-06 07:55:07'), +('default', 'Default Environment', 'e2e473081d3d4306b52264e49f7ce227', 'polaris', 0, '2021-07-27 19:37:37', '2021-07-27 19:37:37'); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `routing_config` +-- + +CREATE TABLE `routing_config` ( + `id` varchar(32) COLLATE utf8_bin NOT NULL, + `in_bounds` text COLLATE utf8_bin, + `out_bounds` text COLLATE utf8_bin, + `revision` varchar(40) COLLATE utf8_bin NOT NULL, + `flag` tinyint(4) NOT NULL DEFAULT '0', + `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `mtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `mtime` (`mtime`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `ratelimit_config` +-- + +CREATE TABLE `ratelimit_config` ( + `id` varchar(32) COLLATE utf8_bin NOT NULL, + `service_id` varchar(32) COLLATE utf8_bin NOT NULL, + `cluster_id` varchar(32) COLLATE utf8_bin NOT NULL, + `labels` text COLLATE utf8_bin NOT NULL, + `priority` smallint(6) NOT NULL DEFAULT '0', + `rule` text COLLATE utf8_bin NOT NULL, + `revision` varchar(32) COLLATE utf8_bin NOT NULL, + `flag` tinyint(4) NOT NULL DEFAULT '0', + `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `mtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `mtime` (`mtime`), + KEY `service_id` (`service_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `ratelimit_revision` +-- + +CREATE TABLE `ratelimit_revision` ( + `service_id` varchar(32) COLLATE utf8_bin NOT NULL, + `last_revision` varchar(40) COLLATE utf8_bin NOT NULL, + `mtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`service_id`), + KEY `service_id` (`service_id`), + KEY `mtime` (`mtime`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `service` +-- + +CREATE TABLE `service` ( + `id` varchar(32) COLLATE utf8_bin NOT NULL, + `name` varchar(128) COLLATE utf8_bin NOT NULL, + `namespace` varchar(64) COLLATE utf8_bin NOT NULL, + `ports` varchar(8192) COLLATE utf8_bin DEFAULT NULL, + `business` varchar(64) COLLATE utf8_bin DEFAULT NULL, + `department` varchar(1024) COLLATE utf8_bin DEFAULT NULL, + `cmdb_mod1` varchar(1024) COLLATE utf8_bin DEFAULT NULL, + `cmdb_mod2` varchar(1024) COLLATE utf8_bin DEFAULT NULL, + `cmdb_mod3` varchar(1024) COLLATE utf8_bin DEFAULT NULL, + `comment` varchar(1024) COLLATE utf8_bin DEFAULT NULL, + `token` varchar(2048) COLLATE utf8_bin NOT NULL, + `revision` varchar(32) COLLATE utf8_bin NOT NULL, + `owner` varchar(1024) COLLATE utf8_bin NOT NULL, + `flag` tinyint(4) NOT NULL DEFAULT '0', + `reference` varchar(32) COLLATE utf8_bin DEFAULT NULL, + `refer_filter` varchar(1024) COLLATE utf8_bin DEFAULT NULL, + `platform_id` varchar(32) COLLATE utf8_bin DEFAULT '', + `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `mtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`,`namespace`), + KEY `namespace` (`namespace`), + KEY `mtime` (`mtime`), + KEY `reference` (`reference`), + KEY `platform_id` (`platform_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `service_metadata` +-- + +CREATE TABLE `service_metadata` ( + `id` varchar(32) COLLATE utf8_bin NOT NULL, + `mkey` varchar(128) COLLATE utf8_bin NOT NULL, + `mvalue` varchar(4096) COLLATE utf8_bin NOT NULL, + `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `mtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`,`mkey`), + KEY `mkey` (`mkey`), + CONSTRAINT `service_metadata_ibfk_1` FOREIGN KEY (`id`) REFERENCES `service` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `owner_service_map` +-- + +CREATE TABLE `owner_service_map` ( + `id` varchar(32) COLLATE utf8_bin NOT NULL, + `owner` varchar(32) COLLATE utf8_bin NOT NULL, + `service` varchar(128) COLLATE utf8_bin NOT NULL, + `namespace` varchar(64) COLLATE utf8_bin NOT NULL, + PRIMARY KEY (`id`), + KEY `owner` (`owner`), + KEY `name` (`service`,`namespace`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `circuitbreaker_rule` +-- + +CREATE TABLE `circuitbreaker_rule` ( + `id` varchar(97) COLLATE utf8_bin NOT NULL, + `version` varchar(32) COLLATE utf8_bin NOT NULL DEFAULT 'master', + `name` varchar(32) COLLATE utf8_bin NOT NULL, + `namespace` varchar(64) COLLATE utf8_bin NOT NULL, + `business` varchar(64) COLLATE utf8_bin DEFAULT NULL, + `department` varchar(1024) COLLATE utf8_bin DEFAULT NULL, + `comment` varchar(1024) COLLATE utf8_bin DEFAULT NULL, + `inbounds` text COLLATE utf8_bin NOT NULL, + `outbounds` text COLLATE utf8_bin NOT NULL, + `token` varchar(32) COLLATE utf8_bin NOT NULL, + `owner` varchar(1024) COLLATE utf8_bin NOT NULL, + `revision` varchar(32) COLLATE utf8_bin NOT NULL, + `flag` tinyint(4) NOT NULL DEFAULT '0', + `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `mtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`,`version`), + UNIQUE KEY `name` (`name`,`namespace`,`version`), + KEY `mtime` (`mtime`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `circuitbreaker_rule_relation` +-- + +CREATE TABLE `circuitbreaker_rule_relation` ( + `service_id` varchar(32) COLLATE utf8_bin NOT NULL, + `rule_id` varchar(97) COLLATE utf8_bin NOT NULL, + `rule_version` varchar(32) COLLATE utf8_bin NOT NULL, + `flag` tinyint(4) NOT NULL DEFAULT '0', + `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `mtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`service_id`), + KEY `mtime` (`mtime`), + KEY `rule_id` (`rule_id`), + CONSTRAINT `circuitbreaker_rule_relation_ibfk_1` FOREIGN KEY (`service_id`) REFERENCES `service` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `platform` +-- + +CREATE TABLE `platform` ( + `id` varchar(32) COLLATE utf8_bin NOT NULL, + `name` varchar(128) COLLATE utf8_bin NOT NULL, + `domain` varchar(1024) COLLATE utf8_bin NOT NULL, + `qps` smallint(6) NOT NULL, + `token` varchar(32) COLLATE utf8_bin NOT NULL, + `owner` varchar(1024) COLLATE utf8_bin NOT NULL, + `department` varchar(1024) COLLATE utf8_bin DEFAULT NULL, + `comment` varchar(1024) COLLATE utf8_bin DEFAULT NULL, + `flag` tinyint(4) NOT NULL DEFAULT '0', + `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `mtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `mtime` (`mtime`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `t_ip_config` +-- + +CREATE TABLE `t_ip_config` ( + `Fip` int(10) unsigned NOT NULL, + `FareaId` int(10) unsigned NOT NULL, + `FcityId` int(10) unsigned NOT NULL, + `FidcId` int(10) unsigned NOT NULL, + `Fflag` tinyint(4) DEFAULT '0', + `Fstamp` datetime NOT NULL, + `Fflow` int(10) unsigned NOT NULL, + PRIMARY KEY (`Fip`), + KEY `idx_Fflow` (`Fflow`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `t_policy` +-- + +CREATE TABLE `t_policy` ( + `FmodId` int(10) unsigned NOT NULL, + `Fdiv` int(10) unsigned NOT NULL, + `Fmod` int(10) unsigned NOT NULL, + `Fflag` tinyint(4) DEFAULT '0', + `Fstamp` datetime NOT NULL, + `Fflow` int(10) unsigned NOT NULL, + PRIMARY KEY (`FmodId`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `t_route` +-- + +CREATE TABLE `t_route` ( + `Fip` int(10) unsigned NOT NULL, + `FmodId` int(10) unsigned NOT NULL, + `FcmdId` int(10) unsigned NOT NULL, + `FsetId` varchar(32) NOT NULL, + `Fflag` tinyint(4) DEFAULT '0', + `Fstamp` datetime NOT NULL, + `Fflow` int(10) unsigned NOT NULL, + PRIMARY KEY (`Fip`,`FmodId`,`FcmdId`), + KEY `Fflow` (`Fflow`), + KEY `idx1` (`FmodId`,`FcmdId`,`FsetId`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `t_section` +-- + +CREATE TABLE `t_section` ( + `FmodId` int(10) unsigned NOT NULL, + `Ffrom` int(10) unsigned NOT NULL, + `Fto` int(10) unsigned NOT NULL, + `Fxid` int(10) unsigned NOT NULL, + `Fflag` tinyint(4) DEFAULT '0', + `Fstamp` datetime NOT NULL, + `Fflow` int(10) unsigned NOT NULL, + PRIMARY KEY (`FmodId`,`Ffrom`,`Fto`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `start_lock` +-- + +CREATE TABLE `start_lock` ( + `lock_id` int(11) NOT NULL COMMENT '锁序号', + `lock_key` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '锁的名字', + `server` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '持有启动锁的Server', + `mtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`lock_id`,`lock_key`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +-- +-- 转存表中的数据 `start_lock` +-- + +INSERT INTO `start_lock` (`lock_id`, `lock_key`, `server`, `mtime`) VALUES +(1, 'sz', 'aaa', '2019-12-05 08:35:49'); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `cl5_module` +-- +CREATE TABLE `cl5_module` ( + `module_id` int(11) NOT NULL COMMENT '模块ID', + `interface_id` int(11) NOT NULL COMMENT '接口ID', + `range_num` int(11) NOT NULL, + `mtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`module_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='用以生成sid'; + +-- +-- 转存表中的数据 `cl5_module` +-- + +insert into cl5_module(module_id, interface_id, range_num) values(3000001, 1, 0); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `mesh` +-- +CREATE TABLE `mesh` ( + `id` varchar(32) COLLATE utf8_bin NOT NULL, /*网格ID*/ + `name` varchar(128) COLLATE utf8_bin NOT NULL, /*网格名*/ + `department` varchar(1024) COLLATE utf8_bin DEFAULT NULL, /*网格所属部门*/ + `business` varchar(128) COLLATE utf8_bin NOT NULL, /*网格所属业务*/ + `managed` tinyint(4) NOT NULL, /*是否托管*/ + `istio_version` varchar(64) COLLATE utf8_bin, /*istio版本*/ + `data_cluster` varchar(1024) COLLATE utf8_bin, /*数据面集群*/ + `revision` varchar(32) COLLATE utf8_bin NOT NULL, /*规则版本号*/ + `comment` varchar(1024) COLLATE utf8_bin DEFAULT NULL, /*规则描述*/ + `token` varchar(32) COLLATE utf8_bin NOT NULL, /*规则鉴权token*/ + `owner` varchar(1024) COLLATE utf8_bin NOT NULL, /*规则的拥有者*/ + `flag` tinyint(4) NOT NULL DEFAULT '0', /*规则是否有效,0为有效,1为无效,己被删除了*/ + `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `mtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `name` (`name`), + KEY `mtime` (`mtime`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `mesh_service` +-- +CREATE TABLE `mesh_service` ( + `id` varchar(32) COLLATE utf8_bin NOT NULL, /*网格规则ID*/ + `mesh_id` varchar(32) COLLATE utf8_bin NOT NULL, /*网格名*/ + `service_id` varchar(32) COLLATE utf8_bin NOT NULL, /*服务ID*/ + `namespace` varchar(64) COLLATE utf8_bin NOT NULL, /*服务命名空间*/ + `service` varchar(128) COLLATE utf8_bin NOT NULL, /*服务名*/ + `mesh_namespace` varchar(64) COLLATE utf8_bin NOT NULL, /*映射到网格的命名空间*/ + `mesh_service` varchar(128) COLLATE utf8_bin NOT NULL, /*映射到网格的服务名*/ + `location` varchar(16) COLLATE utf8_bin NOT NULL, /*服务处于网格哪个位置*/ + `export_to` varchar(1024) COLLATE utf8_bin NOT NULL, /*服务可以被哪些命名空间所见*/ + `revision` varchar(32) COLLATE utf8_bin NOT NULL, /*规则版本号*/ + `flag` tinyint(4) NOT NULL DEFAULT '0', /*规则是否有效,0为有效,1为无效,己被删除了*/ + `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `mtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `relation` (`mesh_id`,`mesh_namespace`,`mesh_service`), + KEY `namespace`(`namespace`), + KEY `service`(`service`), + KEY `location`(`location`), + KEY `export_to`(`export_to`), + KEY `mtime` (`mtime`), + KEY `flag`( `flag`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `mesh_service_revision` +-- +CREATE TABLE `mesh_service_revision` ( + `mesh_id` varchar(32) COLLATE utf8_bin NOT NULL, /*网格名*/ + `revision` varchar(32) COLLATE utf8_bin NOT NULL, /*规则版本号*/ + `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `mtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`mesh_id`), + KEY `mtime` (`mtime`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `mesh_resource` +-- +CREATE TABLE `mesh_resource` ( + `id` varchar(32) COLLATE utf8_bin NOT NULL, /*网格规则ID*/ + `mesh_id` varchar(32) COLLATE utf8_bin NOT NULL, /*网格名*/ + `name` varchar(64) COLLATE utf8_bin NOT NULL, /*规则名*/ + `mesh_namespace` varchar(64) COLLATE utf8_bin NOT NULL, /*规则所处的网格命名空间*/ + `type_url` varchar(96) COLLATE utf8_bin NOT NULL, /*规则类型,如virtualService*/ + `revision` varchar(32) COLLATE utf8_bin NOT NULL, /*规则版本号*/ + `body` text, /*规则内容,json格式字符串*/ + `flag` tinyint(4) NOT NULL DEFAULT '0', /*规则是否有效,0为有效,1为无效,己被删除了*/ + `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `mtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `name`(`mesh_id`, `name`, `mesh_namespace`, `type_url`), + KEY `mtime` (`mtime`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +-- +-- 表的结构 `mesh_revision` +-- +CREATE TABLE `mesh_resource_revision` ( + `mesh_id` varchar(32) COLLATE utf8_bin NOT NULL, /*规则所属网格ID*/ + `type_url` varchar(96) COLLATE utf8_bin NOT NULL, /*规则类型,如virtualService*/ + `revision` varchar(32) COLLATE utf8_bin NOT NULL, /*规则集合的版本号,同一个网格下面所有规则集合的总体版本号*/ + `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `mtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`mesh_id`, `type_url`), + KEY `mtime` (`mtime`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +-- -------------------------------------------------------- +-- +-- flux规则配置表的结构 `ratelimit_flux_rule_config` +-- +CREATE TABLE `ratelimit_flux_rule_config` ( + `id` varchar(32) COLLATE utf8_bin NOT NULL, + `revision` varchar(32) COLLATE utf8_bin NOT NULL, + `callee_service_id` varchar(32) COLLATE utf8_bin NOT NULL, + `callee_service_env` varchar(64) COLLATE utf8_bin NOT NULL, + `callee_service_name` varchar(250) COLLATE utf8_bin NOT NULL DEFAULT '', + `caller_service_business` varchar(250) COLLATE utf8_bin NOT NULL DEFAULT '', + `name` varchar(100) COLLATE utf8_bin NOT NULL DEFAULT '', + `description` varchar(500) COLLATE utf8_bin NOT NULL DEFAULT '', + `type` tinyint(4) NOT NULL DEFAULT '0', + `set_key` varchar(250) COLLATE utf8_bin NOT NULL DEFAULT '', + `set_alert_qps` varchar(10) NOT NULL DEFAULT '', + `set_warning_qps` varchar(10) NOT NULL DEFAULT '', + `set_remark` varchar(500) COLLATE utf8_bin NOT NULL DEFAULT '', + `default_key` varchar(250) COLLATE utf8_bin NOT NULL DEFAULT '', + `default_alert_qps` varchar(10) NOT NULL DEFAULT '', + `default_warning_qps` varchar(10) NOT NULL DEFAULT '', + `default_remark` varchar(500) COLLATE utf8_bin NOT NULL DEFAULT '', + `creator` varchar(32) COLLATE utf8_bin NOT NULL DEFAULT '', + `updater` varchar(32) COLLATE utf8_bin NOT NULL DEFAULT '', + `status` tinyint(4) NOT NULL DEFAULT '0', + `flag` tinyint(4) NOT NULL DEFAULT '0', + `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `mtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `flux_server_id` varchar(32) COLLATE utf8_bin NOT NULL DEFAULT '', + `monitor_server_id` varchar(32) COLLATE utf8_bin NOT NULL DEFAULT '', + PRIMARY KEY (`id`), + UNIQUE KEY `unique_service` (`callee_service_id`,`caller_service_business`,`set_key`), + KEY `mtime` (`mtime`), + KEY `name` (`name`), + KEY `creator` (`creator`), + KEY `callee_service` (`callee_service_env`,`callee_service_name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +-- -------------------------------------------------------- +-- +-- flux规则版本关联表的结构 `ratelimit_flux_rule_revision` +-- +CREATE TABLE `ratelimit_flux_rule_revision` ( + `service_id` varchar(32) COLLATE utf8_bin NOT NULL, + `last_revision` varchar(40) COLLATE utf8_bin NOT NULL, + `mtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`service_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; +-- -------------------------------------------------------- +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/store/defaultStore/ratelimit_config.go b/store/defaultStore/ratelimit_config.go new file mode 100644 index 000000000..87bba2c9c --- /dev/null +++ b/store/defaultStore/ratelimit_config.go @@ -0,0 +1,436 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package defaultStore + +import ( + "database/sql" + "errors" + "fmt" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/store" + "time" +) + +/** + * @brief RateLimitStore的实现 + */ +type rateLimitStore struct { + db *BaseDB +} + +/* + * @brief 新建限流规则 + */ +func (rls *rateLimitStore) CreateRateLimit(limit *model.RateLimit) error { + if limit.ID == "" || limit.ServiceID == "" || limit.Revision == "" { + return errors.New("[Store][database] create rate limit missing some params") + } + err := RetryTransaction("createRateLimit", func() error { + return rls.createRateLimit(limit) + }) + + return store.Error(err) +} + +// createRateLimit +func (rls *rateLimitStore) createRateLimit(limit *model.RateLimit) error { + tx, err := rls.db.Begin() + if err != nil { + log.Errorf("[Store][database] create rate limit(%+v) begin tx err: %s", limit, err.Error()) + return err + } + + defer func() { + _ = tx.Rollback() + }() + + // 新建限流规则 + str := `insert into ratelimit_config(id, service_id, cluster_id, labels, priority, rule, revision, ctime, mtime) + values(?,?,?,?,?,?,?,sysdate(),sysdate())` + if _, err := tx.Exec(str, limit.ID, limit.ServiceID, limit.ClusterID, limit.Labels, limit.Priority, limit.Rule, + limit.Revision); err != nil { + log.Errorf("[Store][database] create rate limit(%+v) err: %s", limit, err.Error()) + return err + } + + // 更新last_revision + str = `insert into ratelimit_revision(service_id,last_revision,mtime) values(?,?,sysdate()) on duplicate key update + last_revision = ?` + if _, err := tx.Exec(str, limit.ServiceID, limit.Revision, limit.Revision); err != nil { + log.Errorf("[Store][database][Create] update rate limit revision with service id(%s) err: %s", + limit.ServiceID, err.Error()) + return err + } + + if err := tx.Commit(); err != nil { + log.Errorf("[Store][database] create rate limit(%+v) commit tx err: %s", limit, err.Error()) + return err + } + + return nil +} + +/* + * @brief 更新限流规则 + */ +func (rls *rateLimitStore) UpdateRateLimit(limit *model.RateLimit) error { + if limit.ID == "" || limit.ServiceID == "" || limit.Revision == "" { + return errors.New("[Store][database] update rate limit missing some params") + } + + err := RetryTransaction("updateRateLimit", func() error { + return rls.updateRateLimit(limit) + }) + + return store.Error(err) +} + +// updateRateLimit +func (rls *rateLimitStore) updateRateLimit(limit *model.RateLimit) error { + tx, err := rls.db.Begin() + if err != nil { + log.Errorf("[Store][database] update rate limit(%+v) begin tx err: %s", limit, err.Error()) + return err + } + + defer func() { + _ = tx.Rollback() + }() + + str := `update ratelimit_config set labels = ?, priority = ?, rule = ?, revision = ?, mtime = sysdate() where id = ?` + if _, err := tx.Exec(str, limit.Labels, limit.Priority, limit.Rule, limit.Revision, limit.ID); err != nil { + log.Errorf("[Store][database] update rate limit(%+v) err: %s", limit, err) + return err + } + + if err := rls.updateLastRevision(tx, limit.ServiceID, limit.Revision); err != nil { + log.Errorf("[Store][database][Update] update rate limit revision with service id(%s) err: %s", + limit.ServiceID, err.Error()) + return err + } + + if err := tx.Commit(); err != nil { + log.Errorf("[Store][database] update rate limit(%+v) commit tx err: %s", limit, err.Error()) + return err + } + return nil +} + +/** + * @brief 删除限流规则 + */ +func (rls *rateLimitStore) DeleteRateLimit(limit *model.RateLimit) error { + if limit.ID == "" || limit.ServiceID == "" || limit.Revision == "" { + return errors.New("[Store][database] delete rate limit missing some params") + } + + err := RetryTransaction("deleteRateLimit", func() error { + return rls.deleteRateLimit(limit) + }) + + return store.Error(err) +} + +// deleteRateLimit +func (rls *rateLimitStore) deleteRateLimit(limit *model.RateLimit) error { + tx, err := rls.db.Begin() + if err != nil { + log.Errorf("[Store][database] delete rate limit(%+v) begin tx err: %s", limit, err.Error()) + return err + } + + defer func() { + _ = tx.Rollback() + }() + + str := `update ratelimit_config set flag = 1, mtime = sysdate() where id = ?` + if _, err := tx.Exec(str, limit.ID); err != nil { + log.Errorf("[Store][database] delete rate limit(%+v) err: %s", limit, err) + return err + } + + if err := rls.updateLastRevision(tx, limit.ServiceID, limit.Revision); err != nil { + log.Errorf("[Store][database][Delete] update rate limit revision with service id(%s) err: %s", + limit.ServiceID, err.Error()) + return err + } + + if err := tx.Commit(); err != nil { + log.Errorf("[Store][database] delete rate limit(%+v) commit tx err: %s", limit, err.Error()) + return err + } + return nil +} + +/** + * @brief 根据限流规则ID获取限流规则 + */ +func (rls *rateLimitStore) GetRateLimitWithID(id string) (*model.RateLimit, error) { + if id == "" { + log.Errorf("[Store][database] get rate limit missing some params") + return nil, errors.New("get rate limit missing some params") + } + + str := `select id, service_id, cluster_id, labels, priority, rule, revision, flag, + unix_timestamp(ctime), unix_timestamp(mtime) + from ratelimit_config + where id = ? and flag = 0` + rows, err := rls.db.Query(str, id) + if err != nil { + log.Errorf("[Store][database] query rate limit with id(%s) err: %s", id, err.Error()) + return nil, err + } + out, err := fetchRateLimitRows(rows) + if err != nil { + return nil, err + } + if len(out) == 0 { + return nil, nil + } + return out[0], nil +} + +/** + * @brief 根据过滤条件获取限流规则及数目 + */ +func (rls *rateLimitStore) GetExtendRateLimits(filter map[string]string, offset uint32, limit uint32) ( + uint32, []*model.ExtendRateLimit, error) { + out, err := rls.getExpandRateLimits(filter, offset, limit) + if err != nil { + return 0, nil, err + } + num, err := rls.getExpandRateLimitsCount(filter) + if err != nil { + return 0, nil, err + } + return num, out, nil +} + +/** + * @brief 根据修改时间拉取增量限流规则及最新版本号 + */ +func (rls *rateLimitStore) GetRateLimitsForCache(mtime time.Time, + firstUpdate bool) ([]*model.RateLimit, []*model.RateLimitRevision, error) { + str := `select id, ratelimit_config.service_id, cluster_id, labels, priority, rule, revision, flag, + unix_timestamp(ratelimit_config.ctime), unix_timestamp(ratelimit_config.mtime), last_revision + from ratelimit_config, ratelimit_revision + where ratelimit_config.mtime > ? and ratelimit_config.service_id = ratelimit_revision.service_id` + if firstUpdate { + str += " and flag != 1" // nolint + } + rows, err := rls.db.Query(str, time2String(mtime)) + if err != nil { + log.Errorf("[Store][database] query rate limits with mtime err: %s", err.Error()) + return nil, nil, err + } + rateLimits, revisions, err := fetchRateLimitCacheRows(rows) + if err != nil { + return nil, nil, err + } + return rateLimits, revisions, nil +} + +/** + * @brief 根据过滤条件获取限流规则 + */ +func (rls *rateLimitStore) getExpandRateLimits(filter map[string]string, offset uint32, limit uint32) ( + []*model.ExtendRateLimit, error) { + str := `select name, namespace, ratelimit_config.id, service_id, cluster_id, labels, priority, rule, + ratelimit_config.revision, unix_timestamp(ratelimit_config.ctime), unix_timestamp(ratelimit_config.mtime) + from ratelimit_config, service + where service_id = service.id and ratelimit_config.flag = 0` + + queryStr, args := genFilterRateLimitSQL(filter) + args = append(args, offset, limit) + str = str + queryStr + ` order by ratelimit_config.mtime desc limit ?, ?` + + rows, err := rls.db.Query(str, args...) + if err != nil { + log.Errorf("[Store][database] query rate limits err: %s", err.Error()) + return nil, err + } + out, err := fetchExpandRateLimitRows(rows) + if err != nil { + return nil, err + } + return out, nil +} + +/** + * @brief 根据过滤条件获取限流规则数目 + */ +func (rls *rateLimitStore) getExpandRateLimitsCount(filter map[string]string) (uint32, error) { + str := `select count(*) from ratelimit_config, service + where service_id = service.id and ratelimit_config.flag = 0` + + queryStr, args := genFilterRateLimitSQL(filter) + str = str + queryStr + var total uint32 + err := rls.db.QueryRow(str, args...).Scan(&total) + switch { + case err == sql.ErrNoRows: + return 0, nil + case err != nil: + log.Errorf("[Store][database] get expand rate limits count err: %s", err.Error()) + return 0, err + default: + } + return total, nil +} + +/** + *@brief 生成查询语句的过滤语句 + */ +func genFilterRateLimitSQL(query map[string]string) (string, []interface{}) { + str := "" + args := make([]interface{}, 0, len(query)) + for key, value := range query { + if key == "labels" { + str += " and labels like ?" + value = "%" + value + "%" + } else { + str += fmt.Sprintf(" and %s = ?", key) + } + args = append(args, value) + } + return str, args +} + +/** + * @brief 读取限流数据 + */ +func fetchRateLimitRows(rows *sql.Rows) ([]*model.RateLimit, error) { + defer rows.Close() + var out []*model.RateLimit + for rows.Next() { + var rateLimit model.RateLimit + var flag int + var ctime, mtime int64 + err := rows.Scan(&rateLimit.ID, &rateLimit.ServiceID, &rateLimit.ClusterID, &rateLimit.Labels, + &rateLimit.Priority, &rateLimit.Rule, &rateLimit.Revision, &flag, &ctime, &mtime) + if err != nil { + log.Errorf("[Store][database] fetch rate limit scan err: %s", err.Error()) + return nil, err + } + rateLimit.CreateTime = time.Unix(ctime, 0) + rateLimit.ModifyTime = time.Unix(mtime, 0) + rateLimit.Valid = true + if flag == 1 { + rateLimit.Valid = false + } + out = append(out, &rateLimit) + } + if err := rows.Err(); err != nil { + log.Errorf("[Store][database] fetch rate limit next err: %s", err.Error()) + return nil, err + } + return out, nil +} + +/** + * @brief 读取包含服务信息的限流数据 + */ +func fetchExpandRateLimitRows(rows *sql.Rows) ([]*model.ExtendRateLimit, error) { + defer rows.Close() + var out []*model.ExtendRateLimit + for rows.Next() { + var expand model.ExtendRateLimit + expand.RateLimit = &model.RateLimit{} + var ctime, mtime int64 + err := rows.Scan(&expand.ServiceName, &expand.NamespaceName, &expand.RateLimit.ID, + &expand.RateLimit.ServiceID, &expand.RateLimit.ClusterID, &expand.RateLimit.Labels, + &expand.RateLimit.Priority, &expand.RateLimit.Rule, &expand.RateLimit.Revision, &ctime, &mtime) + if err != nil { + log.Errorf("[Store][database] fetch expand rate limit scan err: %s", err.Error()) + return nil, err + } + expand.RateLimit.CreateTime = time.Unix(ctime, 0) + expand.RateLimit.ModifyTime = time.Unix(mtime, 0) + out = append(out, &expand) + } + if err := rows.Err(); err != nil { + log.Errorf("[Store][database] fetch expand rate limit next err: %s", err.Error()) + return nil, err + } + return out, nil +} + +/** + * @brief 读取限流数据以及最新版本号 + */ +func fetchRateLimitCacheRows(rows *sql.Rows) ([]*model.RateLimit, []*model.RateLimitRevision, error) { + defer rows.Close() + + var rateLimits []*model.RateLimit + var revisions []*model.RateLimitRevision + + for rows.Next() { + var rateLimit model.RateLimit + var revision model.RateLimitRevision + var ctime, mtime int64 + var serviceID string + var flag int + err := rows.Scan(&rateLimit.ID, &serviceID, &rateLimit.ClusterID, &rateLimit.Labels, + &rateLimit.Priority, &rateLimit.Rule, &rateLimit.Revision, &flag, &ctime, &mtime, &revision.LastRevision) + if err != nil { + log.Errorf("[Store][database] fetch rate limit cache scan err: %s", err.Error()) + return nil, nil, err + } + rateLimit.CreateTime = time.Unix(ctime, 0) + rateLimit.ModifyTime = time.Unix(mtime, 0) + rateLimit.Valid = true + if flag == 1 { + rateLimit.Valid = false + } + rateLimit.ServiceID = serviceID + revision.ServiceID = serviceID + + rateLimits = append(rateLimits, &rateLimit) + revisions = append(revisions, &revision) + } + + if err := rows.Err(); err != nil { + log.Errorf("[Store][database] fetch rate limit cache next err: %s", err.Error()) + return nil, nil, err + } + return rateLimits, revisions, nil +} + +/* + * @brief 从数据库清除限流规则数据 + */ +func (rls *rateLimitStore) cleanRateLimit(id string) error { + str := `delete from ratelimit_config where id = ? and flag = 1` + if _, err := rls.db.Exec(str, id); err != nil { + log.Errorf("[Store][database] clean rate limit id(%s) err: %s", id, err.Error()) + return err + } + return nil +} + +/** + * @brief 更新last_revision + */ +func (rls *rateLimitStore) updateLastRevision(tx *BaseTx, serviceID string, revision string) error { + str := `update ratelimit_revision set last_revision = ?, mtime = sysdate() where service_id = ?` + if _, err := tx.Exec(str, revision, serviceID); err != nil { + return err + } + return nil +} diff --git a/store/defaultStore/routing_config.go b/store/defaultStore/routing_config.go new file mode 100644 index 000000000..a8fc8f78e --- /dev/null +++ b/store/defaultStore/routing_config.go @@ -0,0 +1,293 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package defaultStore + +import ( + "database/sql" + "fmt" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" + "github.com/polarismesh/polaris-server/store" + "time" +) + +// RoutingConfigStore的实现 +type routingConfigStore struct { + master *BaseDB + slave *BaseDB +} + +// 新建RoutingConfig +func (rs *routingConfigStore) CreateRoutingConfig(conf *model.RoutingConfig) error { + if conf.ID == "" || conf.Revision == "" { + log.Errorf("[Store][database] create routing config missing service id or revision") + return store.NewStatusError(store.EmptyParamsErr, "missing service id or revision") + } + if conf.InBounds == "" || conf.OutBounds == "" { + log.Errorf("[Store][database] create routing config missing params") + return store.NewStatusError(store.EmptyParamsErr, "missing some params") + } + + // 新建之前,先清理老数据 + if err := rs.cleanRoutingConfig(conf.ID); err != nil { + return store.Error(err) + } + + // 服务配置的创建由外层进行服务的保护,这里不需要加锁 + str := `insert into routing_config(id, in_bounds, out_bounds, revision, ctime, mtime) + values(?,?,?,?,sysdate(),sysdate())` + if _, err := rs.master.Exec(str, conf.ID, conf.InBounds, conf.OutBounds, conf.Revision); err != nil { + log.Errorf("[Store][database] create routing(%+v) err: %s", conf, err.Error()) + return store.Error(err) + } + + return nil +} + +// 更新 +func (rs *routingConfigStore) UpdateRoutingConfig(conf *model.RoutingConfig) error { + if conf.ID == "" || conf.Revision == "" { + log.Errorf("[Store][database] update routing config missing service id or revision") + return store.NewStatusError(store.EmptyParamsErr, "missing service id or revision") + } + if conf.InBounds == "" || conf.OutBounds == "" { + log.Errorf("[Store][database] update routing config missing params") + return store.NewStatusError(store.EmptyParamsErr, "missing some params") + } + + str := `update routing_config set in_bounds = ?, out_bounds = ?, revision = ?, mtime = sysdate() where id = ?` + if _, err := rs.master.Exec(str, conf.InBounds, conf.OutBounds, conf.Revision, conf.ID); err != nil { + log.Errorf("[Store][database] update routing config(%+v) exec err: %s", conf, err.Error()) + return store.Error(err) + } + + return nil +} + +// 删除 +func (rs *routingConfigStore) DeleteRoutingConfig(serviceID string) error { + if serviceID == "" { + log.Errorf("[Store][database] delete routing config missing service id") + return store.NewStatusError(store.EmptyParamsErr, "missing service id") + } + + str := `update routing_config set flag = 1, mtime = sysdate() where id = ?` + if _, err := rs.master.Exec(str, serviceID); err != nil { + log.Errorf("[Store][database] delete routing config(%s) err: %s", serviceID, err.Error()) + return store.Error(err) + } + + return nil +} + +// 缓存增量拉取 +func (rs *routingConfigStore) GetRoutingConfigsForCache( + mtime time.Time, firstUpdate bool) ([]*model.RoutingConfig, error) { + str := `select id, in_bounds, out_bounds, revision, + flag, unix_timestamp(ctime), unix_timestamp(mtime) + from routing_config where mtime > ?` + if firstUpdate { + str += " and flag != 1" // nolint + } + rows, err := rs.slave.Query(str, time2String(mtime)) + if err != nil { + log.Errorf("[Store][database] query routing configs with mtime err: %s", err.Error()) + return nil, err + } + out, err := fetchRoutingConfigRows(rows) + if err != nil { + return nil, err + } + + return out, nil +} + +// 根据服务名+namespace获取对应的配置 +func (rs *routingConfigStore) GetRoutingConfigWithService( + name string, namespace string) (*model.RoutingConfig, error) { + // 只查询到flag=0的数据 + str := `select routing_config.id, in_bounds, out_bounds, revision, flag, + unix_timestamp(ctime), unix_timestamp(mtime) + from (select id from service where name = ? and namespace = ?) as service, routing_config + where service.id = routing_config.id and routing_config.flag = 0` + rows, err := rs.master.Query(str, name, namespace) + if err != nil { + log.Errorf("[Store][database] query routing config with service(%s, %s) err: %s", + name, namespace, err.Error()) + return nil, err + } + + out, err := fetchRoutingConfigRows(rows) + if err != nil { + return nil, err + } + + if len(out) == 0 { + return nil, nil + } + + return out[0], nil +} + +// 根据服务ID获取对应的配置 +func (rs *routingConfigStore) GetRoutingConfigWithID(id string) (*model.RoutingConfig, error) { + str := `select routing_config.id, in_bounds, out_bounds, revision, flag, + unix_timestamp(ctime), unix_timestamp(mtime) + from routing_config + where id = ? and flag = 0` + rows, err := rs.master.Query(str, id) + if err != nil { + log.Errorf("[Store][database] query routing with id(%s) err: %s", id, err.Error()) + return nil, err + } + + out, err := fetchRoutingConfigRows(rows) + if err != nil { + return nil, err + } + + if len(out) == 0 { + return nil, nil + } + + return out[0], nil +} + +// 获取路由配置列表 +func (rs *routingConfigStore) GetRoutingConfigs(filter map[string]string, + offset uint32, limit uint32) (uint32, []*model.ExtendRoutingConfig, error) { + + filterStr, args := genFilterRoutingConfigSQL(filter) + countStr := genQueryRoutingConfigCountSQL() + filterStr + var total uint32 + err := rs.master.QueryRow(countStr, args...).Scan(&total) + switch { + case err == sql.ErrNoRows: + return 0, nil, nil + case err != nil: + log.Errorf("[Store][database] get routing config query count err: %s", err.Error()) + return 0, nil, err + default: + } + + str := genQueryRoutingConfigSQL() + filterStr + " order by routing_config.mtime desc limit ?, ?" + args = append(args, offset, limit) + rows, err := rs.master.Query(str, args...) + if err != nil { + log.Errorf("[Store][database] get routing configs query err: %s", err.Error()) + return 0, nil, err + } + defer rows.Close() + + var out []*model.ExtendRoutingConfig + for rows.Next() { + var tmp model.ExtendRoutingConfig + tmp.Config = &model.RoutingConfig{} + var ctime, mtime int64 + err := rows.Scan(&tmp.ServiceName, &tmp.NamespaceName, &tmp.Config.ID, + &tmp.Config.InBounds, &tmp.Config.OutBounds, &ctime, &mtime) + if err != nil { + log.Errorf("[Store][database] query routing configs rows scan err: %s", err.Error()) + return 0, nil, err + } + + tmp.Config.CreateTime = time.Unix(ctime, 0) + tmp.Config.ModifyTime = time.Unix(mtime, 0) + + out = append(out, &tmp) + } + if err := rows.Err(); err != nil { + log.Errorf("[Store][database] query routing configs rows next err: %s", err.Error()) + return 0, nil, err + } + + return total, out, nil +} + +// 从数据库彻底清理路由配置 +func (rs *routingConfigStore) cleanRoutingConfig(serviceID string) error { + str := `delete from routing_config where id = ? and flag = 1` + if _, err := rs.master.Exec(str, serviceID); err != nil { + log.Errorf("[Store][database] clean routing config(%s) err: %s", serviceID, err.Error()) + return err + } + + return nil +} + +// 读取数据库的数据,并且释放rows +func fetchRoutingConfigRows(rows *sql.Rows) ([]*model.RoutingConfig, error) { + defer rows.Close() + var out []*model.RoutingConfig + for rows.Next() { + var entry model.RoutingConfig + var flag int + var ctime, mtime int64 + err := rows.Scan(&entry.ID, &entry.InBounds, &entry.OutBounds, &entry.Revision, + &flag, &ctime, &mtime) + if err != nil { + log.Errorf("[database][store] fetch routing config scan err: %s", err.Error()) + return nil, err + } + + entry.CreateTime = time.Unix(ctime, 0) + entry.ModifyTime = time.Unix(mtime, 0) + entry.Valid = true + if flag == 1 { + entry.Valid = false + } + + out = append(out, &entry) + } + if err := rows.Err(); err != nil { + log.Errorf("[database][store] fetch routing config next err: %s", err.Error()) + return nil, err + } + + return out, nil +} + +// 查询路由配置的语句 +func genQueryRoutingConfigSQL() string { + str := `select name, namespace, routing_config.id, in_bounds, out_bounds, + unix_timestamp(routing_config.ctime), unix_timestamp(routing_config.mtime) + from routing_config, service + where routing_config.id = service.id + and routing_config.flag = 0` + return str +} + +// 获取路由配置指定过滤条件下的总条目数 +func genQueryRoutingConfigCountSQL() string { + str := `select count(*) from routing_config, service + where routing_config.id = service.id + and routing_config.flag = 0` + return str +} + +// 生成过滤语句 +func genFilterRoutingConfigSQL(filters map[string]string) (string, []interface{}) { + str := "" + args := make([]interface{}, 0, len(filters)) + for key, value := range filters { + str += fmt.Sprintf(" and %s = ? ", key) + args = append(args, value) + } + + return str, args +} diff --git a/store/defaultStore/service.go b/store/defaultStore/service.go new file mode 100644 index 000000000..335561645 --- /dev/null +++ b/store/defaultStore/service.go @@ -0,0 +1,1282 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package defaultStore + +import ( + "database/sql" + "errors" + "github.com/polarismesh/polaris-server/naming" + "github.com/polarismesh/polaris-server/store" + "strings" + "time" + + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" +) + +/** + * @brief 实现了ServiceStore + */ +type serviceStore struct { + master *BaseDB + slave *BaseDB +} + +/** + * @brief 增加服务 + */ +func (ss *serviceStore) AddService(s *model.Service) error { + if s.ID == "" || s.Name == "" || s.Namespace == "" || + s.Owner == "" || s.Token == "" { + return store.NewStatusError(store.EmptyParamsErr, "add Service missing some params") + } + + // 先清理无效数据 + if err := ss.cleanService(s.Name, s.Namespace); err != nil { + return err + } + + err := RetryTransaction("addService", func() error { + return ss.addService(s) + }) + return store.Error(err) +} + +// add service +func (ss *serviceStore) addService(s *model.Service) error { + tx, err := ss.master.Begin() + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + + // 锁namespace + namespace, err := rlockNamespace(tx.QueryRow, s.Namespace) + if err != nil { + return err + } + if namespace == "" { + return store.NewStatusError(store.NotFoundNamespace, "not found namespace") + } + + // 填充main表 + if err := addServiceMain(tx, s); err != nil { + log.Errorf("[Store][database] add service table err: %s", err.Error()) + return err + } + + // 填充metadata表 + if err := addServiceMeta(tx, s.ID, s.Meta); err != nil { + log.Errorf("[Store][database] add service meta table err: %s", err.Error()) + return err + } + + // 填充owner_service_map表 + if err := addOwnerServiceMap(tx, s.Name, s.Namespace, s.Owner); err != nil { + log.Errorf("[Store][database] add owner_service_map table err: %s", err.Error()) + return err + } + + if err := tx.Commit(); err != nil { + log.Errorf("[Store][database] add service tx commit err: %s", err.Error()) + return err + } + + return nil +} + +/** + * @brief删除服务 + */ +func (ss *serviceStore) DeleteService(id, serviceName, namespaceName string) error { + err := RetryTransaction("deleteService", func() error { + return ss.deleteService(id, serviceName, namespaceName) + }) + return store.Error(err) +} + +// 删除服务的内部函数 +func (ss *serviceStore) deleteService(id, serviceName, namespaceName string) error { + tx, err := ss.master.Begin() + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + + // 锁住服务 + revision, err := lockServiceWithID(tx.QueryRow, id) + if err != nil { + log.Errorf("[Store][database] lock service(%s) err: %s", id, err.Error()) + return err + } + if revision == "" { + log.Infof("[Store][database] not found service(%s)", id) + return nil + } + + // 删除服务 + if err := deleteServiceByID(tx, id); err != nil { + log.Errorf("[Store][database] delete service(%s) err : %s", id, err.Error()) + return err + } + + // 删除负责人、服务映射表对应记录 + if err := deleteOwnerServiceMap(tx, serviceName, namespaceName); err != nil { + log.Errorf("[Store][database] delete owner_service_map(%s) err : %s", id, err.Error()) + return err + } + + if err := tx.Commit(); err != nil { + log.Errorf("[Store][database] add service tx commit err: %s", err.Error()) + return err + } + + return nil +} + +// 删除服务或服务别名 +func deleteServiceByID(tx *BaseTx, id string) error { + log.Infof("[Store][database] delete service id(%s)", id) + str := "update service set flag = 1, mtime = sysdate() where id = ?" + if _, err := tx.Exec(str, id); err != nil { + return err + } + + return nil +} + +// 删除服务别名 +func (ss *serviceStore) DeleteServiceAlias(name string, namespace string) error { + str := "update service set flag = 1, mtime = sysdate() where name = ? and namespace = ?" + if _, err := ss.master.Exec(str, name, namespace); err != nil { + log.Errorf("[Store][database] delete service alias err: %s", err.Error()) + return store.Error(err) + } + + return nil +} + +// 更新服务别名 +func (ss *serviceStore) UpdateServiceAlias(alias *model.Service, needUpdateOwner bool) error { + if alias.ID == "" || alias.Name == "" || alias.Namespace == "" || + alias.Token == "" || alias.Owner == "" || alias.Revision == "" || alias.Reference == "" { + return store.NewStatusError(store.EmptyParamsErr, "Update Service Alias missing some params") + } + + if err := ss.updateServiceAlias(alias, needUpdateOwner); err != nil { + log.Errorf("[Store][ServiceAlias] update service alias err: %s", err.Error()) + return store.Error(err) + } + + return nil +} + +// update service alias +func (ss *serviceStore) updateServiceAlias(alias *model.Service, needUpdateOwner bool) error { + tx, err := ss.master.Begin() + if err != nil { + log.Errorf("[Store][database] update service alias tx begin err: %s", err.Error()) + return err + } + defer func() { _ = tx.Rollback() }() + + str := `update service set name = ?, namespace = ?, reference = ?, comment = ?, token = ?, revision = ?, owner = ?, + mtime = sysdate() + where id = ? and (select flag from (select flag from service where id = ?) as alias) = 0` + + result, err := tx.Exec(str, alias.Name, alias.Namespace, alias.Reference, alias.Comment, alias.Token, + alias.Revision, alias.Owner, alias.ID, alias.Reference) + if err != nil { + log.Errorf("[Store][ServiceAlias] update service alias exec err: %s", err.Error()) + return err + } + + // 更新owner_service_map表 + if needUpdateOwner { + if err := updateOwnerServiceMap(tx, alias.Name, alias.Namespace, alias.Owner); err != nil { + log.Errorf("[Store][database] update owner_service_map table err: %s", err.Error()) + return err + } + } + + if err := tx.Commit(); err != nil { + log.Errorf("[Store][database] update service alias tx commit err: %s", err.Error()) + return err + } + + if err := checkServiceAffectedRows(result, 1); err != nil { + if store.Code(err) == store.AffectedRowsNotMatch { + return store.NewStatusError(store.NotFoundService, "not found service") + } + } + return nil +} + +/** + * @brief 检查服务数据库处理返回的行数 + */ +func checkServiceAffectedRows(result sql.Result, count int64) error { + n, err := result.RowsAffected() + if err != nil { + log.Errorf("[Store][ServiceAlias] get rows affected err: %s", err.Error()) + return err + } + + if n == count { + return nil + } + log.Errorf("[Store][ServiceAlias] get rows affected result(%d) is not match expect(%d)", n, count) + return store.NewStatusError(store.AffectedRowsNotMatch, "affected rows not match") +} + +/** + * @brief 更新完整的服务信息 + */ +func (ss *serviceStore) UpdateService(service *model.Service, needUpdateOwner bool) error { + if service.ID == "" || service.Name == "" || service.Namespace == "" || + service.Token == "" || service.Owner == "" || service.Revision == "" { + return store.NewStatusError(store.EmptyParamsErr, "Update Service missing some params") + } + + err := RetryTransaction("updateService", func() error { + return ss.updateService(service, needUpdateOwner) + }) + if err == nil { + return nil + } + + serr := store.Error(err) + if store.Code(serr) == store.DuplicateEntryErr { + serr = store.NewStatusError(store.DataConflictErr, err.Error()) + } + return serr +} + +// update service +func (ss *serviceStore) updateService(service *model.Service, needUpdateOwner bool) error { + tx, err := ss.master.Begin() + if err != nil { + log.Errorf("[Store][database] update service tx begin err: %s", err.Error()) + return err + } + defer func() { _ = tx.Rollback() }() + + // 更新main表 + if err := updateServiceMain(tx, service); err != nil { + log.Errorf("[Store][database] update service main table err: %s", err.Error()) + return err + } + + // 更新meta表 + if err := updateServiceMeta(tx, service.ID, service.Meta); err != nil { + log.Errorf("[Store][database] update service meta table err: %s", err.Error()) + return err + } + + // 更新owner_service_map表 + if needUpdateOwner { + if err := updateOwnerServiceMap(tx, service.Name, service.Namespace, service.Owner); err != nil { + log.Errorf("[Store][database] update owner_service_map table err: %s", err.Error()) + return err + } + } + + if err := tx.Commit(); err != nil { + log.Errorf("[Store][database] update service tx commit err: %s", err.Error()) + return err + } + return nil +} + +// 更新服务token +func (ss *serviceStore) UpdateServiceToken(id string, token string, revision string) error { + str := `update service set token = ?, revision = ?, mtime = sysdate() where id = ?` + _, err := ss.master.Exec(str, token, revision, id) + if err != nil { + log.Errorf("[Store][database] update service(%s) token err: %s", id, err.Error()) + return store.Error(err) + } + + return nil +} + +/** + * @brief 获取服务详情,只返回有效的数据 + */ +func (ss *serviceStore) GetService(name string, namespace string) (*model.Service, error) { + service, err := ss.getService(name, namespace) + if err != nil { + return nil, err + } + + if service != nil && !service.Valid { + return nil, nil + } + + return service, nil +} + +// 获取只获取服务token +// 返回服务ID,服务token +func (ss *serviceStore) GetSourceServiceToken(name string, namespace string) (*model.Service, error) { + str := `select id, token, IFNULL(platform_id, "") from service + where name = ? and namespace = ? and flag = 0 + and (reference is null or reference = '')` + var out model.Service + err := ss.master.QueryRow(str, name, namespace).Scan(&out.ID, &out.Token, &out.PlatformID) + switch { + case err == sql.ErrNoRows: + return nil, nil + case err != nil: + return nil, err + default: + out.Name = name + out.Namespace = namespace + return &out, nil + } +} + +/** + * @brief 根据服务ID查询服务详情 + */ +func (ss *serviceStore) GetServiceByID(id string) (*model.Service, error) { + service, err := ss.getServiceByID(id) + if err != nil { + return nil, err + } + if service != nil && !service.Valid { + return nil, nil + } + + return service, nil +} + +/** + * @brief 根据相关条件查询对应服务及数目,不包括别名 + */ +func (ss *serviceStore) GetServices(serviceFilters, serviceMetas map[string]string, instanceFilters *store.InstanceArgs, + offset, limit uint32) (uint32, []*model.Service, error) { + // 只查询flag=0的服务列表 + serviceFilters["service.flag"] = "0" + + out, err := ss.getServices(serviceFilters, serviceMetas, instanceFilters, offset, limit) + if err != nil { + return 0, nil, err + } + + num, err := ss.getServicesCount(serviceFilters, serviceMetas, instanceFilters) + if err != nil { + return 0, nil, err + } + return num, out, err +} + +// 获取所有服务总数 +func (ss *serviceStore) GetServicesCount() (uint32, error) { + countStr := "select count(*) from service where flag = 0" + return queryEntryCount(ss.master, countStr, nil) +} + +/** + * @brief 根据modify_time获取增量数据 + */ +func (ss *serviceStore) GetMoreServices(mtime time.Time, firstUpdate, disableBusiness, needMeta bool) ( + map[string]*model.Service, error) { + if needMeta { + services, err := getMoreServiceWithMeta(ss.slave.Query, mtime, firstUpdate, disableBusiness) + if err != nil { + log.Errorf("[Store][database] get more service+meta err: %s", err.Error()) + return nil, err + } + return services, nil + } else { + services, err := getMoreServiceMain(ss.slave.Query, mtime, firstUpdate, disableBusiness) + if err != nil { + log.Errorf("[Store][database] get more service main err: %s", err.Error()) + return nil, err + } + return services, nil + } +} + +/** + * @brief 获取系统服务 + */ +func (ss *serviceStore) GetSystemServices() ([]*model.Service, error) { + str := genServiceSelectSQL() + str += " from service where flag = 0 and namespace = ?" + rows, err := ss.master.Query(str, SystemNamespace) + if err != nil { + log.Errorf("[Store][database] get system service query err: %s", err.Error()) + return nil, err + } + + out, err := ss.fetchRowServices(rows) + if err != nil { + log.Errorf("[Store][database] get row services err: %s", err) + return nil, err + } + + return out, nil +} + +// 获取服务别名列表 +func (ss *serviceStore) GetServiceAliases(filter map[string]string, offset uint32, limit uint32) (uint32, + []*model.ServiceAlias, error) { + + whereFilter := serviceAliasFilter2Where(filter) + count, err := ss.getServiceAliasesCount(whereFilter) + if err != nil { + log.Errorf("[Store][database] get service aliases count err: %s", err.Error()) + return 0, nil, err + } + + items, err := ss.getServiceAliasesInfo(whereFilter, offset, limit) + if err != nil { + log.Errorf("[Store][database] get service aliases info err: %s", err.Error()) + return 0, nil, err + } + + return count, items, nil +} + +// 获取服务别名的详细信息 +func (ss *serviceStore) getServiceAliasesInfo(filter map[string]string, offset uint32, + limit uint32) ([]*model.ServiceAlias, error) { + // limit为0,则直接返回 + if limit == 0 { + return make([]*model.ServiceAlias, 0), nil + } + + baseStr := `select alias.id, alias.name, UNIX_TIMESTAMP(alias.ctime), UNIX_TIMESTAMP(alias.mtime), + alias.comment, source.id as sourceID, source.name as sourceName, source.namespace, alias.owner + from service as alias inner join service as source + on alias.reference = source.id and alias.flag != 1 ` + order := &Order{"alias.mtime", "desc"} + + str, args := genServiceAliasWhereSQLAndArgs(baseStr, filter, order, offset, limit) + rows, err := ss.master.Query(str, args...) + if err != nil { + log.Errorf("[Store][database] get service aliases query(%s) err: %s", str, err.Error()) + return nil, err + } + defer func() { _ = rows.Close() }() + + var out []*model.ServiceAlias + var ctime, mtime int64 + for rows.Next() { + var entry model.ServiceAlias + err := rows.Scan(&entry.ID, &entry.Alias, &ctime, &mtime, &entry.Comment, &entry.ServiceID, + &entry.Service, &entry.Namespace, &entry.Owner) + if err != nil { + log.Errorf("[Store][database] get service alias rows scan err: %s", err.Error()) + return nil, err + } + + entry.CreateTime = time.Unix(ctime, 0) + entry.ModifyTime = time.Unix(mtime, 0) + out = append(out, &entry) + } + + return out, nil +} + +// 获取别名总数 +func (ss *serviceStore) getServiceAliasesCount(filter map[string]string) (uint32, error) { + baseStr := `select count(*) from + service as alias inner join service as source + on alias.reference = source.id and alias.flag != 1 ` + str, args := genServiceAliasWhereSQLAndArgs(baseStr, filter, nil, 0, 1) + return queryEntryCount(ss.master, str, args) +} + +/** + * @brief 根据相关条件查询对应服务,不包括别名 + */ +func (ss *serviceStore) getServices(sFilters, sMetas map[string]string, iFilters *store.InstanceArgs, + offset, limit uint32) ([]*model.Service, error) { + // 不查询任意内容,直接返回空数组 + if limit == 0 { + return make([]*model.Service, 0), nil + } + + // 构造SQL语句 + var args []interface{} + whereStr := " from service where (reference is null or reference = '') " + if len(sMetas) > 0 { + subStr, subArgs := filterMetadata(sMetas) + whereStr += " and service.id in " + subStr + args = append(args, subArgs...) + } + if iFilters != nil { + subStr, subArgs := filterInstance(iFilters) + whereStr += " and service.id in " + subStr + args = append(args, subArgs...) + } + str := genServiceSelectSQL() + whereStr + + filterStr, filterArgs := genServiceFilterSQL(sFilters) + if filterStr != "" { + str += " and " + filterStr + args = append(args, filterArgs...) + } + + order := &Order{"service.mtime", "desc"} + page := &Page{offset, limit} + opStr, opArgs := genOrderAndPage(order, page) + + str += opStr + args = append(args, opArgs...) + rows, err := ss.master.Query(str, args...) + if err != nil { + log.Errorf("[Store][database] get services by filter query(%s) err: %s", str, err.Error()) + return nil, err + } + + out, err := ss.fetchRowServices(rows) + if err != nil { + log.Errorf("[Store][database] get row services err: %s", err) + return nil, err + } + + return out, nil +} + +/** + * @brief 根据相关条件查询对应服务数目,不包括别名 + */ +func (ss *serviceStore) getServicesCount( + sFilters, sMetas map[string]string, iFilters *store.InstanceArgs) (uint32, error) { + str := `select count(*) from service where (reference is null or reference = '')` + var args []interface{} + if len(sMetas) > 0 { + subStr, subArgs := filterMetadata(sMetas) + str += " and service.id in " + subStr + args = append(args, subArgs...) + } + if iFilters != nil { + subStr, subArgs := filterInstance(iFilters) + str += " and service.id in " + subStr + args = append(args, subArgs...) + } + + filterStr, filterArgs := genServiceFilterSQL(sFilters) + if filterStr != "" { + str += " and " + filterStr + args = append(args, filterArgs...) + } + return queryEntryCount(ss.master, str, args) +} + +// 根据rows,获取到services,并且批量获取对应的metadata +func (ss *serviceStore) fetchRowServices(rows *sql.Rows) ([]*model.Service, error) { + services, err := fetchServiceRows(rows) + if err != nil { + return nil, err + } + + data := make([]interface{}, 0, len(services)) + for idx := range services { + // 只获取valid为true的metadata + if services[idx].Valid { + data = append(data, services[idx]) + } + } + + err = BatchQuery("get-service-metadata", data, func(objects []interface{}) error { + rows, batchErr := batchQueryServiceMeta(ss.master.Query, objects) + if batchErr != nil { + return batchErr + } + metas := make(map[string]map[string]string) + batchErr = callFetchServiceMetaRows(rows, func(id, key, value string) (b bool, e error) { + if _, ok := metas[id]; !ok { + metas[id] = make(map[string]string) + } + metas[id][key] = value + return true, nil + }) + if batchErr != nil { + return batchErr + } + for id, meta := range metas { + for _, entry := range objects { + if entry.(*model.Service).ID == id { + entry.(*model.Service).Meta = meta + break + } + } + } + return nil + }) + if err != nil { + log.Errorf("[Store][database] get service metadata err: %s", err.Error()) + return nil, err + } + + return services, nil +} + +/** + * @brief 获取metadata数据 + */ +func (ss *serviceStore) getServiceMeta(id string) (map[string]string, error) { + if id == "" { + return nil, nil + } + + // 从metadata表中获取数据 + metaStr := "select `mkey`, `mvalue` from service_metadata where id = ?" + rows, err := ss.master.Query(metaStr, id) + if err != nil { + log.Errorf("[Store][database] get service metadata query err: %s", err.Error()) + return nil, err + } + return fetchServiceMeta(rows) +} + +// 获取service内部函数 +func (ss *serviceStore) getService(name string, namespace string) (*model.Service, error) { + if name == "" || namespace == "" { + return nil, errors.New("Get Service missing some params") + } + + out, err := ss.getServiceMain(name, namespace) + if err != nil { + return nil, err + } + if out == nil { + return nil, nil + } + + meta, err := ss.getServiceMeta(out.ID) + if err != nil { + return nil, err + } + + out.Meta = meta + return out, nil +} + +// 获取服务表的信息,不包括metadata +func (ss *serviceStore) getServiceMain(name string, namespace string) (*model.Service, error) { + str := genServiceSelectSQL() + " from service where name = ? and namespace = ?" + rows, err := ss.master.Query(str, name, namespace) + if err != nil { + log.Errorf("[Store][database] get service err: %s", err.Error()) + return nil, err + } + + out, err := fetchServiceRows(rows) + if err != nil { + return nil, err + } + + if len(out) == 0 { + return nil, nil + } + + return out[0], nil +} + +// 根据服务ID获取服务详情的内部函数 +func (ss *serviceStore) getServiceByID(serviceID string) (*model.Service, error) { + str := genServiceSelectSQL() + " from service where service.id = ?" + rows, err := ss.master.Query(str, serviceID) + if err != nil { + log.Errorf("[Store][database] get service by id query err: %s", err.Error()) + return nil, err + } + + out, err := fetchServiceRows(rows) + if err != nil { + return nil, err + } + + if len(out) == 0 { + return nil, nil + } + + meta, err := ss.getServiceMeta(out[0].ID) + if err != nil { + return nil, err + } + out[0].Meta = meta + + return out[0], nil +} + +// 清理无效数据,flag=1的数据 +// 只需要删除service即可 +func (ss *serviceStore) cleanService(name string, namespace string) error { + log.Infof("[Store][database] clean service(%s, %s)", name, namespace) + str := "delete from service where name = ? and namespace = ? and flag = 1" + _, err := ss.master.Exec(str, name, namespace) + if err != nil { + log.Errorf("[Store][database] clean service(%s, %s) err: %s", name, namespace, err.Error()) + return err + } + + return nil +} + +/** + * @brief 获取增量服务 + * @note 包括元数据 + */ +func getMoreServiceWithMeta(queryHandler QueryHandler, mtime time.Time, firstUpdate, disableBusiness bool) ( + map[string]*model.Service, error) { + // 首次拉取 + if firstUpdate { + // 获取全量服务 + services, err := getMoreServiceMain(queryHandler, mtime, firstUpdate, disableBusiness) + if err != nil { + log.Errorf("[Store][database] get more service main err: %s", err.Error()) + return nil, err + } + // 获取全量服务元数据 + str := "select id, mkey, mvalue from service_metadata" + rows, err := queryHandler(str) + if err != nil { + log.Errorf("[Store][database] acquire services meta query err: %s", err.Error()) + return nil, err + } + if err := fetchMoreServiceMeta(services, rows); err != nil { + return nil, err + } + return services, nil + } + + // 非首次拉取 + var args []interface{} + args = append(args, time2String(mtime)) + str := genServiceSelectSQL() + `, IFNULL(service_metadata.id, ""), IFNULL(mkey, ""), IFNULL(mvalue, "") ` + + `from service left join service_metadata on service.id = service_metadata.id where service.mtime >= ?` + if disableBusiness { + str += " and service.namespace = ?" + args = append(args, SystemNamespace) + } + rows, err := queryHandler(str, args...) + if err != nil { + log.Errorf("[Store][database] get more services with meta query err: %s", err.Error()) + return nil, err + } + return fetchServiceWithMetaRows(rows) +} + +/** + * @brief 获取service+metadata rows里面的数据 + */ +func fetchServiceWithMetaRows(rows *sql.Rows) (map[string]*model.Service, error) { + if rows == nil { + return nil, nil + } + defer rows.Close() + + out := make(map[string]*model.Service) + var ctime, mtime int64 + var id, mKey, mValue string + var flag int + progress := 0 + for rows.Next() { + progress++ + if progress%100000 == 0 { + log.Infof("[Store][database] services+meta row next progress: %d", progress) + } + + var item model.Service + if err := rows.Scan(&item.ID, &item.Name, &item.Namespace, &item.Business, &item.Comment, + &item.Token, &item.Revision, &item.Owner, &flag, &ctime, &mtime, &item.Ports, + &item.Department, &item.CmdbMod1, &item.CmdbMod2, &item.CmdbMod3, + &item.Reference, &item.ReferFilter, &item.PlatformID, &id, &mKey, &mValue); err != nil { + log.Errorf("[Store][database] fetch service+meta rows scan err: %s", err.Error()) + return nil, err + } + item.CreateTime = time.Unix(ctime, 0) + item.ModifyTime = time.Unix(mtime, 0) + item.Valid = true + if flag == 1 { + item.Valid = false + } + + if _, ok := out[item.ID]; !ok { + out[item.ID] = &item + } + // 服务存在meta + if id != "" { + if out[item.ID].Meta == nil { + out[item.ID].Meta = make(map[string]string) + } + out[item.ID].Meta[mKey] = mValue + } + } + + if err := rows.Err(); err != nil { + log.Errorf("[Store][database] fetch service+meta rows next err: %s", err.Error()) + return nil, err + } + return out, nil +} + +// get more service main +func getMoreServiceMain(queryHandler QueryHandler, mtime time.Time, + firstUpdate, disableBusiness bool) (map[string]*model.Service, error) { + var args []interface{} + args = append(args, time2String(mtime)) + str := genServiceSelectSQL() + " from service where service.mtime >= ?" + if disableBusiness { + str += " and service.namespace = ?" + args = append(args, SystemNamespace) + } + if firstUpdate { + str += " and flag != 1" // nolint + } + rows, err := queryHandler(str, args...) + if err != nil { + log.Errorf("[Store][database] get more services query err: %s", err.Error()) + return nil, err + } + out := make(map[string]*model.Service) + err = callFetchServiceRows(rows, func(entry *model.Service) (b bool, e error) { + out[entry.ID] = entry + return true, nil + }) + if err != nil { + log.Errorf("[Store][database] call fetch service rows err: %s", err.Error()) + return nil, err + } + + return out, nil +} + +// 批量查询service meta的封装 +func batchQueryServiceMeta(handler QueryHandler, services []interface{}) (*sql.Rows, error) { + if len(services) == 0 { + return nil, nil + } + + str := "select `id`, `mkey`, `mvalue` from service_metadata where id in(" + first := true + args := make([]interface{}, 0, len(services)) + for _, ele := range services { + if first { + str += "?" + first = false + } else { + str += ",?" + } + args = append(args, ele.(*model.Service).ID) + } + str += ")" + + rows, err := handler(str, args...) + if err != nil { + log.Errorf("[Store][database] batch query service meta err: %s", err.Error()) + return nil, err + } + + return rows, nil +} + +// fetch more service meta +func fetchMoreServiceMeta(services map[string]*model.Service, rows *sql.Rows) error { + err := callFetchServiceMetaRows(rows, func(id, key, value string) (b bool, e error) { + service, ok := services[id] + if !ok { + return true, nil + } + if service.Meta == nil { + service.Meta = make(map[string]string) + } + service.Meta[key] = value + return true, nil + }) + if err != nil { + log.Errorf("[Store][database] call fetch service meta rows err: %s", err.Error()) + return err + } + + return nil +} + +// 获取metadata的回调 +func callFetchServiceMetaRows(rows *sql.Rows, handler func(id, key, value string) (bool, error)) error { + if rows == nil { + return nil + } + defer rows.Close() + + var id, key, value string + progress := 0 + for rows.Next() { + progress++ + if progress%100000 == 0 { + log.Infof("[Store][database] fetch service meta rows progress: %d", progress) + } + if err := rows.Scan(&id, &key, &value); err != nil { + log.Errorf("[Store][database] multi get service metadata scan err: %s", err.Error()) + return err + } + ok, err := handler(id, key, value) + if err != nil { + return err + } + if !ok { + return nil + } + } + if err := rows.Err(); err != nil { + return err + } + return nil +} + +// rlock service +func rlockServiceWithID(queryRow func(query string, args ...interface{}) *sql.Row, + id string) (string, error) { + str := "select revision from service where id = ? and flag != 1 lock in share mode" + var revision string + err := queryRow(str, id).Scan(&revision) + switch { + case err == sql.ErrNoRows: + return "", nil + case err != nil: + return "", err + default: + return revision, nil + } +} + +// lock service +func lockServiceWithID(queryRow func(query string, args ...interface{}) *sql.Row, + id string) (string, error) { + str := "select revision from service where id = ? and flag != 1 for update" + var revision string + err := queryRow(str, id).Scan(&revision) + switch { + case err == sql.ErrNoRows: + return "", nil + case err != nil: + return "", err + default: + return revision, nil + } +} + +/** + * @brief 增加service主表数据 + */ +func addServiceMain(tx *BaseTx, s *model.Service) error { + // 先把主表填充 + mainStr := `insert into service + (id, name, namespace, ports, business, department, cmdb_mod1, cmdb_mod2, + cmdb_mod3, comment, token, reference, platform_id, revision, owner, ctime, mtime) + values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, sysdate(), sysdate())` + + _, err := tx.Exec(mainStr, s.ID, s.Name, s.Namespace, s.Ports, s.Business, s.Department, + s.CmdbMod1, s.CmdbMod2, s.CmdbMod3, s.Comment, s.Token, + s.Reference, s.PlatformID, s.Revision, s.Owner) + return err +} + +/** + * @brief 增加服务metadata + */ +func addServiceMeta(tx *BaseTx, id string, meta map[string]string) error { + if len(meta) == 0 { + return nil + } + str := "insert into service_metadata(id, `mkey`, `mvalue`, `ctime`, `mtime`) values " + cnt := 0 + args := make([]interface{}, 0, len(meta)*3) + for key, value := range meta { + cnt++ + if cnt == len(meta) { + str += "(?, ?, ?, sysdate(), sysdate())" // nolint + } else { + str += "(?, ?, ?, sysdate(), sysdate())," + } + + args = append(args, id) + args = append(args, key) + args = append(args, value) + } + + //log.Infof("str: %s, args: %+v", str, args) + _, err := tx.Exec(str, args...) + return err +} + +/** + * @brief 更新service主表 + */ +func updateServiceMain(tx *BaseTx, service *model.Service) error { + str := `update service set name = ?, namespace = ?, ports = ?, business = ?, + department = ?, cmdb_mod1 = ?, cmdb_mod2 = ?, cmdb_mod3 = ?, comment = ?, token = ?, platform_id = ?, + revision = ?, owner = ?, mtime = sysdate() where id = ?` + + _, err := tx.Exec(str, service.Name, service.Namespace, service.Ports, service.Business, + service.Department, service.CmdbMod1, service.CmdbMod2, service.CmdbMod3, + service.Comment, service.Token, service.PlatformID, service.Revision, service.Owner, service.ID) + return err +} + +/** + * @brief 更新service meta表 + */ +func updateServiceMeta(tx *BaseTx, id string, meta map[string]string) error { + // 只有metadata为nil的时候,则不用处理。 + // 如果metadata不为nil,但是len(metadata) == 0,则代表删除metadata + if meta == nil { + return nil + } + + str := "delete from service_metadata where id = ?" + if _, err := tx.Exec(str, id); err != nil { + return err + } + + return addServiceMeta(tx, id, meta) +} + +/** + * @brief 从rows里面获取metadata数据 + */ +func fetchServiceMeta(rows *sql.Rows) (map[string]string, error) { + if rows == nil { + return nil, nil + } + defer rows.Close() + + out := make(map[string]string) + var key, value string + for rows.Next() { + + if err := rows.Scan(&key, &value); err != nil { + log.Errorf("[Store][database] fetch service meta err: %s", err.Error()) + return nil, err + } + out[key] = value + } + if err := rows.Err(); err != nil { + log.Errorf("[Store][database] service metadata rows err: %s", err.Error()) + return nil, err + } + + return out, nil +} + +// 生成service查询语句 +func genServiceSelectSQL() string { + return `select service.id, name, namespace, IFNULL(business, ""), IFNULL(comment, ""), + token, service.revision, owner, service.flag, + UNIX_TIMESTAMP(service.ctime), UNIX_TIMESTAMP(service.mtime), + IFNULL(ports, ""), IFNULL(department, ""), IFNULL(cmdb_mod1, ""), IFNULL(cmdb_mod2, ""), + IFNULL(cmdb_mod3, ""), IFNULL(reference, ""), IFNULL(refer_filter, ""), IFNULL(platform_id, "") ` +} + +// call fetch service rows +func callFetchServiceRows(rows *sql.Rows, callback func(entry *model.Service) (bool, error)) error { + if rows == nil { + return nil + } + defer rows.Close() + + var ctime, mtime int64 + var flag int + progress := 0 + for rows.Next() { + progress++ + if progress%100000 == 0 { + log.Infof("[Store][database] services row next progress: %d", progress) + } + + var item model.Service + err := rows.Scan( + &item.ID, &item.Name, &item.Namespace, &item.Business, &item.Comment, + &item.Token, &item.Revision, &item.Owner, &flag, &ctime, &mtime, &item.Ports, + &item.Department, &item.CmdbMod1, &item.CmdbMod2, &item.CmdbMod3, + &item.Reference, &item.ReferFilter, &item.PlatformID) + + if err != nil { + log.Errorf("[Store][database] fetch service rows scan err: %s", err.Error()) + return err + } + + item.CreateTime = time.Unix(ctime, 0) + item.ModifyTime = time.Unix(mtime, 0) + item.Valid = true + if flag == 1 { + item.Valid = false + } + ok, err := callback(&item) + if err != nil { + return err + } + if !ok { + return nil + } + } + if err := rows.Err(); err != nil { + log.Errorf("[Store][database] fetch service rows next err: %s", err.Error()) + return err + } + return nil +} + +// 获取service rows里面的数据 +func fetchServiceRows(rows *sql.Rows) ([]*model.Service, error) { + var out []*model.Service + err := callFetchServiceRows(rows, func(entry *model.Service) (b bool, e error) { + out = append(out, entry) + return true, nil + }) + if err != nil { + return nil, err + } + + return out, nil +} + +// 查找service,根据instance属性过滤 +// 生成子查询语句 +func filterInstance(filters *store.InstanceArgs) (string, []interface{}) { + var args []interface{} + str := "(select service_id from instance where instance.flag != 1 and host in (" + + PlaceholdersN(len(filters.Hosts)) + ")" + if len(filters.Ports) > 0 { + str += "and port in (" + PlaceholdersN(len(filters.Ports)) + ")" + } + str += " group by service_id)" + for _, host := range filters.Hosts { + args = append(args, host) + } + for _, port := range filters.Ports { + args = append(args, port) + } + return str, args +} + +// 查找service,根据metadata属性过滤 +// 生成子查询语句 +// 多个metadata,取交集(and) +func filterMetadata(metas map[string]string) (string, []interface{}) { + str := "(select id from service_metadata where mkey = ? and mvalue = ?)" + args := make([]interface{}, 0, 2) + for key, value := range metas { + args = append(args, key) + args = append(args, value) + } + + return str, args +} + +//查询多个服务的id +func (ss *serviceStore) GetServicesBatch(services []*model.Service) ([]*model.Service, error) { + if len(services) == 0 { + return nil, nil + } + str := `select id, name, namespace,owner from service where flag = 0 and (name, namespace) in (` + args := make([]interface{}, 0, len(services)*2) + for key, value := range services { + str += "(" + PlaceholdersN(2) + ")" + if key != len(services)-1 { + str += "," + } + args = append(args, value.Name) + args = append(args, value.Namespace) + } + str += `)` + + rows, err := ss.master.Query(str, args...) + if err != nil { + log.Errorf("[Store][database] query services batch err: %s", err.Error()) + return nil, err + } + + res := make([]*model.Service, 0, len(services)) + var namespace, name, id, owner string + for rows.Next() { + err := rows.Scan(&id, &name, &namespace, &owner) + if err != nil { + log.Errorf("[Store][database] fetch services batch scan err: %s", err.Error()) + return nil, err + } + res = append(res, &model.Service{ + ID: id, + Name: name, + Namespace: namespace, + Owner: owner, + }) + } + if err := rows.Err(); err != nil { + log.Errorf("[Store][database] fetch services batch next err: %s", err.Error()) + return nil, err + } + return res, nil +} + +// 填充owner_service_map表 +func addOwnerServiceMap(tx *BaseTx, service, namespace, owner string) error { + addSql := "insert into owner_service_map(id,owner,service,namespace) values" + + // 根据; ,进行分割 + owners := strings.FieldsFunc(owner, func(r rune) bool { + return r == ';' || r == ',' + }) + args := make([]interface{}, 0) + + if len(owners) >= 1 { + for i := 0; i < len(owners); i++ { + addSql += "(?,?,?,?)," + args = append(args, naming.NewUUID(), owners[i], service, namespace) + } + if len(args) != 0 { + addSql = strings.TrimSuffix(addSql, ",") + if _, err := tx.Exec(addSql, args...); err != nil { + return err + } + } + } + return nil +} + +// 删除owner_service_map表中对应记录 +func deleteOwnerServiceMap(tx *BaseTx, service, namespace string) error { + log.Infof("[Store][database] delete service(%s) namespace(%s)", service, namespace) + delSql := "delete from owner_service_map where service=? and namespace=?" + if _, err := tx.Exec(delSql, service, namespace); err != nil { + return err + } + + return nil +} + +// owner_service_map表,先删除,后填充 +func updateOwnerServiceMap(tx *BaseTx, service, namespace, owner string) error { + // 删除 + if err := deleteOwnerServiceMap(tx, service, namespace); err != nil { + return err + } + // 填充 + if err := addOwnerServiceMap(tx, service, namespace, owner); err != nil { + return err + } + + return nil +} diff --git a/store/defaultStore/sql.go b/store/defaultStore/sql.go new file mode 100644 index 000000000..f916a9cc0 --- /dev/null +++ b/store/defaultStore/sql.go @@ -0,0 +1,304 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package defaultStore + +import ( + "fmt" + "strconv" + "strings" +) + +const ( + OwnerAttribute = "owner" + And = " and" +) + +// 排序结构体 +type Order struct { + Filed string + Sequence string +} + +// 分页结构体 +type Page struct { + Offset uint32 + Limit uint32 +} + +/** + * @brief 判断名字是否为通配名字,只支持前缀索引(名字最后为*) + */ +func isWildName(name string) bool { + length := len(name) + return length >= 1 && name[length-1:length] == "*" +} + +func boolToInt(v bool) int { + if v { + return 1 + } + return 0 +} + +// 根据filter生成where相关的语句 +func genFilterSQL(filter map[string]string) (string, []interface{}) { + if len(filter) == 0 { + return "", nil + } + + args := make([]interface{}, 0, len(filter)) + var str string + firstIndex := true + for key, value := range filter { + if !firstIndex { + str += And + } + firstIndex = false + // 这个查询组装,先这样完成,后续优化filter TODO + if key == OwnerAttribute || key == "alias."+OwnerAttribute || key == "business" { + str += fmt.Sprintf(" %s like ?", key) + value = "%" + value + "%" + } else if key == "name" && isWildName(value) { + str += " name like ?" + value = "%" + value[0:len(value)-1] + "%" + } else if key == "host" { + hosts := strings.Split(value, ",") + str += " host in (" + PlaceholdersN(len(hosts)) + ")" + for _, host := range hosts { + args = append(args, host) + } + } else if key == "managed" { + str += " managed = ?" + managed, _ := strconv.ParseBool(value) + args = append(args, boolToInt(managed)) + continue + } else { + str += " " + key + "=?" + } + if key != "host" { + args = append(args, value) + } + } + + return str, args +} + +// 根据service filter生成where相关的语句 +func genServiceFilterSQL(filter map[string]string) (string, []interface{}) { + if len(filter) == 0 { + return "", nil + } + + args := make([]interface{}, 0, len(filter)) + var str string + firstIndex := true + for key, value := range filter { + if !firstIndex { + str += And + } + firstIndex = false + + if key == OwnerAttribute { + str += " (service.name, service.namespace) in (select service,namespace from owner_service_map where owner=?)" + } else if key == "alias."+OwnerAttribute { + str += " (alias.name, alias.namespace) in (select service,namespace from owner_service_map where owner=?)" + } else if key == "business" { + str += fmt.Sprintf(" %s like ?", key) + value = "%" + value + "%" + } else if key == "name" && isWildName(value) { + str += " name like ?" + value = "%" + value[0:len(value)-1] + "%" + } else { + str += " " + key + "=?" + } + + args = append(args, value) + } + + return str, args +} + +// 根据规则的filter生成where相关的语句 +func genRuleFilterSQL(tableName string, filter map[string]string) (string, []interface{}) { + if len(filter) == 0 { + return "", nil + } + + args := make([]interface{}, 0, len(filter)) + var str string + firstIndex := true + for key, value := range filter { + if tableName != "" { + key = tableName + "." + key + } + if !firstIndex { + str += And + } + if key == OwnerAttribute || key == (tableName+"."+OwnerAttribute) { + str += fmt.Sprintf(" %s like ? ", key) + value = "%" + value + "%" + } else { + str += " " + key + " = ? " + } + args = append(args, value) + firstIndex = false + } + return str, args +} + +// 生成order和page相关语句 +func genOrderAndPage(order *Order, page *Page) (string, []interface{}) { + var str string + var args []interface{} + if order != nil { + str += " order by " + order.Filed + " " + order.Sequence + } + if page != nil { + str += " limit ?, ?" + args = append(args, page.Offset, page.Limit) + } + + return str, args +} + +/** + * @brief 生成service和instance查询数据的where语句和对应参数 + */ +func genWhereSQLAndArgs(str string, filter, metaFilter map[string]string, order *Order, offset uint32, limit uint32) ( + string, []interface{}) { + baseStr := str + var args []interface{} + filterStr, filterArgs := genFilterSQL(filter) + var conjunction string = " where " + if filterStr != "" { + baseStr += " where " + filterStr + conjunction = " and " + } + args = append(args, filterArgs...) + var metaStr string + var metaArgs []interface{} + if len(metaFilter) > 0 { + metaStr, metaArgs = genInstanceMetadataArgs(metaFilter) + args = append(args, metaArgs...) + baseStr += conjunction + metaStr + } + page := &Page{offset, limit} + opStr, opArgs := genOrderAndPage(order, page) + + return baseStr + opStr, append(args, opArgs...) +} + +func genInstanceMetadataArgs(metaFilter map[string]string) (string, []interface{}) { + str := `instance.id in (select id from instance_metadata where mkey = ? and mvalue = ?)` + args := make([]interface{}, 0, 2) + for k, v := range metaFilter { + args = append(args, k) + args = append(args, v) + } + return str, args +} + +/** + * @brief 生成service alias查询数据的where语句和对应参数 + */ +func genServiceAliasWhereSQLAndArgs(str string, filter map[string]string, order *Order, offset uint32, limit uint32) ( + string, []interface{}) { + baseStr := str + filterStr, filterArgs := genServiceFilterSQL(filter) + if filterStr != "" { + baseStr += " where " + } + page := &Page{offset, limit} + opStr, opArgs := genOrderAndPage(order, page) + + return baseStr + filterStr + opStr, append(filterArgs, opArgs...) +} + +/** + * @brief 生成namespace查询数据的where语句和对应参数 + */ +func genNamespaceWhereSQLAndArgs(str string, filter map[string][]string, order *Order, offset, limit int) ( + string, []interface{}) { + num := 0 + for _, value := range filter { + num += len(value) + } + args := make([]interface{}, 0, num+2) + + if num > 0 { + str += "where" + firstIndex := true + + for index, value := range filter { + if !firstIndex { + str += And + } + str += " (" + + firstItem := true + for _, item := range value { + if !firstItem { + str += " or " + } + if index == OwnerAttribute { + str += "owner like ?" + item = "%" + item + "%" + } else { + str += index + "=?" + } + args = append(args, item) + firstItem = false + } + firstIndex = false + str += ")" + } + } + + if order != nil { + str += " order by " + order.Filed + " " + order.Sequence + } + + str += " limit ?, ?" + args = append(args, offset, limit) + + return str, args +} + +// 根据metadata属性过滤 +// 生成子查询语句 +// 多个metadata,取交集(and) +func filterMetadataWithTable(table string, metas map[string]string) (string, []interface{}) { + str := "(select id from " + table + " where mkey = ? and mvalue = ?)" + args := make([]interface{}, 0, 2) + for key, value := range metas { + args = append(args, key) + args = append(args, value) + } + + return str, args +} + + +// 构造多个占位符 +func PlaceholdersN(size int) string { + if size <= 0 { + return "" + } + str := strings.Repeat("?,", size) + return str[0 : len(str)-1] +} diff --git a/store/defaultStore/sql_test.go b/store/defaultStore/sql_test.go new file mode 100644 index 000000000..4ed10a85f --- /dev/null +++ b/store/defaultStore/sql_test.go @@ -0,0 +1,32 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package defaultStore + +import ( + . "github.com/smartystreets/goconvey/convey" + "testing" +) + +// 构造占位符的测试 +func TestPlaceholdersN(t *testing.T) { + Convey("可以正常输出", t, func() { + So(PlaceholdersN(-1), ShouldBeEmpty) + So(PlaceholdersN(1), ShouldEqual, "?") + So(PlaceholdersN(3), ShouldEqual, "?,?,?") + }) +} diff --git a/store/defaultStore/transaction.go b/store/defaultStore/transaction.go new file mode 100644 index 000000000..f991cf1ed --- /dev/null +++ b/store/defaultStore/transaction.go @@ -0,0 +1,255 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package defaultStore + +import ( + "crypto/rand" + "github.com/polarismesh/polaris-server/common/log" + "github.com/polarismesh/polaris-server/common/model" + "github.com/pkg/errors" + "math/big" +) + +// 事务 +// 不支持多协程并发操作,当前先支持单个协程串行操作 +type transaction struct { + tx *BaseTx + failed bool // 判断事务执行是否失败 + commit bool // 判断事务已经提交,如果已经提交,则Commit会立即返回 +} + +// 提交事务,释放tx +func (t *transaction) Commit() error { + if t.commit { + return nil + } + + t.commit = true + if t.failed { + return t.tx.Rollback() + } + + return t.tx.Commit() +} + +// 启动锁,限制Server启动的并发数 +func (t *transaction) LockBootstrap(key string, server string) error { + countStr := "select count(*) from start_lock where lock_key = ?" + var count int + if err := t.tx.QueryRow(countStr, key).Scan(&count); err != nil { + log.Errorf("[Store][database] lock bootstrap scan count err: %s", err.Error()) + t.failed = true + return err + } + + bid, err := rand.Int(rand.Reader, big.NewInt(1024)) + if err != nil { + log.Errorf("[Store][database] rand int err: %s", err.Error()) + return err + } + + log.Infof("[Store][database] get rand int: %d", bid.Int64()) + id := int(bid.Int64())%count + 1 + // innodb_lock_wait_timeout这个global变量表示锁超时的时间,cdb为7200秒 + log.Infof("[Store][database] update start lock_id: %d, lock_key: %s, lock server: %s", id, key, server) + lockStr := "update start_lock set server = ? where lock_id = ? and lock_key = ?" + if _, err := t.tx.Exec(lockStr, server, id, key); err != nil { + log.Errorf("[Store][database] update start lock err: %s", err.Error()) + t.failed = true + return err + } + + return nil +} + +// 排它锁,锁住指定命名空间 +func (t *transaction) LockNamespace(name string) (*model.Namespace, error) { + str := genNamespaceSelectSQL() + " where name = ? and flag != 1 for update" + return t.getValidNamespace(str, name) +} + +// 共享锁,锁住命名空间 +func (t *transaction) RLockNamespace(name string) (*model.Namespace, error) { + str := genNamespaceSelectSQL() + " where name = ? and flag != 1 lock in share mode" + return t.getValidNamespace(str, name) +} + +// 删除命名空间,并且提交事务 +func (t *transaction) DeleteNamespace(name string) error { + if err := t.finish(); err != nil { + return err + } + + str := "update namespace set flag = 1, mtime = sysdate() where name = ?" + if _, err := t.tx.Exec(str, name); err != nil { + t.failed = true + } + + return t.Commit() +} + +// 排它锁,锁住指定服务 +func (t *transaction) LockService(name string, namespace string) (*model.Service, error) { + str := genServiceSelectSQL() + + " from service where name = ? and namespace = ? and flag !=1 for update" + return t.getValidService(str, name, namespace) +} + +// 共享锁,锁住指定服务 +func (t *transaction) RLockService(name string, namespace string) (*model.Service, error) { + str := genServiceSelectSQL() + + " from service where name = ? and namespace = ? and flag !=1 lock in share mode" + return t.getValidService(str, name, namespace) +} + +// 批量锁住服务 +func (t *transaction) BatchRLockServices(ids map[string]bool) (map[string]bool, error) { + str := "select id, flag from service where id in ( " + first := true + args := make([]interface{}, 0, len(ids)) + for id := range ids { + if first { + str += "?" + first = false + } else { + str += ", ?" + } + args = append(args, id) + } + str += ") and flag != 1 lock in share mode" + log.Infof("[Store][database] RLock services: %+v", args) + rows, err := t.tx.Query(str, args...) + if err != nil { + log.Errorf("[Store][database] batch RLock services err: %s", err.Error()) + return nil, err + } + defer rows.Close() + + out := make(map[string]bool) + var flag int + var id string + for rows.Next() { + if err := rows.Scan(&id, &flag); err != nil { + log.Errorf("[Store][database] RLock services scan err: %s", err.Error()) + return nil, err + } + + if flag == 0 { + out[id] = true + } else { + out[id] = false + } + } + if err := rows.Err(); err != nil { + log.Errorf("[Store][database] RLock service rows next err: %s", err.Error()) + return nil, err + } + + return out, nil +} + +// 删除服务,并且提交事务 +func (t *transaction) DeleteService(name string, namespace string) error { + if err := t.finish(); err != nil { + return err + } + + str := "update service set flag = 1, mtime = sysdate() where name = ? and namespace = ?" + if _, err := t.tx.Exec(str, name, namespace); err != nil { + log.Errorf("[Store][database] delete service err: %s", err.Error()) + t.failed = true + return err + } + + return nil +} + +// 根据源服务的ID,删除其所有的别名 +func (t *transaction) DeleteAliasWithSourceID(sourceServiceID string) error { + if err := t.finish(); err != nil { + return err + } + + str := `update service set flag = 1, mtime = sysdate() where reference = ?` + if _, err := t.tx.Exec(str, sourceServiceID); err != nil { + log.Errorf("[Store][database] delete service alias err: %s", err.Error()) + t.failed = false + return err + } + + return nil +} + +// 判断事务是否已经提交 +func (t *transaction) finish() error { + if t.failed || t.commit { + return errors.New("transaction has failed") + } + + return nil +} + +// 获取有效的命名空间数据 +func (t *transaction) getValidNamespace(sql string, name string) (*model.Namespace, error) { + if err := t.finish(); err != nil { + return nil, err + } + + rows, err := t.tx.Query(sql, name) + if err != nil { + t.failed = true + return nil, err + } + + out, err := namespaceFetchRows(rows) + if err != nil { + t.failed = true + return nil, err + } + + if len(out) == 0 { + return nil, nil + } + return out[0], nil +} + +// 获取有效的服务数据 +// 注意:该函数不会返回service_metadata +func (t *transaction) getValidService(sql string, name string, namespace string) (*model.Service, error) { + if err := t.finish(); err != nil { + return nil, err + } + + rows, err := t.tx.Query(sql, name, namespace) + if err != nil { + t.failed = true + return nil, err + } + + out, err := fetchServiceRows(rows) + if err != nil { + t.failed = true + return nil, err + } + + if len(out) == 0 { + return nil, nil + } + + return out[0], nil +} diff --git a/store/memory/memory.go b/store/memory/memory.go new file mode 100644 index 000000000..5a7aedf6e --- /dev/null +++ b/store/memory/memory.go @@ -0,0 +1,18 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package memory diff --git a/store/mock/README.md b/store/mock/README.md new file mode 100644 index 000000000..484b9f1f1 --- /dev/null +++ b/store/mock/README.md @@ -0,0 +1,7 @@ + # 对Store的API的mock + + ## mock文件生成方法 + 在`./store`目录执行 + ``` + mockgen -source=api.go -destination=./mock/api_mock.go -package=mock + ``` \ No newline at end of file diff --git a/store/mock/api_mock.go b/store/mock/api_mock.go new file mode 100644 index 000000000..4ff52c1c0 --- /dev/null +++ b/store/mock/api_mock.go @@ -0,0 +1,2960 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: api.go + +// Package mock is a generated GoMock package. +package mock + +import ( + model "github.com/polarismesh/polaris-server/common/model" + store "github.com/polarismesh/polaris-server/store" + gomock "github.com/golang/mock/gomock" + reflect "reflect" + time "time" +) + +// MockStore is a mock of Store interface +type MockStore struct { + ctrl *gomock.Controller + recorder *MockStoreMockRecorder +} + +// MockStoreMockRecorder is the mock recorder for MockStore +type MockStoreMockRecorder struct { + mock *MockStore +} + +// NewMockStore creates a new mock instance +func NewMockStore(ctrl *gomock.Controller) *MockStore { + mock := &MockStore{ctrl: ctrl} + mock.recorder = &MockStoreMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockStore) EXPECT() *MockStoreMockRecorder { + return m.recorder +} + +// Name mocks base method +func (m *MockStore) Name() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Name") + ret0, _ := ret[0].(string) + return ret0 +} + +// Name indicates an expected call of Name +func (mr *MockStoreMockRecorder) Name() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockStore)(nil).Name)) +} + +// Initialize mocks base method +func (m *MockStore) Initialize(c *store.Config) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Initialize", c) + ret0, _ := ret[0].(error) + return ret0 +} + +// Initialize indicates an expected call of Initialize +func (mr *MockStoreMockRecorder) Initialize(c interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Initialize", reflect.TypeOf((*MockStore)(nil).Initialize), c) +} + +// Destroy mocks base method +func (m *MockStore) Destroy() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Destroy") + ret0, _ := ret[0].(error) + return ret0 +} + +// Destroy indicates an expected call of Destroy +func (mr *MockStoreMockRecorder) Destroy() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Destroy", reflect.TypeOf((*MockStore)(nil).Destroy)) +} + +// CreateTransaction mocks base method +func (m *MockStore) CreateTransaction() (store.Transaction, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateTransaction") + ret0, _ := ret[0].(store.Transaction) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateTransaction indicates an expected call of CreateTransaction +func (mr *MockStoreMockRecorder) CreateTransaction() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateTransaction", reflect.TypeOf((*MockStore)(nil).CreateTransaction)) +} + +// AddNamespace mocks base method +func (m *MockStore) AddNamespace(namespace *model.Namespace) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddNamespace", namespace) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddNamespace indicates an expected call of AddNamespace +func (mr *MockStoreMockRecorder) AddNamespace(namespace interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddNamespace", reflect.TypeOf((*MockStore)(nil).AddNamespace), namespace) +} + +// UpdateNamespace mocks base method +func (m *MockStore) UpdateNamespace(namespace *model.Namespace) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateNamespace", namespace) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateNamespace indicates an expected call of UpdateNamespace +func (mr *MockStoreMockRecorder) UpdateNamespace(namespace interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateNamespace", reflect.TypeOf((*MockStore)(nil).UpdateNamespace), namespace) +} + +// UpdateNamespaceToken mocks base method +func (m *MockStore) UpdateNamespaceToken(name, token string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateNamespaceToken", name, token) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateNamespaceToken indicates an expected call of UpdateNamespaceToken +func (mr *MockStoreMockRecorder) UpdateNamespaceToken(name, token interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateNamespaceToken", reflect.TypeOf((*MockStore)(nil).UpdateNamespaceToken), name, token) +} + +// ListNamespaces mocks base method +func (m *MockStore) ListNamespaces(owner string) ([]*model.Namespace, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListNamespaces", owner) + ret0, _ := ret[0].([]*model.Namespace) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListNamespaces indicates an expected call of ListNamespaces +func (mr *MockStoreMockRecorder) ListNamespaces(owner interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListNamespaces", reflect.TypeOf((*MockStore)(nil).ListNamespaces), owner) +} + +// GetNamespace mocks base method +func (m *MockStore) GetNamespace(name string) (*model.Namespace, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetNamespace", name) + ret0, _ := ret[0].(*model.Namespace) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetNamespace indicates an expected call of GetNamespace +func (mr *MockStoreMockRecorder) GetNamespace(name interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNamespace", reflect.TypeOf((*MockStore)(nil).GetNamespace), name) +} + +// GetNamespaces mocks base method +func (m *MockStore) GetNamespaces(filter map[string][]string, offset, limit int) ([]*model.Namespace, uint32, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetNamespaces", filter, offset, limit) + ret0, _ := ret[0].([]*model.Namespace) + ret1, _ := ret[1].(uint32) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetNamespaces indicates an expected call of GetNamespaces +func (mr *MockStoreMockRecorder) GetNamespaces(filter, offset, limit interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNamespaces", reflect.TypeOf((*MockStore)(nil).GetNamespaces), filter, offset, limit) +} + +// GetMoreNamespaces mocks base method +func (m *MockStore) GetMoreNamespaces(mtime time.Time) ([]*model.Namespace, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMoreNamespaces", mtime) + ret0, _ := ret[0].([]*model.Namespace) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMoreNamespaces indicates an expected call of GetMoreNamespaces +func (mr *MockStoreMockRecorder) GetMoreNamespaces(mtime interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMoreNamespaces", reflect.TypeOf((*MockStore)(nil).GetMoreNamespaces), mtime) +} + +// AddBusiness mocks base method +func (m *MockStore) AddBusiness(business *model.Business) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddBusiness", business) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddBusiness indicates an expected call of AddBusiness +func (mr *MockStoreMockRecorder) AddBusiness(business interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddBusiness", reflect.TypeOf((*MockStore)(nil).AddBusiness), business) +} + +// DeleteBusiness mocks base method +func (m *MockStore) DeleteBusiness(bid string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteBusiness", bid) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteBusiness indicates an expected call of DeleteBusiness +func (mr *MockStoreMockRecorder) DeleteBusiness(bid interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteBusiness", reflect.TypeOf((*MockStore)(nil).DeleteBusiness), bid) +} + +// UpdateBusiness mocks base method +func (m *MockStore) UpdateBusiness(business *model.Business) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateBusiness", business) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateBusiness indicates an expected call of UpdateBusiness +func (mr *MockStoreMockRecorder) UpdateBusiness(business interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateBusiness", reflect.TypeOf((*MockStore)(nil).UpdateBusiness), business) +} + +// UpdateBusinessToken mocks base method +func (m *MockStore) UpdateBusinessToken(bid, token string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateBusinessToken", bid, token) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateBusinessToken indicates an expected call of UpdateBusinessToken +func (mr *MockStoreMockRecorder) UpdateBusinessToken(bid, token interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateBusinessToken", reflect.TypeOf((*MockStore)(nil).UpdateBusinessToken), bid, token) +} + +// ListBusiness mocks base method +func (m *MockStore) ListBusiness(owner string) ([]*model.Business, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListBusiness", owner) + ret0, _ := ret[0].([]*model.Business) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListBusiness indicates an expected call of ListBusiness +func (mr *MockStoreMockRecorder) ListBusiness(owner interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListBusiness", reflect.TypeOf((*MockStore)(nil).ListBusiness), owner) +} + +// GetBusinessByID mocks base method +func (m *MockStore) GetBusinessByID(id string) (*model.Business, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBusinessByID", id) + ret0, _ := ret[0].(*model.Business) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBusinessByID indicates an expected call of GetBusinessByID +func (mr *MockStoreMockRecorder) GetBusinessByID(id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBusinessByID", reflect.TypeOf((*MockStore)(nil).GetBusinessByID), id) +} + +// GetMoreBusiness mocks base method +func (m *MockStore) GetMoreBusiness(mtime time.Time) ([]*model.Business, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMoreBusiness", mtime) + ret0, _ := ret[0].([]*model.Business) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMoreBusiness indicates an expected call of GetMoreBusiness +func (mr *MockStoreMockRecorder) GetMoreBusiness(mtime interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMoreBusiness", reflect.TypeOf((*MockStore)(nil).GetMoreBusiness), mtime) +} + +// AddService mocks base method +func (m *MockStore) AddService(service *model.Service) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddService", service) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddService indicates an expected call of AddService +func (mr *MockStoreMockRecorder) AddService(service interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddService", reflect.TypeOf((*MockStore)(nil).AddService), service) +} + +// DeleteService mocks base method +func (m *MockStore) DeleteService(id, serviceName, namespaceName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteService", id, serviceName, namespaceName) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteService indicates an expected call of DeleteService +func (mr *MockStoreMockRecorder) DeleteService(id, serviceName, namespaceName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteService", reflect.TypeOf((*MockStore)(nil).DeleteService), id, serviceName, namespaceName) +} + +// DeleteServiceAlias mocks base method +func (m *MockStore) DeleteServiceAlias(name, namespace string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteServiceAlias", name, namespace) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteServiceAlias indicates an expected call of DeleteServiceAlias +func (mr *MockStoreMockRecorder) DeleteServiceAlias(name, namespace interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteServiceAlias", reflect.TypeOf((*MockStore)(nil).DeleteServiceAlias), name, namespace) +} + +// UpdateServiceAlias mocks base method +func (m *MockStore) UpdateServiceAlias(alias *model.Service, needUpdateOwner bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateServiceAlias", alias, needUpdateOwner) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateServiceAlias indicates an expected call of UpdateServiceAlias +func (mr *MockStoreMockRecorder) UpdateServiceAlias(alias, needUpdateOwner interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateServiceAlias", reflect.TypeOf((*MockStore)(nil).UpdateServiceAlias), alias, needUpdateOwner) +} + +// UpdateService mocks base method +func (m *MockStore) UpdateService(service *model.Service, needUpdateOwner bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateService", service, needUpdateOwner) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateService indicates an expected call of UpdateService +func (mr *MockStoreMockRecorder) UpdateService(service, needUpdateOwner interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateService", reflect.TypeOf((*MockStore)(nil).UpdateService), service, needUpdateOwner) +} + +// UpdateServiceToken mocks base method +func (m *MockStore) UpdateServiceToken(serviceID, token, revision string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateServiceToken", serviceID, token, revision) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateServiceToken indicates an expected call of UpdateServiceToken +func (mr *MockStoreMockRecorder) UpdateServiceToken(serviceID, token, revision interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateServiceToken", reflect.TypeOf((*MockStore)(nil).UpdateServiceToken), serviceID, token, revision) +} + +// GetSourceServiceToken mocks base method +func (m *MockStore) GetSourceServiceToken(name, namespace string) (*model.Service, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSourceServiceToken", name, namespace) + ret0, _ := ret[0].(*model.Service) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSourceServiceToken indicates an expected call of GetSourceServiceToken +func (mr *MockStoreMockRecorder) GetSourceServiceToken(name, namespace interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSourceServiceToken", reflect.TypeOf((*MockStore)(nil).GetSourceServiceToken), name, namespace) +} + +// GetService mocks base method +func (m *MockStore) GetService(name, namespace string) (*model.Service, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetService", name, namespace) + ret0, _ := ret[0].(*model.Service) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetService indicates an expected call of GetService +func (mr *MockStoreMockRecorder) GetService(name, namespace interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetService", reflect.TypeOf((*MockStore)(nil).GetService), name, namespace) +} + +// GetServiceByID mocks base method +func (m *MockStore) GetServiceByID(id string) (*model.Service, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetServiceByID", id) + ret0, _ := ret[0].(*model.Service) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetServiceByID indicates an expected call of GetServiceByID +func (mr *MockStoreMockRecorder) GetServiceByID(id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServiceByID", reflect.TypeOf((*MockStore)(nil).GetServiceByID), id) +} + +// GetServices mocks base method +func (m *MockStore) GetServices(serviceFilters, serviceMetas map[string]string, instanceFilters *store.InstanceArgs, offset, limit uint32) (uint32, []*model.Service, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetServices", serviceFilters, serviceMetas, instanceFilters, offset, limit) + ret0, _ := ret[0].(uint32) + ret1, _ := ret[1].([]*model.Service) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetServices indicates an expected call of GetServices +func (mr *MockStoreMockRecorder) GetServices(serviceFilters, serviceMetas, instanceFilters, offset, limit interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServices", reflect.TypeOf((*MockStore)(nil).GetServices), serviceFilters, serviceMetas, instanceFilters, offset, limit) +} + +// GetServicesCount mocks base method +func (m *MockStore) GetServicesCount() (uint32, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetServicesCount") + ret0, _ := ret[0].(uint32) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetServicesCount indicates an expected call of GetServicesCount +func (mr *MockStoreMockRecorder) GetServicesCount() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServicesCount", reflect.TypeOf((*MockStore)(nil).GetServicesCount)) +} + +// GetMoreServices mocks base method +func (m *MockStore) GetMoreServices(mtime time.Time, firstUpdate, disableBusiness, needMeta bool) (map[string]*model.Service, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMoreServices", mtime, firstUpdate, disableBusiness, needMeta) + ret0, _ := ret[0].(map[string]*model.Service) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMoreServices indicates an expected call of GetMoreServices +func (mr *MockStoreMockRecorder) GetMoreServices(mtime, firstUpdate, disableBusiness, needMeta interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMoreServices", reflect.TypeOf((*MockStore)(nil).GetMoreServices), mtime, firstUpdate, disableBusiness, needMeta) +} + +// GetServiceAliases mocks base method +func (m *MockStore) GetServiceAliases(filter map[string]string, offset, limit uint32) (uint32, []*model.ServiceAlias, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetServiceAliases", filter, offset, limit) + ret0, _ := ret[0].(uint32) + ret1, _ := ret[1].([]*model.ServiceAlias) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetServiceAliases indicates an expected call of GetServiceAliases +func (mr *MockStoreMockRecorder) GetServiceAliases(filter, offset, limit interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServiceAliases", reflect.TypeOf((*MockStore)(nil).GetServiceAliases), filter, offset, limit) +} + +// GetSystemServices mocks base method +func (m *MockStore) GetSystemServices() ([]*model.Service, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSystemServices") + ret0, _ := ret[0].([]*model.Service) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSystemServices indicates an expected call of GetSystemServices +func (mr *MockStoreMockRecorder) GetSystemServices() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSystemServices", reflect.TypeOf((*MockStore)(nil).GetSystemServices)) +} + +// GetServicesBatch mocks base method +func (m *MockStore) GetServicesBatch(services []*model.Service) ([]*model.Service, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetServicesBatch", services) + ret0, _ := ret[0].([]*model.Service) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetServicesBatch indicates an expected call of GetServicesBatch +func (mr *MockStoreMockRecorder) GetServicesBatch(services interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServicesBatch", reflect.TypeOf((*MockStore)(nil).GetServicesBatch), services) +} + +// AddInstance mocks base method +func (m *MockStore) AddInstance(instance *model.Instance) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddInstance", instance) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddInstance indicates an expected call of AddInstance +func (mr *MockStoreMockRecorder) AddInstance(instance interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddInstance", reflect.TypeOf((*MockStore)(nil).AddInstance), instance) +} + +// BatchAddInstances mocks base method +func (m *MockStore) BatchAddInstances(instances []*model.Instance) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BatchAddInstances", instances) + ret0, _ := ret[0].(error) + return ret0 +} + +// BatchAddInstances indicates an expected call of BatchAddInstances +func (mr *MockStoreMockRecorder) BatchAddInstances(instances interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BatchAddInstances", reflect.TypeOf((*MockStore)(nil).BatchAddInstances), instances) +} + +// UpdateInstance mocks base method +func (m *MockStore) UpdateInstance(instance *model.Instance) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateInstance", instance) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateInstance indicates an expected call of UpdateInstance +func (mr *MockStoreMockRecorder) UpdateInstance(instance interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateInstance", reflect.TypeOf((*MockStore)(nil).UpdateInstance), instance) +} + +// DeleteInstance mocks base method +func (m *MockStore) DeleteInstance(instanceID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteInstance", instanceID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteInstance indicates an expected call of DeleteInstance +func (mr *MockStoreMockRecorder) DeleteInstance(instanceID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteInstance", reflect.TypeOf((*MockStore)(nil).DeleteInstance), instanceID) +} + +// BatchDeleteInstances mocks base method +func (m *MockStore) BatchDeleteInstances(ids []interface{}) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BatchDeleteInstances", ids) + ret0, _ := ret[0].(error) + return ret0 +} + +// BatchDeleteInstances indicates an expected call of BatchDeleteInstances +func (mr *MockStoreMockRecorder) BatchDeleteInstances(ids interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BatchDeleteInstances", reflect.TypeOf((*MockStore)(nil).BatchDeleteInstances), ids) +} + +// CleanInstance mocks base method +func (m *MockStore) CleanInstance(instanceID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CleanInstance", instanceID) + ret0, _ := ret[0].(error) + return ret0 +} + +// CleanInstance indicates an expected call of CleanInstance +func (mr *MockStoreMockRecorder) CleanInstance(instanceID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CleanInstance", reflect.TypeOf((*MockStore)(nil).CleanInstance), instanceID) +} + +// CheckInstancesExisted mocks base method +func (m *MockStore) CheckInstancesExisted(ids map[string]bool) (map[string]bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CheckInstancesExisted", ids) + ret0, _ := ret[0].(map[string]bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CheckInstancesExisted indicates an expected call of CheckInstancesExisted +func (mr *MockStoreMockRecorder) CheckInstancesExisted(ids interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckInstancesExisted", reflect.TypeOf((*MockStore)(nil).CheckInstancesExisted), ids) +} + +// GetInstancesBrief mocks base method +func (m *MockStore) GetInstancesBrief(ids map[string]bool) (map[string]*model.Instance, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetInstancesBrief", ids) + ret0, _ := ret[0].(map[string]*model.Instance) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetInstancesBrief indicates an expected call of GetInstancesBrief +func (mr *MockStoreMockRecorder) GetInstancesBrief(ids interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInstancesBrief", reflect.TypeOf((*MockStore)(nil).GetInstancesBrief), ids) +} + +// GetInstance mocks base method +func (m *MockStore) GetInstance(instanceID string) (*model.Instance, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetInstance", instanceID) + ret0, _ := ret[0].(*model.Instance) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetInstance indicates an expected call of GetInstance +func (mr *MockStoreMockRecorder) GetInstance(instanceID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInstance", reflect.TypeOf((*MockStore)(nil).GetInstance), instanceID) +} + +// GetInstancesCount mocks base method +func (m *MockStore) GetInstancesCount() (uint32, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetInstancesCount") + ret0, _ := ret[0].(uint32) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetInstancesCount indicates an expected call of GetInstancesCount +func (mr *MockStoreMockRecorder) GetInstancesCount() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInstancesCount", reflect.TypeOf((*MockStore)(nil).GetInstancesCount)) +} + +// GetInstancesMainByService mocks base method +func (m *MockStore) GetInstancesMainByService(serviceID, host string) ([]*model.Instance, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetInstancesMainByService", serviceID, host) + ret0, _ := ret[0].([]*model.Instance) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetInstancesMainByService indicates an expected call of GetInstancesMainByService +func (mr *MockStoreMockRecorder) GetInstancesMainByService(serviceID, host interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInstancesMainByService", reflect.TypeOf((*MockStore)(nil).GetInstancesMainByService), serviceID, host) +} + +// GetExpandInstances mocks base method +func (m *MockStore) GetExpandInstances(filter, metaFilter map[string]string, offset, limit uint32) (uint32, []*model.Instance, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetExpandInstances", filter, metaFilter, offset, limit) + ret0, _ := ret[0].(uint32) + ret1, _ := ret[1].([]*model.Instance) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetExpandInstances indicates an expected call of GetExpandInstances +func (mr *MockStoreMockRecorder) GetExpandInstances(filter, metaFilter, offset, limit interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetExpandInstances", reflect.TypeOf((*MockStore)(nil).GetExpandInstances), filter, metaFilter, offset, limit) +} + +// GetMoreInstances mocks base method +func (m *MockStore) GetMoreInstances(mtime time.Time, firstUpdate, needMeta bool, serviceID []string) (map[string]*model.Instance, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMoreInstances", mtime, firstUpdate, needMeta, serviceID) + ret0, _ := ret[0].(map[string]*model.Instance) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMoreInstances indicates an expected call of GetMoreInstances +func (mr *MockStoreMockRecorder) GetMoreInstances(mtime, firstUpdate, needMeta, serviceID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMoreInstances", reflect.TypeOf((*MockStore)(nil).GetMoreInstances), mtime, firstUpdate, needMeta, serviceID) +} + +// SetInstanceHealthStatus mocks base method +func (m *MockStore) SetInstanceHealthStatus(instanceID string, flag int, revision string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetInstanceHealthStatus", instanceID, flag, revision) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetInstanceHealthStatus indicates an expected call of SetInstanceHealthStatus +func (mr *MockStoreMockRecorder) SetInstanceHealthStatus(instanceID, flag, revision interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetInstanceHealthStatus", reflect.TypeOf((*MockStore)(nil).SetInstanceHealthStatus), instanceID, flag, revision) +} + +// BatchSetInstanceIsolate mocks base method +func (m *MockStore) BatchSetInstanceIsolate(ids []interface{}, isolate int, revision string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BatchSetInstanceIsolate", ids, isolate, revision) + ret0, _ := ret[0].(error) + return ret0 +} + +// BatchSetInstanceIsolate indicates an expected call of BatchSetInstanceIsolate +func (mr *MockStoreMockRecorder) BatchSetInstanceIsolate(ids, isolate, revision interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BatchSetInstanceIsolate", reflect.TypeOf((*MockStore)(nil).BatchSetInstanceIsolate), ids, isolate, revision) +} + +// CreateRoutingConfig mocks base method +func (m *MockStore) CreateRoutingConfig(conf *model.RoutingConfig) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateRoutingConfig", conf) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateRoutingConfig indicates an expected call of CreateRoutingConfig +func (mr *MockStoreMockRecorder) CreateRoutingConfig(conf interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateRoutingConfig", reflect.TypeOf((*MockStore)(nil).CreateRoutingConfig), conf) +} + +// UpdateRoutingConfig mocks base method +func (m *MockStore) UpdateRoutingConfig(conf *model.RoutingConfig) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateRoutingConfig", conf) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateRoutingConfig indicates an expected call of UpdateRoutingConfig +func (mr *MockStoreMockRecorder) UpdateRoutingConfig(conf interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateRoutingConfig", reflect.TypeOf((*MockStore)(nil).UpdateRoutingConfig), conf) +} + +// DeleteRoutingConfig mocks base method +func (m *MockStore) DeleteRoutingConfig(serviceID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteRoutingConfig", serviceID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteRoutingConfig indicates an expected call of DeleteRoutingConfig +func (mr *MockStoreMockRecorder) DeleteRoutingConfig(serviceID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteRoutingConfig", reflect.TypeOf((*MockStore)(nil).DeleteRoutingConfig), serviceID) +} + +// GetRoutingConfigsForCache mocks base method +func (m *MockStore) GetRoutingConfigsForCache(mtime time.Time, firstUpdate bool) ([]*model.RoutingConfig, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRoutingConfigsForCache", mtime, firstUpdate) + ret0, _ := ret[0].([]*model.RoutingConfig) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRoutingConfigsForCache indicates an expected call of GetRoutingConfigsForCache +func (mr *MockStoreMockRecorder) GetRoutingConfigsForCache(mtime, firstUpdate interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRoutingConfigsForCache", reflect.TypeOf((*MockStore)(nil).GetRoutingConfigsForCache), mtime, firstUpdate) +} + +// GetRoutingConfigWithService mocks base method +func (m *MockStore) GetRoutingConfigWithService(name, namespace string) (*model.RoutingConfig, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRoutingConfigWithService", name, namespace) + ret0, _ := ret[0].(*model.RoutingConfig) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRoutingConfigWithService indicates an expected call of GetRoutingConfigWithService +func (mr *MockStoreMockRecorder) GetRoutingConfigWithService(name, namespace interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRoutingConfigWithService", reflect.TypeOf((*MockStore)(nil).GetRoutingConfigWithService), name, namespace) +} + +// GetRoutingConfigWithID mocks base method +func (m *MockStore) GetRoutingConfigWithID(id string) (*model.RoutingConfig, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRoutingConfigWithID", id) + ret0, _ := ret[0].(*model.RoutingConfig) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRoutingConfigWithID indicates an expected call of GetRoutingConfigWithID +func (mr *MockStoreMockRecorder) GetRoutingConfigWithID(id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRoutingConfigWithID", reflect.TypeOf((*MockStore)(nil).GetRoutingConfigWithID), id) +} + +// GetRoutingConfigs mocks base method +func (m *MockStore) GetRoutingConfigs(filter map[string]string, offset, limit uint32) (uint32, []*model.ExtendRoutingConfig, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRoutingConfigs", filter, offset, limit) + ret0, _ := ret[0].(uint32) + ret1, _ := ret[1].([]*model.ExtendRoutingConfig) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetRoutingConfigs indicates an expected call of GetRoutingConfigs +func (mr *MockStoreMockRecorder) GetRoutingConfigs(filter, offset, limit interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRoutingConfigs", reflect.TypeOf((*MockStore)(nil).GetRoutingConfigs), filter, offset, limit) +} + +// GetL5Extend mocks base method +func (m *MockStore) GetL5Extend(serviceID string) (map[string]interface{}, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetL5Extend", serviceID) + ret0, _ := ret[0].(map[string]interface{}) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetL5Extend indicates an expected call of GetL5Extend +func (mr *MockStoreMockRecorder) GetL5Extend(serviceID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetL5Extend", reflect.TypeOf((*MockStore)(nil).GetL5Extend), serviceID) +} + +// SetL5Extend mocks base method +func (m *MockStore) SetL5Extend(serviceID string, meta map[string]interface{}) (map[string]interface{}, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetL5Extend", serviceID, meta) + ret0, _ := ret[0].(map[string]interface{}) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SetL5Extend indicates an expected call of SetL5Extend +func (mr *MockStoreMockRecorder) SetL5Extend(serviceID, meta interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetL5Extend", reflect.TypeOf((*MockStore)(nil).SetL5Extend), serviceID, meta) +} + +// GenNextL5Sid mocks base method +func (m *MockStore) GenNextL5Sid(layoutID uint32) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GenNextL5Sid", layoutID) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GenNextL5Sid indicates an expected call of GenNextL5Sid +func (mr *MockStoreMockRecorder) GenNextL5Sid(layoutID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GenNextL5Sid", reflect.TypeOf((*MockStore)(nil).GenNextL5Sid), layoutID) +} + +// GetMoreL5Extend mocks base method +func (m *MockStore) GetMoreL5Extend(mtime time.Time) (map[string]map[string]interface{}, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMoreL5Extend", mtime) + ret0, _ := ret[0].(map[string]map[string]interface{}) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMoreL5Extend indicates an expected call of GetMoreL5Extend +func (mr *MockStoreMockRecorder) GetMoreL5Extend(mtime interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMoreL5Extend", reflect.TypeOf((*MockStore)(nil).GetMoreL5Extend), mtime) +} + +// GetMoreL5Routes mocks base method +func (m *MockStore) GetMoreL5Routes(flow uint32) ([]*model.Route, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMoreL5Routes", flow) + ret0, _ := ret[0].([]*model.Route) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMoreL5Routes indicates an expected call of GetMoreL5Routes +func (mr *MockStoreMockRecorder) GetMoreL5Routes(flow interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMoreL5Routes", reflect.TypeOf((*MockStore)(nil).GetMoreL5Routes), flow) +} + +// GetMoreL5Policies mocks base method +func (m *MockStore) GetMoreL5Policies(flow uint32) ([]*model.Policy, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMoreL5Policies", flow) + ret0, _ := ret[0].([]*model.Policy) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMoreL5Policies indicates an expected call of GetMoreL5Policies +func (mr *MockStoreMockRecorder) GetMoreL5Policies(flow interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMoreL5Policies", reflect.TypeOf((*MockStore)(nil).GetMoreL5Policies), flow) +} + +// GetMoreL5Sections mocks base method +func (m *MockStore) GetMoreL5Sections(flow uint32) ([]*model.Section, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMoreL5Sections", flow) + ret0, _ := ret[0].([]*model.Section) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMoreL5Sections indicates an expected call of GetMoreL5Sections +func (mr *MockStoreMockRecorder) GetMoreL5Sections(flow interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMoreL5Sections", reflect.TypeOf((*MockStore)(nil).GetMoreL5Sections), flow) +} + +// GetMoreL5IPConfigs mocks base method +func (m *MockStore) GetMoreL5IPConfigs(flow uint32) ([]*model.IPConfig, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMoreL5IPConfigs", flow) + ret0, _ := ret[0].([]*model.IPConfig) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMoreL5IPConfigs indicates an expected call of GetMoreL5IPConfigs +func (mr *MockStoreMockRecorder) GetMoreL5IPConfigs(flow interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMoreL5IPConfigs", reflect.TypeOf((*MockStore)(nil).GetMoreL5IPConfigs), flow) +} + +// CreateRateLimit mocks base method +func (m *MockStore) CreateRateLimit(limiting *model.RateLimit) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateRateLimit", limiting) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateRateLimit indicates an expected call of CreateRateLimit +func (mr *MockStoreMockRecorder) CreateRateLimit(limiting interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateRateLimit", reflect.TypeOf((*MockStore)(nil).CreateRateLimit), limiting) +} + +// UpdateRateLimit mocks base method +func (m *MockStore) UpdateRateLimit(limiting *model.RateLimit) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateRateLimit", limiting) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateRateLimit indicates an expected call of UpdateRateLimit +func (mr *MockStoreMockRecorder) UpdateRateLimit(limiting interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateRateLimit", reflect.TypeOf((*MockStore)(nil).UpdateRateLimit), limiting) +} + +// DeleteRateLimit mocks base method +func (m *MockStore) DeleteRateLimit(limiting *model.RateLimit) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteRateLimit", limiting) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteRateLimit indicates an expected call of DeleteRateLimit +func (mr *MockStoreMockRecorder) DeleteRateLimit(limiting interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteRateLimit", reflect.TypeOf((*MockStore)(nil).DeleteRateLimit), limiting) +} + +// GetExtendRateLimits mocks base method +func (m *MockStore) GetExtendRateLimits(query map[string]string, offset, limit uint32) (uint32, []*model.ExtendRateLimit, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetExtendRateLimits", query, offset, limit) + ret0, _ := ret[0].(uint32) + ret1, _ := ret[1].([]*model.ExtendRateLimit) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetExtendRateLimits indicates an expected call of GetExtendRateLimits +func (mr *MockStoreMockRecorder) GetExtendRateLimits(query, offset, limit interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetExtendRateLimits", reflect.TypeOf((*MockStore)(nil).GetExtendRateLimits), query, offset, limit) +} + +// GetRateLimitWithID mocks base method +func (m *MockStore) GetRateLimitWithID(id string) (*model.RateLimit, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRateLimitWithID", id) + ret0, _ := ret[0].(*model.RateLimit) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRateLimitWithID indicates an expected call of GetRateLimitWithID +func (mr *MockStoreMockRecorder) GetRateLimitWithID(id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRateLimitWithID", reflect.TypeOf((*MockStore)(nil).GetRateLimitWithID), id) +} + +// GetRateLimitsForCache mocks base method +func (m *MockStore) GetRateLimitsForCache(mtime time.Time, firstUpdate bool) ([]*model.RateLimit, []*model.RateLimitRevision, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRateLimitsForCache", mtime, firstUpdate) + ret0, _ := ret[0].([]*model.RateLimit) + ret1, _ := ret[1].([]*model.RateLimitRevision) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetRateLimitsForCache indicates an expected call of GetRateLimitsForCache +func (mr *MockStoreMockRecorder) GetRateLimitsForCache(mtime, firstUpdate interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRateLimitsForCache", reflect.TypeOf((*MockStore)(nil).GetRateLimitsForCache), mtime, firstUpdate) +} + +// CreateCircuitBreaker mocks base method +func (m *MockStore) CreateCircuitBreaker(circuitBreaker *model.CircuitBreaker) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateCircuitBreaker", circuitBreaker) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateCircuitBreaker indicates an expected call of CreateCircuitBreaker +func (mr *MockStoreMockRecorder) CreateCircuitBreaker(circuitBreaker interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateCircuitBreaker", reflect.TypeOf((*MockStore)(nil).CreateCircuitBreaker), circuitBreaker) +} + +// TagCircuitBreaker mocks base method +func (m *MockStore) TagCircuitBreaker(circuitBreaker *model.CircuitBreaker) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TagCircuitBreaker", circuitBreaker) + ret0, _ := ret[0].(error) + return ret0 +} + +// TagCircuitBreaker indicates an expected call of TagCircuitBreaker +func (mr *MockStoreMockRecorder) TagCircuitBreaker(circuitBreaker interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TagCircuitBreaker", reflect.TypeOf((*MockStore)(nil).TagCircuitBreaker), circuitBreaker) +} + +// ReleaseCircuitBreaker mocks base method +func (m *MockStore) ReleaseCircuitBreaker(circuitBreakerRelation *model.CircuitBreakerRelation) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ReleaseCircuitBreaker", circuitBreakerRelation) + ret0, _ := ret[0].(error) + return ret0 +} + +// ReleaseCircuitBreaker indicates an expected call of ReleaseCircuitBreaker +func (mr *MockStoreMockRecorder) ReleaseCircuitBreaker(circuitBreakerRelation interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReleaseCircuitBreaker", reflect.TypeOf((*MockStore)(nil).ReleaseCircuitBreaker), circuitBreakerRelation) +} + +// UnbindCircuitBreaker mocks base method +func (m *MockStore) UnbindCircuitBreaker(serviceID, ruleID, ruleVersion string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UnbindCircuitBreaker", serviceID, ruleID, ruleVersion) + ret0, _ := ret[0].(error) + return ret0 +} + +// UnbindCircuitBreaker indicates an expected call of UnbindCircuitBreaker +func (mr *MockStoreMockRecorder) UnbindCircuitBreaker(serviceID, ruleID, ruleVersion interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnbindCircuitBreaker", reflect.TypeOf((*MockStore)(nil).UnbindCircuitBreaker), serviceID, ruleID, ruleVersion) +} + +// DeleteTagCircuitBreaker mocks base method +func (m *MockStore) DeleteTagCircuitBreaker(id, version string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteTagCircuitBreaker", id, version) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteTagCircuitBreaker indicates an expected call of DeleteTagCircuitBreaker +func (mr *MockStoreMockRecorder) DeleteTagCircuitBreaker(id, version interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteTagCircuitBreaker", reflect.TypeOf((*MockStore)(nil).DeleteTagCircuitBreaker), id, version) +} + +// DeleteMasterCircuitBreaker mocks base method +func (m *MockStore) DeleteMasterCircuitBreaker(id string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteMasterCircuitBreaker", id) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteMasterCircuitBreaker indicates an expected call of DeleteMasterCircuitBreaker +func (mr *MockStoreMockRecorder) DeleteMasterCircuitBreaker(id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteMasterCircuitBreaker", reflect.TypeOf((*MockStore)(nil).DeleteMasterCircuitBreaker), id) +} + +// UpdateCircuitBreaker mocks base method +func (m *MockStore) UpdateCircuitBreaker(circuitBraker *model.CircuitBreaker) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateCircuitBreaker", circuitBraker) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateCircuitBreaker indicates an expected call of UpdateCircuitBreaker +func (mr *MockStoreMockRecorder) UpdateCircuitBreaker(circuitBraker interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateCircuitBreaker", reflect.TypeOf((*MockStore)(nil).UpdateCircuitBreaker), circuitBraker) +} + +// GetCircuitBreaker mocks base method +func (m *MockStore) GetCircuitBreaker(id, version string) (*model.CircuitBreaker, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCircuitBreaker", id, version) + ret0, _ := ret[0].(*model.CircuitBreaker) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCircuitBreaker indicates an expected call of GetCircuitBreaker +func (mr *MockStoreMockRecorder) GetCircuitBreaker(id, version interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCircuitBreaker", reflect.TypeOf((*MockStore)(nil).GetCircuitBreaker), id, version) +} + +// GetCircuitBreakerVersions mocks base method +func (m *MockStore) GetCircuitBreakerVersions(id string) ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCircuitBreakerVersions", id) + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCircuitBreakerVersions indicates an expected call of GetCircuitBreakerVersions +func (mr *MockStoreMockRecorder) GetCircuitBreakerVersions(id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCircuitBreakerVersions", reflect.TypeOf((*MockStore)(nil).GetCircuitBreakerVersions), id) +} + +// GetCircuitBreakerMasterRelation mocks base method +func (m *MockStore) GetCircuitBreakerMasterRelation(ruleID string) ([]*model.CircuitBreakerRelation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCircuitBreakerMasterRelation", ruleID) + ret0, _ := ret[0].([]*model.CircuitBreakerRelation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCircuitBreakerMasterRelation indicates an expected call of GetCircuitBreakerMasterRelation +func (mr *MockStoreMockRecorder) GetCircuitBreakerMasterRelation(ruleID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCircuitBreakerMasterRelation", reflect.TypeOf((*MockStore)(nil).GetCircuitBreakerMasterRelation), ruleID) +} + +// GetCircuitBreakerRelation mocks base method +func (m *MockStore) GetCircuitBreakerRelation(ruleID, ruleVersion string) ([]*model.CircuitBreakerRelation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCircuitBreakerRelation", ruleID, ruleVersion) + ret0, _ := ret[0].([]*model.CircuitBreakerRelation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCircuitBreakerRelation indicates an expected call of GetCircuitBreakerRelation +func (mr *MockStoreMockRecorder) GetCircuitBreakerRelation(ruleID, ruleVersion interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCircuitBreakerRelation", reflect.TypeOf((*MockStore)(nil).GetCircuitBreakerRelation), ruleID, ruleVersion) +} + +// GetCircuitBreakerForCache mocks base method +func (m *MockStore) GetCircuitBreakerForCache(mtime time.Time, firstUpdate bool) ([]*model.ServiceWithCircuitBreaker, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCircuitBreakerForCache", mtime, firstUpdate) + ret0, _ := ret[0].([]*model.ServiceWithCircuitBreaker) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCircuitBreakerForCache indicates an expected call of GetCircuitBreakerForCache +func (mr *MockStoreMockRecorder) GetCircuitBreakerForCache(mtime, firstUpdate interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCircuitBreakerForCache", reflect.TypeOf((*MockStore)(nil).GetCircuitBreakerForCache), mtime, firstUpdate) +} + +// ListMasterCircuitBreakers mocks base method +func (m *MockStore) ListMasterCircuitBreakers(filters map[string]string, offset, limit uint32) (*model.CircuitBreakerDetail, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListMasterCircuitBreakers", filters, offset, limit) + ret0, _ := ret[0].(*model.CircuitBreakerDetail) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListMasterCircuitBreakers indicates an expected call of ListMasterCircuitBreakers +func (mr *MockStoreMockRecorder) ListMasterCircuitBreakers(filters, offset, limit interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListMasterCircuitBreakers", reflect.TypeOf((*MockStore)(nil).ListMasterCircuitBreakers), filters, offset, limit) +} + +// ListReleaseCircuitBreakers mocks base method +func (m *MockStore) ListReleaseCircuitBreakers(filters map[string]string, offset, limit uint32) (*model.CircuitBreakerDetail, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListReleaseCircuitBreakers", filters, offset, limit) + ret0, _ := ret[0].(*model.CircuitBreakerDetail) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListReleaseCircuitBreakers indicates an expected call of ListReleaseCircuitBreakers +func (mr *MockStoreMockRecorder) ListReleaseCircuitBreakers(filters, offset, limit interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListReleaseCircuitBreakers", reflect.TypeOf((*MockStore)(nil).ListReleaseCircuitBreakers), filters, offset, limit) +} + +// GetCircuitBreakersByService mocks base method +func (m *MockStore) GetCircuitBreakersByService(name, namespace string) (*model.CircuitBreaker, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCircuitBreakersByService", name, namespace) + ret0, _ := ret[0].(*model.CircuitBreaker) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCircuitBreakersByService indicates an expected call of GetCircuitBreakersByService +func (mr *MockStoreMockRecorder) GetCircuitBreakersByService(name, namespace interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCircuitBreakersByService", reflect.TypeOf((*MockStore)(nil).GetCircuitBreakersByService), name, namespace) +} + +// CreatePlatform mocks base method +func (m *MockStore) CreatePlatform(platform *model.Platform) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreatePlatform", platform) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreatePlatform indicates an expected call of CreatePlatform +func (mr *MockStoreMockRecorder) CreatePlatform(platform interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatePlatform", reflect.TypeOf((*MockStore)(nil).CreatePlatform), platform) +} + +// UpdatePlatform mocks base method +func (m *MockStore) UpdatePlatform(platform *model.Platform) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdatePlatform", platform) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdatePlatform indicates an expected call of UpdatePlatform +func (mr *MockStoreMockRecorder) UpdatePlatform(platform interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdatePlatform", reflect.TypeOf((*MockStore)(nil).UpdatePlatform), platform) +} + +// DeletePlatform mocks base method +func (m *MockStore) DeletePlatform(id string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeletePlatform", id) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeletePlatform indicates an expected call of DeletePlatform +func (mr *MockStoreMockRecorder) DeletePlatform(id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePlatform", reflect.TypeOf((*MockStore)(nil).DeletePlatform), id) +} + +// GetPlatformById mocks base method +func (m *MockStore) GetPlatformById(id string) (*model.Platform, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPlatformById", id) + ret0, _ := ret[0].(*model.Platform) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetPlatformById indicates an expected call of GetPlatformById +func (mr *MockStoreMockRecorder) GetPlatformById(id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPlatformById", reflect.TypeOf((*MockStore)(nil).GetPlatformById), id) +} + +// GetPlatforms mocks base method +func (m *MockStore) GetPlatforms(query map[string]string, offset, limit uint32) (uint32, []*model.Platform, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPlatforms", query, offset, limit) + ret0, _ := ret[0].(uint32) + ret1, _ := ret[1].([]*model.Platform) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetPlatforms indicates an expected call of GetPlatforms +func (mr *MockStoreMockRecorder) GetPlatforms(query, offset, limit interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPlatforms", reflect.TypeOf((*MockStore)(nil).GetPlatforms), query, offset, limit) +} + +// MockNamespaceStore is a mock of NamespaceStore interface +type MockNamespaceStore struct { + ctrl *gomock.Controller + recorder *MockNamespaceStoreMockRecorder +} + +// MockNamespaceStoreMockRecorder is the mock recorder for MockNamespaceStore +type MockNamespaceStoreMockRecorder struct { + mock *MockNamespaceStore +} + +// NewMockNamespaceStore creates a new mock instance +func NewMockNamespaceStore(ctrl *gomock.Controller) *MockNamespaceStore { + mock := &MockNamespaceStore{ctrl: ctrl} + mock.recorder = &MockNamespaceStoreMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockNamespaceStore) EXPECT() *MockNamespaceStoreMockRecorder { + return m.recorder +} + +// AddNamespace mocks base method +func (m *MockNamespaceStore) AddNamespace(namespace *model.Namespace) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddNamespace", namespace) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddNamespace indicates an expected call of AddNamespace +func (mr *MockNamespaceStoreMockRecorder) AddNamespace(namespace interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddNamespace", reflect.TypeOf((*MockNamespaceStore)(nil).AddNamespace), namespace) +} + +// UpdateNamespace mocks base method +func (m *MockNamespaceStore) UpdateNamespace(namespace *model.Namespace) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateNamespace", namespace) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateNamespace indicates an expected call of UpdateNamespace +func (mr *MockNamespaceStoreMockRecorder) UpdateNamespace(namespace interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateNamespace", reflect.TypeOf((*MockNamespaceStore)(nil).UpdateNamespace), namespace) +} + +// UpdateNamespaceToken mocks base method +func (m *MockNamespaceStore) UpdateNamespaceToken(name, token string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateNamespaceToken", name, token) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateNamespaceToken indicates an expected call of UpdateNamespaceToken +func (mr *MockNamespaceStoreMockRecorder) UpdateNamespaceToken(name, token interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateNamespaceToken", reflect.TypeOf((*MockNamespaceStore)(nil).UpdateNamespaceToken), name, token) +} + +// ListNamespaces mocks base method +func (m *MockNamespaceStore) ListNamespaces(owner string) ([]*model.Namespace, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListNamespaces", owner) + ret0, _ := ret[0].([]*model.Namespace) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListNamespaces indicates an expected call of ListNamespaces +func (mr *MockNamespaceStoreMockRecorder) ListNamespaces(owner interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListNamespaces", reflect.TypeOf((*MockNamespaceStore)(nil).ListNamespaces), owner) +} + +// GetNamespace mocks base method +func (m *MockNamespaceStore) GetNamespace(name string) (*model.Namespace, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetNamespace", name) + ret0, _ := ret[0].(*model.Namespace) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetNamespace indicates an expected call of GetNamespace +func (mr *MockNamespaceStoreMockRecorder) GetNamespace(name interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNamespace", reflect.TypeOf((*MockNamespaceStore)(nil).GetNamespace), name) +} + +// GetNamespaces mocks base method +func (m *MockNamespaceStore) GetNamespaces(filter map[string][]string, offset, limit int) ([]*model.Namespace, uint32, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetNamespaces", filter, offset, limit) + ret0, _ := ret[0].([]*model.Namespace) + ret1, _ := ret[1].(uint32) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetNamespaces indicates an expected call of GetNamespaces +func (mr *MockNamespaceStoreMockRecorder) GetNamespaces(filter, offset, limit interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNamespaces", reflect.TypeOf((*MockNamespaceStore)(nil).GetNamespaces), filter, offset, limit) +} + +// GetMoreNamespaces mocks base method +func (m *MockNamespaceStore) GetMoreNamespaces(mtime time.Time) ([]*model.Namespace, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMoreNamespaces", mtime) + ret0, _ := ret[0].([]*model.Namespace) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMoreNamespaces indicates an expected call of GetMoreNamespaces +func (mr *MockNamespaceStoreMockRecorder) GetMoreNamespaces(mtime interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMoreNamespaces", reflect.TypeOf((*MockNamespaceStore)(nil).GetMoreNamespaces), mtime) +} + +// MockBusinessStore is a mock of BusinessStore interface +type MockBusinessStore struct { + ctrl *gomock.Controller + recorder *MockBusinessStoreMockRecorder +} + +// MockBusinessStoreMockRecorder is the mock recorder for MockBusinessStore +type MockBusinessStoreMockRecorder struct { + mock *MockBusinessStore +} + +// NewMockBusinessStore creates a new mock instance +func NewMockBusinessStore(ctrl *gomock.Controller) *MockBusinessStore { + mock := &MockBusinessStore{ctrl: ctrl} + mock.recorder = &MockBusinessStoreMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockBusinessStore) EXPECT() *MockBusinessStoreMockRecorder { + return m.recorder +} + +// AddBusiness mocks base method +func (m *MockBusinessStore) AddBusiness(business *model.Business) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddBusiness", business) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddBusiness indicates an expected call of AddBusiness +func (mr *MockBusinessStoreMockRecorder) AddBusiness(business interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddBusiness", reflect.TypeOf((*MockBusinessStore)(nil).AddBusiness), business) +} + +// DeleteBusiness mocks base method +func (m *MockBusinessStore) DeleteBusiness(bid string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteBusiness", bid) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteBusiness indicates an expected call of DeleteBusiness +func (mr *MockBusinessStoreMockRecorder) DeleteBusiness(bid interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteBusiness", reflect.TypeOf((*MockBusinessStore)(nil).DeleteBusiness), bid) +} + +// UpdateBusiness mocks base method +func (m *MockBusinessStore) UpdateBusiness(business *model.Business) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateBusiness", business) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateBusiness indicates an expected call of UpdateBusiness +func (mr *MockBusinessStoreMockRecorder) UpdateBusiness(business interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateBusiness", reflect.TypeOf((*MockBusinessStore)(nil).UpdateBusiness), business) +} + +// UpdateBusinessToken mocks base method +func (m *MockBusinessStore) UpdateBusinessToken(bid, token string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateBusinessToken", bid, token) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateBusinessToken indicates an expected call of UpdateBusinessToken +func (mr *MockBusinessStoreMockRecorder) UpdateBusinessToken(bid, token interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateBusinessToken", reflect.TypeOf((*MockBusinessStore)(nil).UpdateBusinessToken), bid, token) +} + +// ListBusiness mocks base method +func (m *MockBusinessStore) ListBusiness(owner string) ([]*model.Business, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListBusiness", owner) + ret0, _ := ret[0].([]*model.Business) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListBusiness indicates an expected call of ListBusiness +func (mr *MockBusinessStoreMockRecorder) ListBusiness(owner interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListBusiness", reflect.TypeOf((*MockBusinessStore)(nil).ListBusiness), owner) +} + +// GetBusinessByID mocks base method +func (m *MockBusinessStore) GetBusinessByID(id string) (*model.Business, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBusinessByID", id) + ret0, _ := ret[0].(*model.Business) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBusinessByID indicates an expected call of GetBusinessByID +func (mr *MockBusinessStoreMockRecorder) GetBusinessByID(id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBusinessByID", reflect.TypeOf((*MockBusinessStore)(nil).GetBusinessByID), id) +} + +// GetMoreBusiness mocks base method +func (m *MockBusinessStore) GetMoreBusiness(mtime time.Time) ([]*model.Business, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMoreBusiness", mtime) + ret0, _ := ret[0].([]*model.Business) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMoreBusiness indicates an expected call of GetMoreBusiness +func (mr *MockBusinessStoreMockRecorder) GetMoreBusiness(mtime interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMoreBusiness", reflect.TypeOf((*MockBusinessStore)(nil).GetMoreBusiness), mtime) +} + +// MockServiceStore is a mock of ServiceStore interface +type MockServiceStore struct { + ctrl *gomock.Controller + recorder *MockServiceStoreMockRecorder +} + +// MockServiceStoreMockRecorder is the mock recorder for MockServiceStore +type MockServiceStoreMockRecorder struct { + mock *MockServiceStore +} + +// NewMockServiceStore creates a new mock instance +func NewMockServiceStore(ctrl *gomock.Controller) *MockServiceStore { + mock := &MockServiceStore{ctrl: ctrl} + mock.recorder = &MockServiceStoreMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockServiceStore) EXPECT() *MockServiceStoreMockRecorder { + return m.recorder +} + +// AddService mocks base method +func (m *MockServiceStore) AddService(service *model.Service) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddService", service) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddService indicates an expected call of AddService +func (mr *MockServiceStoreMockRecorder) AddService(service interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddService", reflect.TypeOf((*MockServiceStore)(nil).AddService), service) +} + +// DeleteService mocks base method +func (m *MockServiceStore) DeleteService(id, serviceName, namespaceName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteService", id, serviceName, namespaceName) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteService indicates an expected call of DeleteService +func (mr *MockServiceStoreMockRecorder) DeleteService(id, serviceName, namespaceName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteService", reflect.TypeOf((*MockServiceStore)(nil).DeleteService), id, serviceName, namespaceName) +} + +// DeleteServiceAlias mocks base method +func (m *MockServiceStore) DeleteServiceAlias(name, namespace string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteServiceAlias", name, namespace) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteServiceAlias indicates an expected call of DeleteServiceAlias +func (mr *MockServiceStoreMockRecorder) DeleteServiceAlias(name, namespace interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteServiceAlias", reflect.TypeOf((*MockServiceStore)(nil).DeleteServiceAlias), name, namespace) +} + +// UpdateServiceAlias mocks base method +func (m *MockServiceStore) UpdateServiceAlias(alias *model.Service, needUpdateOwner bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateServiceAlias", alias, needUpdateOwner) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateServiceAlias indicates an expected call of UpdateServiceAlias +func (mr *MockServiceStoreMockRecorder) UpdateServiceAlias(alias, needUpdateOwner interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateServiceAlias", reflect.TypeOf((*MockServiceStore)(nil).UpdateServiceAlias), alias, needUpdateOwner) +} + +// UpdateService mocks base method +func (m *MockServiceStore) UpdateService(service *model.Service, needUpdateOwner bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateService", service, needUpdateOwner) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateService indicates an expected call of UpdateService +func (mr *MockServiceStoreMockRecorder) UpdateService(service, needUpdateOwner interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateService", reflect.TypeOf((*MockServiceStore)(nil).UpdateService), service, needUpdateOwner) +} + +// UpdateServiceToken mocks base method +func (m *MockServiceStore) UpdateServiceToken(serviceID, token, revision string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateServiceToken", serviceID, token, revision) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateServiceToken indicates an expected call of UpdateServiceToken +func (mr *MockServiceStoreMockRecorder) UpdateServiceToken(serviceID, token, revision interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateServiceToken", reflect.TypeOf((*MockServiceStore)(nil).UpdateServiceToken), serviceID, token, revision) +} + +// GetSourceServiceToken mocks base method +func (m *MockServiceStore) GetSourceServiceToken(name, namespace string) (*model.Service, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSourceServiceToken", name, namespace) + ret0, _ := ret[0].(*model.Service) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSourceServiceToken indicates an expected call of GetSourceServiceToken +func (mr *MockServiceStoreMockRecorder) GetSourceServiceToken(name, namespace interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSourceServiceToken", reflect.TypeOf((*MockServiceStore)(nil).GetSourceServiceToken), name, namespace) +} + +// GetService mocks base method +func (m *MockServiceStore) GetService(name, namespace string) (*model.Service, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetService", name, namespace) + ret0, _ := ret[0].(*model.Service) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetService indicates an expected call of GetService +func (mr *MockServiceStoreMockRecorder) GetService(name, namespace interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetService", reflect.TypeOf((*MockServiceStore)(nil).GetService), name, namespace) +} + +// GetServiceByID mocks base method +func (m *MockServiceStore) GetServiceByID(id string) (*model.Service, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetServiceByID", id) + ret0, _ := ret[0].(*model.Service) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetServiceByID indicates an expected call of GetServiceByID +func (mr *MockServiceStoreMockRecorder) GetServiceByID(id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServiceByID", reflect.TypeOf((*MockServiceStore)(nil).GetServiceByID), id) +} + +// GetServices mocks base method +func (m *MockServiceStore) GetServices(serviceFilters, serviceMetas map[string]string, instanceFilters *store.InstanceArgs, offset, limit uint32) (uint32, []*model.Service, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetServices", serviceFilters, serviceMetas, instanceFilters, offset, limit) + ret0, _ := ret[0].(uint32) + ret1, _ := ret[1].([]*model.Service) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetServices indicates an expected call of GetServices +func (mr *MockServiceStoreMockRecorder) GetServices(serviceFilters, serviceMetas, instanceFilters, offset, limit interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServices", reflect.TypeOf((*MockServiceStore)(nil).GetServices), serviceFilters, serviceMetas, instanceFilters, offset, limit) +} + +// GetServicesCount mocks base method +func (m *MockServiceStore) GetServicesCount() (uint32, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetServicesCount") + ret0, _ := ret[0].(uint32) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetServicesCount indicates an expected call of GetServicesCount +func (mr *MockServiceStoreMockRecorder) GetServicesCount() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServicesCount", reflect.TypeOf((*MockServiceStore)(nil).GetServicesCount)) +} + +// GetMoreServices mocks base method +func (m *MockServiceStore) GetMoreServices(mtime time.Time, firstUpdate, disableBusiness, needMeta bool) (map[string]*model.Service, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMoreServices", mtime, firstUpdate, disableBusiness, needMeta) + ret0, _ := ret[0].(map[string]*model.Service) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMoreServices indicates an expected call of GetMoreServices +func (mr *MockServiceStoreMockRecorder) GetMoreServices(mtime, firstUpdate, disableBusiness, needMeta interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMoreServices", reflect.TypeOf((*MockServiceStore)(nil).GetMoreServices), mtime, firstUpdate, disableBusiness, needMeta) +} + +// GetServiceAliases mocks base method +func (m *MockServiceStore) GetServiceAliases(filter map[string]string, offset, limit uint32) (uint32, []*model.ServiceAlias, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetServiceAliases", filter, offset, limit) + ret0, _ := ret[0].(uint32) + ret1, _ := ret[1].([]*model.ServiceAlias) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetServiceAliases indicates an expected call of GetServiceAliases +func (mr *MockServiceStoreMockRecorder) GetServiceAliases(filter, offset, limit interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServiceAliases", reflect.TypeOf((*MockServiceStore)(nil).GetServiceAliases), filter, offset, limit) +} + +// GetSystemServices mocks base method +func (m *MockServiceStore) GetSystemServices() ([]*model.Service, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSystemServices") + ret0, _ := ret[0].([]*model.Service) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSystemServices indicates an expected call of GetSystemServices +func (mr *MockServiceStoreMockRecorder) GetSystemServices() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSystemServices", reflect.TypeOf((*MockServiceStore)(nil).GetSystemServices)) +} + +// GetServicesBatch mocks base method +func (m *MockServiceStore) GetServicesBatch(services []*model.Service) ([]*model.Service, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetServicesBatch", services) + ret0, _ := ret[0].([]*model.Service) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetServicesBatch indicates an expected call of GetServicesBatch +func (mr *MockServiceStoreMockRecorder) GetServicesBatch(services interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServicesBatch", reflect.TypeOf((*MockServiceStore)(nil).GetServicesBatch), services) +} + +// MockInstanceStore is a mock of InstanceStore interface +type MockInstanceStore struct { + ctrl *gomock.Controller + recorder *MockInstanceStoreMockRecorder +} + +// MockInstanceStoreMockRecorder is the mock recorder for MockInstanceStore +type MockInstanceStoreMockRecorder struct { + mock *MockInstanceStore +} + +// NewMockInstanceStore creates a new mock instance +func NewMockInstanceStore(ctrl *gomock.Controller) *MockInstanceStore { + mock := &MockInstanceStore{ctrl: ctrl} + mock.recorder = &MockInstanceStoreMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockInstanceStore) EXPECT() *MockInstanceStoreMockRecorder { + return m.recorder +} + +// AddInstance mocks base method +func (m *MockInstanceStore) AddInstance(instance *model.Instance) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddInstance", instance) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddInstance indicates an expected call of AddInstance +func (mr *MockInstanceStoreMockRecorder) AddInstance(instance interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddInstance", reflect.TypeOf((*MockInstanceStore)(nil).AddInstance), instance) +} + +// BatchAddInstances mocks base method +func (m *MockInstanceStore) BatchAddInstances(instances []*model.Instance) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BatchAddInstances", instances) + ret0, _ := ret[0].(error) + return ret0 +} + +// BatchAddInstances indicates an expected call of BatchAddInstances +func (mr *MockInstanceStoreMockRecorder) BatchAddInstances(instances interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BatchAddInstances", reflect.TypeOf((*MockInstanceStore)(nil).BatchAddInstances), instances) +} + +// UpdateInstance mocks base method +func (m *MockInstanceStore) UpdateInstance(instance *model.Instance) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateInstance", instance) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateInstance indicates an expected call of UpdateInstance +func (mr *MockInstanceStoreMockRecorder) UpdateInstance(instance interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateInstance", reflect.TypeOf((*MockInstanceStore)(nil).UpdateInstance), instance) +} + +// DeleteInstance mocks base method +func (m *MockInstanceStore) DeleteInstance(instanceID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteInstance", instanceID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteInstance indicates an expected call of DeleteInstance +func (mr *MockInstanceStoreMockRecorder) DeleteInstance(instanceID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteInstance", reflect.TypeOf((*MockInstanceStore)(nil).DeleteInstance), instanceID) +} + +// BatchDeleteInstances mocks base method +func (m *MockInstanceStore) BatchDeleteInstances(ids []interface{}) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BatchDeleteInstances", ids) + ret0, _ := ret[0].(error) + return ret0 +} + +// BatchDeleteInstances indicates an expected call of BatchDeleteInstances +func (mr *MockInstanceStoreMockRecorder) BatchDeleteInstances(ids interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BatchDeleteInstances", reflect.TypeOf((*MockInstanceStore)(nil).BatchDeleteInstances), ids) +} + +// CleanInstance mocks base method +func (m *MockInstanceStore) CleanInstance(instanceID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CleanInstance", instanceID) + ret0, _ := ret[0].(error) + return ret0 +} + +// CleanInstance indicates an expected call of CleanInstance +func (mr *MockInstanceStoreMockRecorder) CleanInstance(instanceID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CleanInstance", reflect.TypeOf((*MockInstanceStore)(nil).CleanInstance), instanceID) +} + +// CheckInstancesExisted mocks base method +func (m *MockInstanceStore) CheckInstancesExisted(ids map[string]bool) (map[string]bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CheckInstancesExisted", ids) + ret0, _ := ret[0].(map[string]bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CheckInstancesExisted indicates an expected call of CheckInstancesExisted +func (mr *MockInstanceStoreMockRecorder) CheckInstancesExisted(ids interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckInstancesExisted", reflect.TypeOf((*MockInstanceStore)(nil).CheckInstancesExisted), ids) +} + +// GetInstancesBrief mocks base method +func (m *MockInstanceStore) GetInstancesBrief(ids map[string]bool) (map[string]*model.Instance, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetInstancesBrief", ids) + ret0, _ := ret[0].(map[string]*model.Instance) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetInstancesBrief indicates an expected call of GetInstancesBrief +func (mr *MockInstanceStoreMockRecorder) GetInstancesBrief(ids interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInstancesBrief", reflect.TypeOf((*MockInstanceStore)(nil).GetInstancesBrief), ids) +} + +// GetInstance mocks base method +func (m *MockInstanceStore) GetInstance(instanceID string) (*model.Instance, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetInstance", instanceID) + ret0, _ := ret[0].(*model.Instance) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetInstance indicates an expected call of GetInstance +func (mr *MockInstanceStoreMockRecorder) GetInstance(instanceID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInstance", reflect.TypeOf((*MockInstanceStore)(nil).GetInstance), instanceID) +} + +// GetInstancesCount mocks base method +func (m *MockInstanceStore) GetInstancesCount() (uint32, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetInstancesCount") + ret0, _ := ret[0].(uint32) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetInstancesCount indicates an expected call of GetInstancesCount +func (mr *MockInstanceStoreMockRecorder) GetInstancesCount() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInstancesCount", reflect.TypeOf((*MockInstanceStore)(nil).GetInstancesCount)) +} + +// GetInstancesMainByService mocks base method +func (m *MockInstanceStore) GetInstancesMainByService(serviceID, host string) ([]*model.Instance, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetInstancesMainByService", serviceID, host) + ret0, _ := ret[0].([]*model.Instance) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetInstancesMainByService indicates an expected call of GetInstancesMainByService +func (mr *MockInstanceStoreMockRecorder) GetInstancesMainByService(serviceID, host interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInstancesMainByService", reflect.TypeOf((*MockInstanceStore)(nil).GetInstancesMainByService), serviceID, host) +} + +// GetExpandInstances mocks base method +func (m *MockInstanceStore) GetExpandInstances(filter, metaFilter map[string]string, offset, limit uint32) (uint32, []*model.Instance, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetExpandInstances", filter, metaFilter, offset, limit) + ret0, _ := ret[0].(uint32) + ret1, _ := ret[1].([]*model.Instance) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetExpandInstances indicates an expected call of GetExpandInstances +func (mr *MockInstanceStoreMockRecorder) GetExpandInstances(filter, metaFilter, offset, limit interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetExpandInstances", reflect.TypeOf((*MockInstanceStore)(nil).GetExpandInstances), filter, metaFilter, offset, limit) +} + +// GetMoreInstances mocks base method +func (m *MockInstanceStore) GetMoreInstances(mtime time.Time, firstUpdate, needMeta bool, serviceID []string) (map[string]*model.Instance, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMoreInstances", mtime, firstUpdate, needMeta, serviceID) + ret0, _ := ret[0].(map[string]*model.Instance) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMoreInstances indicates an expected call of GetMoreInstances +func (mr *MockInstanceStoreMockRecorder) GetMoreInstances(mtime, firstUpdate, needMeta, serviceID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMoreInstances", reflect.TypeOf((*MockInstanceStore)(nil).GetMoreInstances), mtime, firstUpdate, needMeta, serviceID) +} + +// SetInstanceHealthStatus mocks base method +func (m *MockInstanceStore) SetInstanceHealthStatus(instanceID string, flag int, revision string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetInstanceHealthStatus", instanceID, flag, revision) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetInstanceHealthStatus indicates an expected call of SetInstanceHealthStatus +func (mr *MockInstanceStoreMockRecorder) SetInstanceHealthStatus(instanceID, flag, revision interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetInstanceHealthStatus", reflect.TypeOf((*MockInstanceStore)(nil).SetInstanceHealthStatus), instanceID, flag, revision) +} + +// BatchSetInstanceIsolate mocks base method +func (m *MockInstanceStore) BatchSetInstanceIsolate(ids []interface{}, isolate int, revision string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BatchSetInstanceIsolate", ids, isolate, revision) + ret0, _ := ret[0].(error) + return ret0 +} + +// BatchSetInstanceIsolate indicates an expected call of BatchSetInstanceIsolate +func (mr *MockInstanceStoreMockRecorder) BatchSetInstanceIsolate(ids, isolate, revision interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BatchSetInstanceIsolate", reflect.TypeOf((*MockInstanceStore)(nil).BatchSetInstanceIsolate), ids, isolate, revision) +} + +// MockL5Store is a mock of L5Store interface +type MockL5Store struct { + ctrl *gomock.Controller + recorder *MockL5StoreMockRecorder +} + +// MockL5StoreMockRecorder is the mock recorder for MockL5Store +type MockL5StoreMockRecorder struct { + mock *MockL5Store +} + +// NewMockL5Store creates a new mock instance +func NewMockL5Store(ctrl *gomock.Controller) *MockL5Store { + mock := &MockL5Store{ctrl: ctrl} + mock.recorder = &MockL5StoreMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockL5Store) EXPECT() *MockL5StoreMockRecorder { + return m.recorder +} + +// GetL5Extend mocks base method +func (m *MockL5Store) GetL5Extend(serviceID string) (map[string]interface{}, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetL5Extend", serviceID) + ret0, _ := ret[0].(map[string]interface{}) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetL5Extend indicates an expected call of GetL5Extend +func (mr *MockL5StoreMockRecorder) GetL5Extend(serviceID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetL5Extend", reflect.TypeOf((*MockL5Store)(nil).GetL5Extend), serviceID) +} + +// SetL5Extend mocks base method +func (m *MockL5Store) SetL5Extend(serviceID string, meta map[string]interface{}) (map[string]interface{}, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetL5Extend", serviceID, meta) + ret0, _ := ret[0].(map[string]interface{}) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SetL5Extend indicates an expected call of SetL5Extend +func (mr *MockL5StoreMockRecorder) SetL5Extend(serviceID, meta interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetL5Extend", reflect.TypeOf((*MockL5Store)(nil).SetL5Extend), serviceID, meta) +} + +// GenNextL5Sid mocks base method +func (m *MockL5Store) GenNextL5Sid(layoutID uint32) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GenNextL5Sid", layoutID) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GenNextL5Sid indicates an expected call of GenNextL5Sid +func (mr *MockL5StoreMockRecorder) GenNextL5Sid(layoutID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GenNextL5Sid", reflect.TypeOf((*MockL5Store)(nil).GenNextL5Sid), layoutID) +} + +// GetMoreL5Extend mocks base method +func (m *MockL5Store) GetMoreL5Extend(mtime time.Time) (map[string]map[string]interface{}, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMoreL5Extend", mtime) + ret0, _ := ret[0].(map[string]map[string]interface{}) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMoreL5Extend indicates an expected call of GetMoreL5Extend +func (mr *MockL5StoreMockRecorder) GetMoreL5Extend(mtime interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMoreL5Extend", reflect.TypeOf((*MockL5Store)(nil).GetMoreL5Extend), mtime) +} + +// GetMoreL5Routes mocks base method +func (m *MockL5Store) GetMoreL5Routes(flow uint32) ([]*model.Route, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMoreL5Routes", flow) + ret0, _ := ret[0].([]*model.Route) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMoreL5Routes indicates an expected call of GetMoreL5Routes +func (mr *MockL5StoreMockRecorder) GetMoreL5Routes(flow interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMoreL5Routes", reflect.TypeOf((*MockL5Store)(nil).GetMoreL5Routes), flow) +} + +// GetMoreL5Policies mocks base method +func (m *MockL5Store) GetMoreL5Policies(flow uint32) ([]*model.Policy, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMoreL5Policies", flow) + ret0, _ := ret[0].([]*model.Policy) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMoreL5Policies indicates an expected call of GetMoreL5Policies +func (mr *MockL5StoreMockRecorder) GetMoreL5Policies(flow interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMoreL5Policies", reflect.TypeOf((*MockL5Store)(nil).GetMoreL5Policies), flow) +} + +// GetMoreL5Sections mocks base method +func (m *MockL5Store) GetMoreL5Sections(flow uint32) ([]*model.Section, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMoreL5Sections", flow) + ret0, _ := ret[0].([]*model.Section) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMoreL5Sections indicates an expected call of GetMoreL5Sections +func (mr *MockL5StoreMockRecorder) GetMoreL5Sections(flow interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMoreL5Sections", reflect.TypeOf((*MockL5Store)(nil).GetMoreL5Sections), flow) +} + +// GetMoreL5IPConfigs mocks base method +func (m *MockL5Store) GetMoreL5IPConfigs(flow uint32) ([]*model.IPConfig, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMoreL5IPConfigs", flow) + ret0, _ := ret[0].([]*model.IPConfig) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMoreL5IPConfigs indicates an expected call of GetMoreL5IPConfigs +func (mr *MockL5StoreMockRecorder) GetMoreL5IPConfigs(flow interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMoreL5IPConfigs", reflect.TypeOf((*MockL5Store)(nil).GetMoreL5IPConfigs), flow) +} + +// MockRoutingConfigStore is a mock of RoutingConfigStore interface +type MockRoutingConfigStore struct { + ctrl *gomock.Controller + recorder *MockRoutingConfigStoreMockRecorder +} + +// MockRoutingConfigStoreMockRecorder is the mock recorder for MockRoutingConfigStore +type MockRoutingConfigStoreMockRecorder struct { + mock *MockRoutingConfigStore +} + +// NewMockRoutingConfigStore creates a new mock instance +func NewMockRoutingConfigStore(ctrl *gomock.Controller) *MockRoutingConfigStore { + mock := &MockRoutingConfigStore{ctrl: ctrl} + mock.recorder = &MockRoutingConfigStoreMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockRoutingConfigStore) EXPECT() *MockRoutingConfigStoreMockRecorder { + return m.recorder +} + +// CreateRoutingConfig mocks base method +func (m *MockRoutingConfigStore) CreateRoutingConfig(conf *model.RoutingConfig) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateRoutingConfig", conf) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateRoutingConfig indicates an expected call of CreateRoutingConfig +func (mr *MockRoutingConfigStoreMockRecorder) CreateRoutingConfig(conf interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateRoutingConfig", reflect.TypeOf((*MockRoutingConfigStore)(nil).CreateRoutingConfig), conf) +} + +// UpdateRoutingConfig mocks base method +func (m *MockRoutingConfigStore) UpdateRoutingConfig(conf *model.RoutingConfig) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateRoutingConfig", conf) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateRoutingConfig indicates an expected call of UpdateRoutingConfig +func (mr *MockRoutingConfigStoreMockRecorder) UpdateRoutingConfig(conf interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateRoutingConfig", reflect.TypeOf((*MockRoutingConfigStore)(nil).UpdateRoutingConfig), conf) +} + +// DeleteRoutingConfig mocks base method +func (m *MockRoutingConfigStore) DeleteRoutingConfig(serviceID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteRoutingConfig", serviceID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteRoutingConfig indicates an expected call of DeleteRoutingConfig +func (mr *MockRoutingConfigStoreMockRecorder) DeleteRoutingConfig(serviceID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteRoutingConfig", reflect.TypeOf((*MockRoutingConfigStore)(nil).DeleteRoutingConfig), serviceID) +} + +// GetRoutingConfigsForCache mocks base method +func (m *MockRoutingConfigStore) GetRoutingConfigsForCache(mtime time.Time, firstUpdate bool) ([]*model.RoutingConfig, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRoutingConfigsForCache", mtime, firstUpdate) + ret0, _ := ret[0].([]*model.RoutingConfig) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRoutingConfigsForCache indicates an expected call of GetRoutingConfigsForCache +func (mr *MockRoutingConfigStoreMockRecorder) GetRoutingConfigsForCache(mtime, firstUpdate interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRoutingConfigsForCache", reflect.TypeOf((*MockRoutingConfigStore)(nil).GetRoutingConfigsForCache), mtime, firstUpdate) +} + +// GetRoutingConfigWithService mocks base method +func (m *MockRoutingConfigStore) GetRoutingConfigWithService(name, namespace string) (*model.RoutingConfig, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRoutingConfigWithService", name, namespace) + ret0, _ := ret[0].(*model.RoutingConfig) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRoutingConfigWithService indicates an expected call of GetRoutingConfigWithService +func (mr *MockRoutingConfigStoreMockRecorder) GetRoutingConfigWithService(name, namespace interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRoutingConfigWithService", reflect.TypeOf((*MockRoutingConfigStore)(nil).GetRoutingConfigWithService), name, namespace) +} + +// GetRoutingConfigWithID mocks base method +func (m *MockRoutingConfigStore) GetRoutingConfigWithID(id string) (*model.RoutingConfig, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRoutingConfigWithID", id) + ret0, _ := ret[0].(*model.RoutingConfig) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRoutingConfigWithID indicates an expected call of GetRoutingConfigWithID +func (mr *MockRoutingConfigStoreMockRecorder) GetRoutingConfigWithID(id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRoutingConfigWithID", reflect.TypeOf((*MockRoutingConfigStore)(nil).GetRoutingConfigWithID), id) +} + +// GetRoutingConfigs mocks base method +func (m *MockRoutingConfigStore) GetRoutingConfigs(filter map[string]string, offset, limit uint32) (uint32, []*model.ExtendRoutingConfig, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRoutingConfigs", filter, offset, limit) + ret0, _ := ret[0].(uint32) + ret1, _ := ret[1].([]*model.ExtendRoutingConfig) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetRoutingConfigs indicates an expected call of GetRoutingConfigs +func (mr *MockRoutingConfigStoreMockRecorder) GetRoutingConfigs(filter, offset, limit interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRoutingConfigs", reflect.TypeOf((*MockRoutingConfigStore)(nil).GetRoutingConfigs), filter, offset, limit) +} + +// MockRateLimitStore is a mock of RateLimitStore interface +type MockRateLimitStore struct { + ctrl *gomock.Controller + recorder *MockRateLimitStoreMockRecorder +} + +// MockRateLimitStoreMockRecorder is the mock recorder for MockRateLimitStore +type MockRateLimitStoreMockRecorder struct { + mock *MockRateLimitStore +} + +// NewMockRateLimitStore creates a new mock instance +func NewMockRateLimitStore(ctrl *gomock.Controller) *MockRateLimitStore { + mock := &MockRateLimitStore{ctrl: ctrl} + mock.recorder = &MockRateLimitStoreMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockRateLimitStore) EXPECT() *MockRateLimitStoreMockRecorder { + return m.recorder +} + +// CreateRateLimit mocks base method +func (m *MockRateLimitStore) CreateRateLimit(limiting *model.RateLimit) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateRateLimit", limiting) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateRateLimit indicates an expected call of CreateRateLimit +func (mr *MockRateLimitStoreMockRecorder) CreateRateLimit(limiting interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateRateLimit", reflect.TypeOf((*MockRateLimitStore)(nil).CreateRateLimit), limiting) +} + +// UpdateRateLimit mocks base method +func (m *MockRateLimitStore) UpdateRateLimit(limiting *model.RateLimit) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateRateLimit", limiting) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateRateLimit indicates an expected call of UpdateRateLimit +func (mr *MockRateLimitStoreMockRecorder) UpdateRateLimit(limiting interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateRateLimit", reflect.TypeOf((*MockRateLimitStore)(nil).UpdateRateLimit), limiting) +} + +// DeleteRateLimit mocks base method +func (m *MockRateLimitStore) DeleteRateLimit(limiting *model.RateLimit) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteRateLimit", limiting) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteRateLimit indicates an expected call of DeleteRateLimit +func (mr *MockRateLimitStoreMockRecorder) DeleteRateLimit(limiting interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteRateLimit", reflect.TypeOf((*MockRateLimitStore)(nil).DeleteRateLimit), limiting) +} + +// GetExtendRateLimits mocks base method +func (m *MockRateLimitStore) GetExtendRateLimits(query map[string]string, offset, limit uint32) (uint32, []*model.ExtendRateLimit, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetExtendRateLimits", query, offset, limit) + ret0, _ := ret[0].(uint32) + ret1, _ := ret[1].([]*model.ExtendRateLimit) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetExtendRateLimits indicates an expected call of GetExtendRateLimits +func (mr *MockRateLimitStoreMockRecorder) GetExtendRateLimits(query, offset, limit interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetExtendRateLimits", reflect.TypeOf((*MockRateLimitStore)(nil).GetExtendRateLimits), query, offset, limit) +} + +// GetRateLimitWithID mocks base method +func (m *MockRateLimitStore) GetRateLimitWithID(id string) (*model.RateLimit, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRateLimitWithID", id) + ret0, _ := ret[0].(*model.RateLimit) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRateLimitWithID indicates an expected call of GetRateLimitWithID +func (mr *MockRateLimitStoreMockRecorder) GetRateLimitWithID(id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRateLimitWithID", reflect.TypeOf((*MockRateLimitStore)(nil).GetRateLimitWithID), id) +} + +// GetRateLimitsForCache mocks base method +func (m *MockRateLimitStore) GetRateLimitsForCache(mtime time.Time, firstUpdate bool) ([]*model.RateLimit, []*model.RateLimitRevision, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRateLimitsForCache", mtime, firstUpdate) + ret0, _ := ret[0].([]*model.RateLimit) + ret1, _ := ret[1].([]*model.RateLimitRevision) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetRateLimitsForCache indicates an expected call of GetRateLimitsForCache +func (mr *MockRateLimitStoreMockRecorder) GetRateLimitsForCache(mtime, firstUpdate interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRateLimitsForCache", reflect.TypeOf((*MockRateLimitStore)(nil).GetRateLimitsForCache), mtime, firstUpdate) +} + +// MockCircuitBreakerStore is a mock of CircuitBreakerStore interface +type MockCircuitBreakerStore struct { + ctrl *gomock.Controller + recorder *MockCircuitBreakerStoreMockRecorder +} + +// MockCircuitBreakerStoreMockRecorder is the mock recorder for MockCircuitBreakerStore +type MockCircuitBreakerStoreMockRecorder struct { + mock *MockCircuitBreakerStore +} + +// NewMockCircuitBreakerStore creates a new mock instance +func NewMockCircuitBreakerStore(ctrl *gomock.Controller) *MockCircuitBreakerStore { + mock := &MockCircuitBreakerStore{ctrl: ctrl} + mock.recorder = &MockCircuitBreakerStoreMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockCircuitBreakerStore) EXPECT() *MockCircuitBreakerStoreMockRecorder { + return m.recorder +} + +// CreateCircuitBreaker mocks base method +func (m *MockCircuitBreakerStore) CreateCircuitBreaker(circuitBreaker *model.CircuitBreaker) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateCircuitBreaker", circuitBreaker) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateCircuitBreaker indicates an expected call of CreateCircuitBreaker +func (mr *MockCircuitBreakerStoreMockRecorder) CreateCircuitBreaker(circuitBreaker interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateCircuitBreaker", reflect.TypeOf((*MockCircuitBreakerStore)(nil).CreateCircuitBreaker), circuitBreaker) +} + +// TagCircuitBreaker mocks base method +func (m *MockCircuitBreakerStore) TagCircuitBreaker(circuitBreaker *model.CircuitBreaker) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TagCircuitBreaker", circuitBreaker) + ret0, _ := ret[0].(error) + return ret0 +} + +// TagCircuitBreaker indicates an expected call of TagCircuitBreaker +func (mr *MockCircuitBreakerStoreMockRecorder) TagCircuitBreaker(circuitBreaker interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TagCircuitBreaker", reflect.TypeOf((*MockCircuitBreakerStore)(nil).TagCircuitBreaker), circuitBreaker) +} + +// ReleaseCircuitBreaker mocks base method +func (m *MockCircuitBreakerStore) ReleaseCircuitBreaker(circuitBreakerRelation *model.CircuitBreakerRelation) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ReleaseCircuitBreaker", circuitBreakerRelation) + ret0, _ := ret[0].(error) + return ret0 +} + +// ReleaseCircuitBreaker indicates an expected call of ReleaseCircuitBreaker +func (mr *MockCircuitBreakerStoreMockRecorder) ReleaseCircuitBreaker(circuitBreakerRelation interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReleaseCircuitBreaker", reflect.TypeOf((*MockCircuitBreakerStore)(nil).ReleaseCircuitBreaker), circuitBreakerRelation) +} + +// UnbindCircuitBreaker mocks base method +func (m *MockCircuitBreakerStore) UnbindCircuitBreaker(serviceID, ruleID, ruleVersion string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UnbindCircuitBreaker", serviceID, ruleID, ruleVersion) + ret0, _ := ret[0].(error) + return ret0 +} + +// UnbindCircuitBreaker indicates an expected call of UnbindCircuitBreaker +func (mr *MockCircuitBreakerStoreMockRecorder) UnbindCircuitBreaker(serviceID, ruleID, ruleVersion interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnbindCircuitBreaker", reflect.TypeOf((*MockCircuitBreakerStore)(nil).UnbindCircuitBreaker), serviceID, ruleID, ruleVersion) +} + +// DeleteTagCircuitBreaker mocks base method +func (m *MockCircuitBreakerStore) DeleteTagCircuitBreaker(id, version string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteTagCircuitBreaker", id, version) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteTagCircuitBreaker indicates an expected call of DeleteTagCircuitBreaker +func (mr *MockCircuitBreakerStoreMockRecorder) DeleteTagCircuitBreaker(id, version interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteTagCircuitBreaker", reflect.TypeOf((*MockCircuitBreakerStore)(nil).DeleteTagCircuitBreaker), id, version) +} + +// DeleteMasterCircuitBreaker mocks base method +func (m *MockCircuitBreakerStore) DeleteMasterCircuitBreaker(id string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteMasterCircuitBreaker", id) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteMasterCircuitBreaker indicates an expected call of DeleteMasterCircuitBreaker +func (mr *MockCircuitBreakerStoreMockRecorder) DeleteMasterCircuitBreaker(id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteMasterCircuitBreaker", reflect.TypeOf((*MockCircuitBreakerStore)(nil).DeleteMasterCircuitBreaker), id) +} + +// UpdateCircuitBreaker mocks base method +func (m *MockCircuitBreakerStore) UpdateCircuitBreaker(circuitBraker *model.CircuitBreaker) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateCircuitBreaker", circuitBraker) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateCircuitBreaker indicates an expected call of UpdateCircuitBreaker +func (mr *MockCircuitBreakerStoreMockRecorder) UpdateCircuitBreaker(circuitBraker interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateCircuitBreaker", reflect.TypeOf((*MockCircuitBreakerStore)(nil).UpdateCircuitBreaker), circuitBraker) +} + +// GetCircuitBreaker mocks base method +func (m *MockCircuitBreakerStore) GetCircuitBreaker(id, version string) (*model.CircuitBreaker, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCircuitBreaker", id, version) + ret0, _ := ret[0].(*model.CircuitBreaker) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCircuitBreaker indicates an expected call of GetCircuitBreaker +func (mr *MockCircuitBreakerStoreMockRecorder) GetCircuitBreaker(id, version interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCircuitBreaker", reflect.TypeOf((*MockCircuitBreakerStore)(nil).GetCircuitBreaker), id, version) +} + +// GetCircuitBreakerVersions mocks base method +func (m *MockCircuitBreakerStore) GetCircuitBreakerVersions(id string) ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCircuitBreakerVersions", id) + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCircuitBreakerVersions indicates an expected call of GetCircuitBreakerVersions +func (mr *MockCircuitBreakerStoreMockRecorder) GetCircuitBreakerVersions(id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCircuitBreakerVersions", reflect.TypeOf((*MockCircuitBreakerStore)(nil).GetCircuitBreakerVersions), id) +} + +// GetCircuitBreakerMasterRelation mocks base method +func (m *MockCircuitBreakerStore) GetCircuitBreakerMasterRelation(ruleID string) ([]*model.CircuitBreakerRelation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCircuitBreakerMasterRelation", ruleID) + ret0, _ := ret[0].([]*model.CircuitBreakerRelation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCircuitBreakerMasterRelation indicates an expected call of GetCircuitBreakerMasterRelation +func (mr *MockCircuitBreakerStoreMockRecorder) GetCircuitBreakerMasterRelation(ruleID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCircuitBreakerMasterRelation", reflect.TypeOf((*MockCircuitBreakerStore)(nil).GetCircuitBreakerMasterRelation), ruleID) +} + +// GetCircuitBreakerRelation mocks base method +func (m *MockCircuitBreakerStore) GetCircuitBreakerRelation(ruleID, ruleVersion string) ([]*model.CircuitBreakerRelation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCircuitBreakerRelation", ruleID, ruleVersion) + ret0, _ := ret[0].([]*model.CircuitBreakerRelation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCircuitBreakerRelation indicates an expected call of GetCircuitBreakerRelation +func (mr *MockCircuitBreakerStoreMockRecorder) GetCircuitBreakerRelation(ruleID, ruleVersion interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCircuitBreakerRelation", reflect.TypeOf((*MockCircuitBreakerStore)(nil).GetCircuitBreakerRelation), ruleID, ruleVersion) +} + +// GetCircuitBreakerForCache mocks base method +func (m *MockCircuitBreakerStore) GetCircuitBreakerForCache(mtime time.Time, firstUpdate bool) ([]*model.ServiceWithCircuitBreaker, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCircuitBreakerForCache", mtime, firstUpdate) + ret0, _ := ret[0].([]*model.ServiceWithCircuitBreaker) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCircuitBreakerForCache indicates an expected call of GetCircuitBreakerForCache +func (mr *MockCircuitBreakerStoreMockRecorder) GetCircuitBreakerForCache(mtime, firstUpdate interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCircuitBreakerForCache", reflect.TypeOf((*MockCircuitBreakerStore)(nil).GetCircuitBreakerForCache), mtime, firstUpdate) +} + +// ListMasterCircuitBreakers mocks base method +func (m *MockCircuitBreakerStore) ListMasterCircuitBreakers(filters map[string]string, offset, limit uint32) (*model.CircuitBreakerDetail, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListMasterCircuitBreakers", filters, offset, limit) + ret0, _ := ret[0].(*model.CircuitBreakerDetail) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListMasterCircuitBreakers indicates an expected call of ListMasterCircuitBreakers +func (mr *MockCircuitBreakerStoreMockRecorder) ListMasterCircuitBreakers(filters, offset, limit interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListMasterCircuitBreakers", reflect.TypeOf((*MockCircuitBreakerStore)(nil).ListMasterCircuitBreakers), filters, offset, limit) +} + +// ListReleaseCircuitBreakers mocks base method +func (m *MockCircuitBreakerStore) ListReleaseCircuitBreakers(filters map[string]string, offset, limit uint32) (*model.CircuitBreakerDetail, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListReleaseCircuitBreakers", filters, offset, limit) + ret0, _ := ret[0].(*model.CircuitBreakerDetail) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListReleaseCircuitBreakers indicates an expected call of ListReleaseCircuitBreakers +func (mr *MockCircuitBreakerStoreMockRecorder) ListReleaseCircuitBreakers(filters, offset, limit interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListReleaseCircuitBreakers", reflect.TypeOf((*MockCircuitBreakerStore)(nil).ListReleaseCircuitBreakers), filters, offset, limit) +} + +// GetCircuitBreakersByService mocks base method +func (m *MockCircuitBreakerStore) GetCircuitBreakersByService(name, namespace string) (*model.CircuitBreaker, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCircuitBreakersByService", name, namespace) + ret0, _ := ret[0].(*model.CircuitBreaker) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCircuitBreakersByService indicates an expected call of GetCircuitBreakersByService +func (mr *MockCircuitBreakerStoreMockRecorder) GetCircuitBreakersByService(name, namespace interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCircuitBreakersByService", reflect.TypeOf((*MockCircuitBreakerStore)(nil).GetCircuitBreakersByService), name, namespace) +} + +// MockPlatformStore is a mock of PlatformStore interface +type MockPlatformStore struct { + ctrl *gomock.Controller + recorder *MockPlatformStoreMockRecorder +} + +// MockPlatformStoreMockRecorder is the mock recorder for MockPlatformStore +type MockPlatformStoreMockRecorder struct { + mock *MockPlatformStore +} + +// NewMockPlatformStore creates a new mock instance +func NewMockPlatformStore(ctrl *gomock.Controller) *MockPlatformStore { + mock := &MockPlatformStore{ctrl: ctrl} + mock.recorder = &MockPlatformStoreMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockPlatformStore) EXPECT() *MockPlatformStoreMockRecorder { + return m.recorder +} + +// CreatePlatform mocks base method +func (m *MockPlatformStore) CreatePlatform(platform *model.Platform) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreatePlatform", platform) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreatePlatform indicates an expected call of CreatePlatform +func (mr *MockPlatformStoreMockRecorder) CreatePlatform(platform interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatePlatform", reflect.TypeOf((*MockPlatformStore)(nil).CreatePlatform), platform) +} + +// UpdatePlatform mocks base method +func (m *MockPlatformStore) UpdatePlatform(platform *model.Platform) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdatePlatform", platform) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdatePlatform indicates an expected call of UpdatePlatform +func (mr *MockPlatformStoreMockRecorder) UpdatePlatform(platform interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdatePlatform", reflect.TypeOf((*MockPlatformStore)(nil).UpdatePlatform), platform) +} + +// DeletePlatform mocks base method +func (m *MockPlatformStore) DeletePlatform(id string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeletePlatform", id) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeletePlatform indicates an expected call of DeletePlatform +func (mr *MockPlatformStoreMockRecorder) DeletePlatform(id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePlatform", reflect.TypeOf((*MockPlatformStore)(nil).DeletePlatform), id) +} + +// GetPlatformById mocks base method +func (m *MockPlatformStore) GetPlatformById(id string) (*model.Platform, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPlatformById", id) + ret0, _ := ret[0].(*model.Platform) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetPlatformById indicates an expected call of GetPlatformById +func (mr *MockPlatformStoreMockRecorder) GetPlatformById(id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPlatformById", reflect.TypeOf((*MockPlatformStore)(nil).GetPlatformById), id) +} + +// GetPlatforms mocks base method +func (m *MockPlatformStore) GetPlatforms(query map[string]string, offset, limit uint32) (uint32, []*model.Platform, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPlatforms", query, offset, limit) + ret0, _ := ret[0].(uint32) + ret1, _ := ret[1].([]*model.Platform) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetPlatforms indicates an expected call of GetPlatforms +func (mr *MockPlatformStoreMockRecorder) GetPlatforms(query, offset, limit interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPlatforms", reflect.TypeOf((*MockPlatformStore)(nil).GetPlatforms), query, offset, limit) +} + +// MockTransaction is a mock of Transaction interface +type MockTransaction struct { + ctrl *gomock.Controller + recorder *MockTransactionMockRecorder +} + +// MockTransactionMockRecorder is the mock recorder for MockTransaction +type MockTransactionMockRecorder struct { + mock *MockTransaction +} + +// NewMockTransaction creates a new mock instance +func NewMockTransaction(ctrl *gomock.Controller) *MockTransaction { + mock := &MockTransaction{ctrl: ctrl} + mock.recorder = &MockTransactionMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockTransaction) EXPECT() *MockTransactionMockRecorder { + return m.recorder +} + +// Commit mocks base method +func (m *MockTransaction) Commit() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Commit") + ret0, _ := ret[0].(error) + return ret0 +} + +// Commit indicates an expected call of Commit +func (mr *MockTransactionMockRecorder) Commit() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Commit", reflect.TypeOf((*MockTransaction)(nil).Commit)) +} + +// LockBootstrap mocks base method +func (m *MockTransaction) LockBootstrap(key, server string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LockBootstrap", key, server) + ret0, _ := ret[0].(error) + return ret0 +} + +// LockBootstrap indicates an expected call of LockBootstrap +func (mr *MockTransactionMockRecorder) LockBootstrap(key, server interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LockBootstrap", reflect.TypeOf((*MockTransaction)(nil).LockBootstrap), key, server) +} + +// LockNamespace mocks base method +func (m *MockTransaction) LockNamespace(name string) (*model.Namespace, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LockNamespace", name) + ret0, _ := ret[0].(*model.Namespace) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LockNamespace indicates an expected call of LockNamespace +func (mr *MockTransactionMockRecorder) LockNamespace(name interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LockNamespace", reflect.TypeOf((*MockTransaction)(nil).LockNamespace), name) +} + +// RLockNamespace mocks base method +func (m *MockTransaction) RLockNamespace(name string) (*model.Namespace, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RLockNamespace", name) + ret0, _ := ret[0].(*model.Namespace) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// RLockNamespace indicates an expected call of RLockNamespace +func (mr *MockTransactionMockRecorder) RLockNamespace(name interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RLockNamespace", reflect.TypeOf((*MockTransaction)(nil).RLockNamespace), name) +} + +// DeleteNamespace mocks base method +func (m *MockTransaction) DeleteNamespace(name string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteNamespace", name) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteNamespace indicates an expected call of DeleteNamespace +func (mr *MockTransactionMockRecorder) DeleteNamespace(name interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteNamespace", reflect.TypeOf((*MockTransaction)(nil).DeleteNamespace), name) +} + +// LockService mocks base method +func (m *MockTransaction) LockService(name, namespace string) (*model.Service, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LockService", name, namespace) + ret0, _ := ret[0].(*model.Service) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LockService indicates an expected call of LockService +func (mr *MockTransactionMockRecorder) LockService(name, namespace interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LockService", reflect.TypeOf((*MockTransaction)(nil).LockService), name, namespace) +} + +// RLockService mocks base method +func (m *MockTransaction) RLockService(name, namespace string) (*model.Service, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RLockService", name, namespace) + ret0, _ := ret[0].(*model.Service) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// RLockService indicates an expected call of RLockService +func (mr *MockTransactionMockRecorder) RLockService(name, namespace interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RLockService", reflect.TypeOf((*MockTransaction)(nil).RLockService), name, namespace) +} + +// BatchRLockServices mocks base method +func (m *MockTransaction) BatchRLockServices(ids map[string]bool) (map[string]bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BatchRLockServices", ids) + ret0, _ := ret[0].(map[string]bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// BatchRLockServices indicates an expected call of BatchRLockServices +func (mr *MockTransactionMockRecorder) BatchRLockServices(ids interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BatchRLockServices", reflect.TypeOf((*MockTransaction)(nil).BatchRLockServices), ids) +} + +// DeleteService mocks base method +func (m *MockTransaction) DeleteService(name, namespace string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteService", name, namespace) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteService indicates an expected call of DeleteService +func (mr *MockTransactionMockRecorder) DeleteService(name, namespace interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteService", reflect.TypeOf((*MockTransaction)(nil).DeleteService), name, namespace) +} + +// DeleteAliasWithSourceID mocks base method +func (m *MockTransaction) DeleteAliasWithSourceID(sourceServiceID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteAliasWithSourceID", sourceServiceID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteAliasWithSourceID indicates an expected call of DeleteAliasWithSourceID +func (mr *MockTransactionMockRecorder) DeleteAliasWithSourceID(sourceServiceID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAliasWithSourceID", reflect.TypeOf((*MockTransaction)(nil).DeleteAliasWithSourceID), sourceServiceID) +} diff --git a/store/sql.go b/store/sql.go new file mode 100644 index 000000000..5edf12a4d --- /dev/null +++ b/store/sql.go @@ -0,0 +1,24 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package store + +// 用于通过服务实例查询服务的参数 +type InstanceArgs struct { + Hosts []string + Ports []uint32 +} diff --git a/store/status.go b/store/status.go new file mode 100644 index 000000000..3dede6884 --- /dev/null +++ b/store/status.go @@ -0,0 +1,109 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package store + +import ( + "strings" +) + +// 存储层的状态码 +type StatusCode uint32 + +// 状态码定义 +const ( + Ok StatusCode = iota + EmptyParamsErr // 参数不合法 + OutOfRangeErr // 数据不合法,比如越级了,超过了字段大小 + DataConflictErr // 数据冲突,在并发更新metadata的时候可能会出现 + NotFoundNamespace // 找不到namespace,service插入依赖namespace是否存在 + NotFoundService // 找不到service,在instance等资源插入的时候依赖service是否存在 + NotFoundMasterConfig // 在标记规则前,需要保证规则的master版本存在 + NotFoundTagConfigOrService // 在发布规则前,需要保证规则已标记且服务存在 + ExistReleasedConfig // 在删除规则时,发现存在已经发布的版本 + AffectedRowsNotMatch // 操作的行数与预期不符合 + DuplicateEntryErr // 主键重复,一般是资源已存在了,提醒用户资源存在 + ForeignKeyErr // 外键错误,一般是操作不当导致的 + DeadlockErr // 数据库死锁 + NotFoundMeshOrService // 网格订阅服务的时候,网格或者服务不存在 + NotFoundMeshService // 更新订阅服务的时候,订阅服务不存在 + Unknown +) + +// 普通error转StatusError +func Error(err error) error { + if err == nil { + return nil + } + + // 已经是StatusError了,不再转换 + if _, ok := err.(*StatusError); ok { + return err + } + + s := &StatusError{message: err.Error()} + if strings.Contains(s.message, "Data too long") { + s.code = OutOfRangeErr + } else if strings.Contains(s.message, "Duplicate entry") { + s.code = DuplicateEntryErr + } else if strings.Contains(s.message, "a foreign key constraint fails") { + s.code = ForeignKeyErr + } else if strings.Contains(s.message, "Deadlock") { + s.code = DeadlockErr + } else { + s.code = Unknown + } + + return s +} + +// 根据code和message创建StatusError +func NewStatusError(code StatusCode, message string) error { + return &StatusError{ + code: code, + message: message, + } +} + +// 根据error接口,获取状态码 +func Code(err error) StatusCode { + if err == nil { + return Ok + } + + se, ok := err.(*StatusError) + if ok { + return se.code + } + + return Unknown +} + +// 包括了状态码的error接口 +type StatusError struct { + code StatusCode + message string +} + +// 实现error接口 +func (s *StatusError) Error() string { + if s == nil { + return "" + } + + return s.message +} diff --git a/store/store.go b/store/store.go new file mode 100644 index 000000000..9d71fe615 --- /dev/null +++ b/store/store.go @@ -0,0 +1,89 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package store + +import ( + "errors" + "fmt" + "os" + "sync" +) + +/** + * @brief Store的通用配置 + */ +type Config struct { + Name string + Option map[string]interface{} +} + +var ( + StoreSlots = make(map[string]Store) + once = &sync.Once{} + config = &Config{} +) + +/** + * @brief 注册一个新的Store + */ +func RegisterStore(s Store) error { + name := s.Name() + if _, ok := StoreSlots[name]; ok { + return errors.New("Store name is exist") + } + + StoreSlots[name] = s + return nil +} + +/** + * @brief 获取Store + */ +func GetStore() (Store, error) { + name := config.Name + if name == "" { + return nil, errors.New("Store Name is empty") + } + + store, ok := StoreSlots[name] + if !ok { + return nil, errors.New("No such name Store") + } + + initialize(store) + return store, nil +} + +/** + * @brief 设置store的conf + */ +func SetStoreConfig(conf *Config) { + config = conf +} + +/** + * @brief 包裹了初始化函数,在GetStore的时候会在自动调用,全局初始化一次 + */ +func initialize(s Store) { + once.Do(func() { + if err := s.Initialize(config); err != nil { + fmt.Printf("err: %s", err.Error()) + os.Exit(-1) + } + }) +} diff --git a/test/circuitbreaker_config_test.go b/test/circuitbreaker_config_test.go new file mode 100644 index 000000000..7a29af7e3 --- /dev/null +++ b/test/circuitbreaker_config_test.go @@ -0,0 +1,164 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package test + +import ( + "github.com/polarismesh/polaris-server/test/http" + "github.com/polarismesh/polaris-server/test/resource" + "testing" +) + +/** + * @brief 测试熔断规则 + */ +func TestCircuitBreaker(t *testing.T) { + t.Log("test circuit breaker interface") + + client := http.NewClient(httpserverAddress, httpserverVersion) + + //------------------------------------------------------- + namespaces := resource.CreateNamespaces() + // 创建命名空间 + ret, err := client.CreateNamespaces(namespaces) + if err != nil { + t.Fatalf("create namespaces fail,error: %s", err.Error()) + } + for index, item := range ret.GetResponses() { + namespaces[index].Token = item.GetNamespace().GetToken() + } + t.Log("create namepsaces success") + + circuitBreakers := resource.CreateCircuitBreakers(namespaces[0]) + + // 创建熔断规则 + ret, err = client.CreateCircuitBreakers(circuitBreakers) + if err != nil { + t.Fatalf("create circuit breakers fail, err: %s", err.Error()) + } + for index, item := range ret.GetResponses() { + circuitBreakers[index].Id = item.GetCircuitBreaker().GetId() + circuitBreakers[index].Version = item.GetCircuitBreaker().GetVersion() + circuitBreakers[index].Token = item.GetCircuitBreaker().GetToken() + } + t.Log("create rate limits success") + + // 查询熔断规则:根据id和version + if err = client.GetCircuitBreaker(circuitBreakers[0], circuitBreakers[0]); err != nil { + t.Fatalf("get circuit breaker fail, err : %s", err.Error()) + } + t.Log("get circuit breaker success") + + // 更新熔断规则 + resource.UpdateCircuitBreakers(circuitBreakers) + + if err := client.UpdateCircuitBreakers(circuitBreakers); err != nil { + t.Fatalf("update circuit breakers fail, err: %s", err.Error()) + } + t.Log("update circuit breaker success") + + // 查询熔断规则:根据id和version + if err = client.GetCircuitBreaker(circuitBreakers[0], circuitBreakers[0]); err != nil { + t.Fatalf("get circuit breaker fail, err : %s", err.Error()) + } + t.Log("get circuit breaker success") + + // 创建熔断规则版本 + newCircuitBreakers := resource.CreateCircuitBreakerVersions(circuitBreakers) + + ret, err = client.CreateCircuitBreakerVersions(newCircuitBreakers) + if err != nil { + t.Fatalf("create circuit breaker versions fail, err: %s", err.Error()) + } + for index, item := range ret.GetResponses() { + newCircuitBreakers[index].Id = item.GetCircuitBreaker().GetId() + } + t.Log("create circuit breaker versions success") + + // 查询熔断规则:根据id和version + if err = client.GetCircuitBreaker(circuitBreakers[0], newCircuitBreakers[0]); err != nil { + t.Fatalf("get circuit breaker fail, err: %s", err.Error()) + } + t.Log("get circuit breaker success") + + // 查询熔断规则的所有版本 + if err := client.GetCircuitBreakerVersions(newCircuitBreakers[0]); err != nil { + t.Fatalf("get circuit breaker version fail, err: %s", err.Error()) + } + + //------------------------------------------------------- + services := resource.CreateServices(namespaces[0]) + // 创建服务 + ret, err = client.CreateServices(services) + if err != nil { + t.Fatalf("create services fail,error: %s", err.Error()) + } + for index, item := range ret.GetResponses() { + services[index].Token = item.GetService().GetToken() + } + t.Log("create services success") + //------------------------------------------------------- + + // 发布熔断规则 + configReleases := resource.CreateConfigRelease(services, newCircuitBreakers) + if err := client.ReleaseCircuitBreakers(configReleases); err != nil { + t.Fatalf("release config release fail,error: %s", err.Error()) + } + t.Log("release circuit breakers success") + + // 查询熔断规则的已发布版本及绑定服务 + if err := client.GetCircuitBreakersRelease(newCircuitBreakers[0], services[0]); err != nil { + t.Fatalf("get circuit breakers tag err: %s", err.Error()) + } + t.Log("get circuit breakers tag success") + + // 查询服务绑定的熔断规则 + if err := client.GetCircuitBreakerByService(services[0], circuitBreakers[0], newCircuitBreakers[0]); err != nil { + t.Fatalf("get circuit breaker by service err: %s", err.Error()) + } + t.Log("get circuit breaker by service success") + + // 解绑熔断规则 + if err := client.UnbindCircuitBreakers(configReleases); err != nil { + t.Fatalf("unbind config release fail,error: %s", err.Error()) + } + t.Log("unbind circuit breakers success") + + // 删除熔断规则 + circuitBreakers = append(circuitBreakers, newCircuitBreakers...) + err = client.DeleteCircuitBreakers(circuitBreakers) + if err != nil { + t.Fatalf("delete rate limits fail,error: %s", err.Error()) + } + t.Log("delete rate limits success") + + //------------------------------------------------------- + + // 删除服务 + err = client.DeleteServices(services) + if err != nil { + t.Fatalf("delete services fail,error: %s", err.Error()) + } + t.Log("delete services success") + + // 删除命名空间 + err = client.DeleteNamespaces(namespaces) + if err != nil { + t.Fatalf("delete namespaces fail,error: %s", err.Error()) + } + t.Log("delete namepsaces success") +} diff --git a/test/client_grpc_test.go b/test/client_grpc_test.go new file mode 100644 index 000000000..f597276f1 --- /dev/null +++ b/test/client_grpc_test.go @@ -0,0 +1,124 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package test + +import ( + "testing" + "time" + + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/test/grpc" + "github.com/polarismesh/polaris-server/test/http" + "github.com/polarismesh/polaris-server/test/resource" +) + +/** + * @brief 测试客户端GRPC接口 + */ +func TestClientGRPC(t *testing.T) { + t.Log("test client grpc interface") + + clientHTTP := http.NewClient(httpserverAddress, httpserverVersion) + + namespaces := resource.CreateNamespaces() + services := resource.CreateServices(namespaces[0]) + + // 创建命名空间 + ret, err := clientHTTP.CreateNamespaces(namespaces) + if err != nil { + t.Fatalf("create namespaces fail") + } + for index, item := range ret.GetResponses() { + namespaces[index].Token = item.GetNamespace().GetToken() + } + t.Log("create namespaces success") + + // 创建服务 + ret, err = clientHTTP.CreateServices(services) + if err != nil { + t.Fatalf("create services fail") + } + for index, item := range ret.GetResponses() { + services[index].Token = item.GetService().GetToken() + } + t.Log("create services success") + + //------------------------------------------------------- + + clientGRPC, err := grpc.NewClient(grpcserverAddress) + if err != nil { + t.Fatalf("new grpc client fail") + } + defer clientGRPC.Close() + + client := resource.CreateClient(0) + instances := resource.CreateInstances(services[0]) + + // 上报客户端信息 + err = clientGRPC.ReportClient(client) + if err != nil { + t.Fatalf("report client fail") + } + t.Log("report client success") + + // 注册服务实例 + err = clientGRPC.RegisterInstance(instances[0]) + if err != nil { + t.Fatalf("register instance fail") + } + t.Log("register instance success") + + time.Sleep(3 * time.Second) // 延迟 + + // 上报心跳 + err = clientGRPC.Heartbeat(instances[0]) + if err != nil { + t.Fatalf("instance heartbeat fail") + } + t.Log("instance heartbeat success") + + time.Sleep(3 * time.Second) // 延迟 + + // 查询服务实例 + err = clientGRPC.Discover(api.DiscoverRequest_INSTANCE, services[0]) + if err != nil { + t.Fatalf("discover instance fail") + } + t.Log("discover instance success") + + // 反注册服务实例 + err = clientGRPC.DeregisterInstance(instances[0]) + if err != nil { + t.Fatalf("deregister instance fail") + } + t.Log("deregister instance success") + + // 删除服务 + err = clientHTTP.DeleteServices(services) + if err != nil { + t.Fatalf("delete services fail") + } + t.Log("delete services success") + + // 删除命名空间 + err = clientHTTP.DeleteNamespaces(namespaces) + if err != nil { + t.Fatalf("delete namespaces fail") + } + t.Log("delete namespaces success") +} diff --git a/test/common_test.go b/test/common_test.go new file mode 100644 index 000000000..5245bd624 --- /dev/null +++ b/test/common_test.go @@ -0,0 +1,24 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package test + +const ( + httpserverAddress = "127.0.0.1:8090" + httpserverVersion = "v1" + grpcserverAddress = "127.0.0.1:8091" +) diff --git a/test/grpc/client.go b/test/grpc/client.go new file mode 100644 index 000000000..c912a56e1 --- /dev/null +++ b/test/grpc/client.go @@ -0,0 +1,59 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package grpc + +import ( + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "google.golang.org/grpc" +) + +/** + * @brief 创建GRPC客户端 + */ +func NewClient(address string) (*Client, error) { + fmt.Printf("\nnew grpc client\n") + + conn, err := grpc.Dial(address, grpc.WithInsecure()) + if err != nil { + fmt.Printf("%v\n", err) + return nil, err + } + + client := &Client{ + Conn: conn, + Worker: api.NewPolarisGRPCClient(conn), + } + + return client, nil +} + +/** + * @brief GRPC客户端 + */ +type Client struct { + Conn *grpc.ClientConn + Worker api.PolarisGRPCClient +} + +/** + * @brief 关闭连接 + */ +func (c *Client) Close() { + c.Conn.Close() +} diff --git a/test/grpc/discover.go b/test/grpc/discover.go new file mode 100644 index 000000000..49ca2cf72 --- /dev/null +++ b/test/grpc/discover.go @@ -0,0 +1,113 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package grpc + +import ( + "context" + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/naming" + "google.golang.org/grpc/metadata" + "io" + "time" +) + +/** + * @brief 统一发现函数 + */ +func (c *Client) Discover(drt api.DiscoverRequest_DiscoverRequestType, service *api.Service) error { + fmt.Printf("\ndiscover\n") + + md := metadata.Pairs("request-id", naming.NewUUID()) + ctx := metadata.NewOutgoingContext(context.Background(), md) + + ctx, cancel := context.WithTimeout(ctx, time.Second) + defer cancel() + + worker, err := c.Worker.Discover(ctx) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + request := &api.DiscoverRequest{ + Type: drt, + Service: service, + } + if err := worker.Send(request); err != nil { + fmt.Printf("%v\n", err) + return err + } + worker.CloseSend() + + for { + rsp, err := worker.Recv() + if err != nil { + if err == io.EOF { + return nil + } + + fmt.Printf("%v\n", err) + return err + } + + fmt.Printf("%v\n", rsp) + } +} + +/** + * @brief 统一发现函数 + */ +func (c *Client) DiscoverRequest(request *api.DiscoverRequest) (*api.DiscoverResponse, error) { + fmt.Printf("\ndiscover\n") + + md := metadata.Pairs("request-id", naming.NewUUID()) + ctx := metadata.NewOutgoingContext(context.Background(), md) + + ctx, cancel := context.WithTimeout(ctx, time.Second) + defer cancel() + + worker, err := c.Worker.Discover(ctx) + if err != nil { + fmt.Printf("%v\n", err) + return nil, err + } + + if err := worker.Send(request); err != nil { + fmt.Printf("%v\n", err) + return nil, err + } + worker.CloseSend() + + var latestResp *api.DiscoverResponse + + for { + rsp, err := worker.Recv() + if err != nil { + if err == io.EOF { + return latestResp, nil + } + + fmt.Printf("%v\n", err) + } + + fmt.Printf("%v\n", rsp) + latestResp = rsp + } + +} \ No newline at end of file diff --git a/test/grpc/heartbeat.go b/test/grpc/heartbeat.go new file mode 100644 index 000000000..d1cf49b37 --- /dev/null +++ b/test/grpc/heartbeat.go @@ -0,0 +1,51 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package grpc + +import ( + "context" + "fmt" + "time" + + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/naming" + "google.golang.org/grpc/metadata" +) + +/** + * @brief 上报心跳 + */ +func (c *Client) Heartbeat(instance *api.Instance) error { + fmt.Printf("\nheartbeat\n") + + md := metadata.Pairs("request-id", naming.NewUUID()) + ctx := metadata.NewOutgoingContext(context.Background(), md) + + ctx, cancel := context.WithTimeout(ctx, time.Second) + defer cancel() + + rsp, err := c.Worker.Heartbeat(ctx, instance) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + fmt.Printf("%v\n", rsp) + + return nil +} diff --git a/test/grpc/register.go b/test/grpc/register.go new file mode 100644 index 000000000..866d13f80 --- /dev/null +++ b/test/grpc/register.go @@ -0,0 +1,74 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package grpc + +import ( + "context" + "fmt" + "time" + + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/naming" + "google.golang.org/grpc/metadata" +) + +/** + * @brief 注册服务实例 + */ +func (c *Client) RegisterInstance(instance *api.Instance) error { + fmt.Printf("\nregister instance\n") + + md := metadata.Pairs("request-id", naming.NewUUID()) + ctx := metadata.NewOutgoingContext(context.Background(), md) + + ctx, cancel := context.WithTimeout(ctx, time.Second) + defer cancel() + + rsp, err := c.Worker.RegisterInstance(ctx, instance) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + fmt.Printf("%v\n", rsp) + + return nil +} + +/** + * @brief 反注册服务实例 + */ +func (c *Client) DeregisterInstance(instance *api.Instance) error { + fmt.Printf("\nderegister instance\n") + + md := metadata.Pairs("request-id", naming.NewUUID()) + ctx := metadata.NewOutgoingContext(context.Background(), md) + + ctx, cancel := context.WithTimeout(ctx, time.Second) + defer cancel() + + rsp, err := c.Worker.DeregisterInstance(ctx, instance) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + fmt.Printf("%v\n", rsp) + + return nil +} diff --git a/test/grpc/report_client.go b/test/grpc/report_client.go new file mode 100644 index 000000000..3e5d70640 --- /dev/null +++ b/test/grpc/report_client.go @@ -0,0 +1,51 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package grpc + +import ( + "context" + "fmt" + "time" + + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/naming" + "google.golang.org/grpc/metadata" +) + +/** + * @brief 上报客户端信息 + */ +func (c *Client) ReportClient(client *api.Client) error { + fmt.Printf("\nreport client\n") + + md := metadata.Pairs("request-id", naming.NewUUID()) + ctx := metadata.NewOutgoingContext(context.Background(), md) + + ctx, cancel := context.WithTimeout(ctx, time.Second) + defer cancel() + + rsp, err := c.Worker.ReportClient(ctx, client) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + fmt.Printf("%v\n", rsp) + + return nil +} diff --git a/test/http/circuitbreaker_config.go b/test/http/circuitbreaker_config.go new file mode 100644 index 000000000..ae8b48e1c --- /dev/null +++ b/test/http/circuitbreaker_config.go @@ -0,0 +1,590 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package http + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/golang/protobuf/jsonpb" + "io" +) + +/** + * @brief 熔断规则数组转JSON + */ +func JSONFromCircuitBreakers(circuitBreakers []*api.CircuitBreaker) (*bytes.Buffer, error) { + m := jsonpb.Marshaler{Indent: " "} + + buffer := bytes.NewBuffer([]byte{}) + + buffer.Write([]byte("[")) + for index, circuitBreaker := range circuitBreakers { + if index > 0 { + buffer.Write([]byte(",\n")) + } + err := m.Marshal(buffer, circuitBreaker) + if err != nil { + return nil, err + } + } + + buffer.Write([]byte("]")) + return buffer, nil +} + +/** + * @brief 发布规则转JSON + */ +func JSONFromConfigReleases(configReleases []*api.ConfigRelease) (*bytes.Buffer, error) { + m := jsonpb.Marshaler{Indent: " "} + + buffer := bytes.NewBuffer([]byte{}) + + buffer.Write([]byte("[")) + for index, configRelease := range configReleases { + if index > 0 { + buffer.Write([]byte(",\n")) + } + err := m.Marshal(buffer, configRelease) + if err != nil { + return nil, err + } + } + + buffer.Write([]byte("]")) + return buffer, nil +} + +/** + * @brief 创建熔断规则 + */ +func (c *Client) CreateCircuitBreakers(circuitBreakers []*api.CircuitBreaker) (*api.BatchWriteResponse, error) { + fmt.Printf("\ncreate circuit breakers\n") + + url := fmt.Sprintf("http://%v/naming/%v/circuitbreakers", c.Address, c.Version) + + body, err := JSONFromCircuitBreakers(circuitBreakers) + if err != nil { + fmt.Printf("%v\n", err) + return nil, err + } + + response, err := c.SendRequest("POST", url, body) + if err != nil { + fmt.Printf("%v\n", err) + return nil, err + } + + ret, err := GetBatchWriteResponse(response) + if err != nil { + fmt.Printf("%v\n", err) + return nil, err + } + + return checkCreateCircuitBreakersResponse(ret, circuitBreakers) +} + +/** + * @brief 创建熔断规则版本 + */ +func (c *Client) CreateCircuitBreakerVersions(circuitBreakers []*api.CircuitBreaker) (*api.BatchWriteResponse, error) { + fmt.Printf("\ncreate circuit breaker versions\n") + + url := fmt.Sprintf("http://%v/naming/%v/circuitbreakers/version", c.Address, c.Version) + body, err := JSONFromCircuitBreakers(circuitBreakers) + if err != nil { + fmt.Printf("%v\n", err) + return nil, err + } + + response, err := c.SendRequest("POST", url, body) + if err != nil { + fmt.Printf("%v\n", err) + return nil, err + } + + ret, err := GetBatchWriteResponse(response) + if err != nil { + fmt.Printf("%v\n", err) + return nil, err + } + + return checkCreateCircuitBreakersResponse(ret, circuitBreakers) +} + +/** + * @breif 更新熔断规则 + */ +func (c *Client) UpdateCircuitBreakers(circuitBreakers []*api.CircuitBreaker) error { + fmt.Printf("\nupdate circuit breakers\n") + + url := fmt.Sprintf("http://%v/naming/%v/circuitbreakers", c.Address, c.Version) + + body, err := JSONFromCircuitBreakers(circuitBreakers) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + response, err := c.SendRequest("PUT", url, body) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + _, err = GetBatchWriteResponse(response) + if err != nil { + if err == io.EOF { + return nil + } + + fmt.Printf("%v\n", err) + return err + } + return nil +} + +/** + * @brief 删除熔断规则 + */ +func (c *Client) DeleteCircuitBreakers(circuitBreakers []*api.CircuitBreaker) error { + fmt.Printf("\ndelete circuit breakers\n") + + url := fmt.Sprintf("http://%v/naming/%v/circuitbreakers/delete", c.Address, c.Version) + + body, err := JSONFromCircuitBreakers(circuitBreakers) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + response, err := c.SendRequest("POST", url, body) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + _, err = GetBatchWriteResponse(response) + if err != nil { + if err == io.EOF { + return nil + } + + fmt.Printf("%v\n", err) + return err + } + return nil +} + +/** + * @brief 发布熔断规则 + */ +func (c *Client) ReleaseCircuitBreakers(configReleases []*api.ConfigRelease) error { + fmt.Printf("\nrelease circuit breakers\n") + + url := fmt.Sprintf("http://%v/naming/%v/circuitbreakers/release", c.Address, c.Version) + + body, err := JSONFromConfigReleases(configReleases) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + response, err := c.SendRequest("POST", url, body) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + _, err = GetBatchWriteResponse(response) + if err != nil { + if err == io.EOF { + return nil + } + + fmt.Printf("%v\n", err) + return err + } + return nil +} + +/** + * @brief 解绑熔断规则 + */ +func (c *Client) UnbindCircuitBreakers(configReleases []*api.ConfigRelease) error { + fmt.Printf("\nunbind circuit breakers\n") + + url := fmt.Sprintf("http://%v/naming/%v/circuitbreakers/unbind", c.Address, c.Version) + + body, err := JSONFromConfigReleases(configReleases) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + response, err := c.SendRequest("POST", url, body) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + _, err = GetBatchWriteResponse(response) + if err != nil { + if err == io.EOF { + return nil + } + + fmt.Printf("%v\n", err) + return err + } + return nil +} + +/** + * @brief 根据id和version查询熔断规则 + */ +func (c *Client) GetCircuitBreaker(masterCircuitBreaker, circuitBreaker *api.CircuitBreaker) error { + fmt.Printf("\nget circuit breaker by id and version\n") + + url := fmt.Sprintf("http://%v/naming/%v/circuitbreaker", c.Address, c.Version) + + params := map[string][]interface{}{ + "id": {circuitBreaker.GetId().GetValue()}, + "version": {circuitBreaker.GetVersion().GetValue()}, + } + + url = c.CompleteURL(url, params) + response, err := c.SendRequest("GET", url, nil) + if err != nil { + return err + } + + ret, err := GetBatchQueryResponse(response) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + if ret.GetCode() == nil || ret.GetCode().GetValue() != api.ExecuteSuccess { + return errors.New("invalid batch code") + } + + size := 1 + + if ret.GetAmount() == nil || ret.GetAmount().GetValue() != uint32(size) { + return errors.New("invalid batch amount") + } + + if ret.GetSize() == nil || ret.GetSize().GetValue() != uint32(size) { + return errors.New("invalid batch size") + } + + item := ret.GetConfigWithServices() + if item == nil || len(item) != size { + return errors.New("invalid batch circuit breakers") + } + + if item[0].GetCircuitBreaker() == nil { + return errors.New("invalid circuit breakers") + } + + if result, err := compareCircuitBreaker(circuitBreaker, masterCircuitBreaker, item[0].GetCircuitBreaker()); !result { + return err + } + + return nil +} + +/** + * @brief 查询熔断规则的已发布规则及服务 + */ +func (c *Client) GetCircuitBreakersRelease(circuitBreaker *api.CircuitBreaker, correctService *api.Service) error { + fmt.Printf("\nget circuit breaker release\n") + + url := fmt.Sprintf("http://%v/naming/%v/circuitbreakers/release", c.Address, c.Version) + + params := map[string][]interface{}{ + "id": {circuitBreaker.GetId().GetValue()}, + } + + url = c.CompleteURL(url, params) + response, err := c.SendRequest("GET", url, nil) + if err != nil { + return err + } + + ret, err := GetBatchQueryResponse(response) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + if ret.GetCode() == nil || ret.GetCode().GetValue() != api.ExecuteSuccess { + return errors.New("invalid batch code") + } + + size := 1 + + if ret.GetAmount() == nil || ret.GetAmount().GetValue() != uint32(size) { + return errors.New("invalid batch amount") + } + + if ret.GetSize() == nil || ret.GetSize().GetValue() != uint32(size) { + return errors.New("invalid batch size") + } + + configWithServices := ret.GetConfigWithServices() + if configWithServices == nil || len(configWithServices) != size { + return errors.New("invalid batch circuit breakers") + } + + if configWithServices[0].GetCircuitBreaker() == nil { + return errors.New("invalid circuit breakers") + } + + rule := configWithServices[0].GetCircuitBreaker() + + if circuitBreaker.GetId().GetValue() != rule.GetId().GetValue() || + circuitBreaker.GetVersion().GetValue() != rule.GetVersion().GetValue() { + return errors.New("error circuit breaker id or version") + } + + if configWithServices[0].GetServices() == nil || configWithServices[0].GetServices()[0] == nil { + return errors.New("invalid services") + } + + service := configWithServices[0].GetServices()[0] + serviceName := service.GetName().GetValue() + namespaceName := service.GetNamespace().GetValue() + + if serviceName != correctService.GetName().GetValue() || + namespaceName != correctService.GetNamespace().GetValue() { + return errors.New("invalid service name or namespace") + } + + return nil +} + +/** + * @brief 查询熔断规则所有版本 + */ +func (c *Client) GetCircuitBreakerVersions(circuitBreaker *api.CircuitBreaker) error { + fmt.Printf("\nget circuit breaker versions\n") + + url := fmt.Sprintf("http://%v/naming/%v/circuitbreaker/versions", c.Address, c.Version) + + params := map[string][]interface{}{ + "id": {circuitBreaker.GetId().GetValue()}, + } + + url = c.CompleteURL(url, params) + response, err := c.SendRequest("GET", url, nil) + if err != nil { + return err + } + + ret, err := GetBatchQueryResponse(response) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + if ret.GetCode() == nil || ret.GetCode().GetValue() != api.ExecuteSuccess { + return errors.New("invalid batch code") + } + + size := 2 + + if ret.GetAmount() == nil || ret.GetAmount().GetValue() != uint32(size) { + return errors.New("invalid batch amount") + } + + if ret.GetSize() == nil || ret.GetSize().GetValue() != uint32(size) { + return errors.New("invalid batch size") + } + + configWithServices := ret.GetConfigWithServices() + if configWithServices == nil || len(configWithServices) != size { + return errors.New("invalid batch circuit breakers") + } + + versions := make([]string, 0, size) + for _, item := range configWithServices { + cb := item.GetCircuitBreaker() + if cb.GetId().GetValue() != circuitBreaker.GetId().GetValue() { + return errors.New("invalid circuit breaker id") + } + versions = append(versions, cb.GetVersion().GetValue()) + } + + correctVersions := map[string]bool{ + circuitBreaker.GetVersion().GetValue(): true, + "master": true, + } + + for _, version := range versions { + if _, ok := correctVersions[version]; !ok { + return errors.New("invalid circuit breaker version") + } + } + + return nil +} + +/** + * @brief 查询服务绑定的熔断规则 + */ +func (c *Client) GetCircuitBreakerByService(service *api.Service, masterCircuitBreaker, + circuitBreaker *api.CircuitBreaker) error { + fmt.Printf("\nget circuit breaker by service\n") + + url := fmt.Sprintf("http://%v/naming/%v/service/circuitbreaker", c.Address, c.Version) + + params := map[string][]interface{}{ + "service": {service.GetName().GetValue()}, + "namespace": {service.GetNamespace().GetValue()}, + } + + url = c.CompleteURL(url, params) + response, err := c.SendRequest("GET", url, nil) + if err != nil { + return err + } + + ret, err := GetBatchQueryResponse(response) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + if ret.GetCode() == nil || ret.GetCode().GetValue() != api.ExecuteSuccess { + return errors.New("invalid batch code") + } + + size := 1 + + if ret.GetAmount() == nil || ret.GetAmount().GetValue() != uint32(size) { + return errors.New("invalid batch amount") + } + + if ret.GetSize() == nil || ret.GetSize().GetValue() != uint32(size) { + return errors.New("invalid batch size") + } + + configWithServices := ret.GetConfigWithServices() + if configWithServices == nil || len(configWithServices) != size { + return errors.New("invalid batch circuit breakers") + } + + rule := configWithServices[0].GetCircuitBreaker() + if rule == nil { + return errors.New("invalid circuit breaker") + } + + if result, err := compareCircuitBreaker(circuitBreaker, masterCircuitBreaker, rule); !result { + return err + } + + return nil +} + +/** + * @brief 检查创建熔断规则的回复 + */ +func checkCreateCircuitBreakersResponse(ret *api.BatchWriteResponse, circuitBreakers []*api.CircuitBreaker) ( + *api.BatchWriteResponse, error) { + switch { + case ret.GetCode().GetValue() != api.ExecuteSuccess: + return nil, errors.New("invalid batch code") + case ret.GetSize().GetValue() != uint32(len(circuitBreakers)): + return nil, errors.New("invalid batch size") + case len(ret.GetResponses()) != len(circuitBreakers): + return nil, errors.New("invalid batch response") + } + + for index, item := range ret.GetResponses() { + if item.GetCode().GetValue() != api.ExecuteSuccess { + return nil, errors.New("invalid code") + } + circuitBreaker := item.GetCircuitBreaker() + if circuitBreaker == nil { + return nil, errors.New("empty circuit breaker") + } + + if result, err := compareCircuitBreaker(circuitBreakers[index], circuitBreakers[index], circuitBreaker); !result { + return nil, err + } else { + return ret, nil + } + } + return ret, nil +} + +/** + * @brief 比较circuit breaker是否相等 + */ +func compareCircuitBreaker(correctItem, correctMaster *api.CircuitBreaker, item *api.CircuitBreaker) (bool, error) { + switch { + case item.GetId() == nil || item.GetId().GetValue() == "": + return false, errors.New("error id") + case item.GetVersion() == nil || item.GetVersion().GetValue() == "": + return false, errors.New("error version") + case correctMaster.GetName().GetValue() != item.GetName().GetValue(): + return false, errors.New("error name") + case correctMaster.GetNamespace().GetValue() != item.GetNamespace().GetValue(): + return false, errors.New("error namespace") + case correctMaster.GetOwners().GetValue() != item.GetOwners().GetValue(): + return false, errors.New("error owners") + case correctMaster.GetComment().GetValue() != item.GetComment().GetValue(): + return false, errors.New("error comment") + case correctMaster.GetBusiness().GetValue() != item.GetBusiness().GetValue(): + return false, errors.New("error business") + case correctMaster.GetDepartment().GetValue() != item.GetDepartment().GetValue(): + return false, errors.New("error department") + default: + break + } + + correctInbounds, err := json.Marshal(correctItem.GetInbounds()) + if err != nil { + panic(err) + } + inbounds, err := json.Marshal(item.GetInbounds()) + if err != nil { + panic(err) + } + if string(correctInbounds) != string(inbounds) { + return false, errors.New("error inbounds") + } + + correctOutbounds, err := json.Marshal(correctItem.GetOutbounds()) + if err != nil { + panic(err) + } + outbounds, err := json.Marshal(item.GetOutbounds()) + if err != nil { + panic(err) + } + if string(correctOutbounds) != string(outbounds) { + return false, errors.New("error inbounds") + } + return true, nil +} diff --git a/test/http/client.go b/test/http/client.go new file mode 100644 index 000000000..d6d74acee --- /dev/null +++ b/test/http/client.go @@ -0,0 +1,188 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package http + +import ( + "bytes" + "errors" + "fmt" + "io" + "net/http" + + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/golang/protobuf/jsonpb" +) + +/** + * @brief 创建HTTP客户端 + */ +func NewClient(address, version string) *Client { + return &Client{ + Address: address, + Version: version, + Worker: &http.Client{}, + } +} + +/** + * @brief HTTP客户端 + */ +type Client struct { + Address string + Version string + Worker *http.Client +} + +/** + * @brief HTTP Post/Put + */ +func (c *Client) SendRequest(method string, url string, body *bytes.Buffer) (*http.Response, error) { + var request *http.Request + var err error + + if body == nil { + request, err = http.NewRequest(method, url, nil) + } else { + request, err = http.NewRequest(method, url, body) + } + + if err != nil { + return nil, err + } + + request.Header.Add("Content-Type", "application/json") + request.Header.Add("Request-Id", "test") + + response, err := c.Worker.Do(request) + if err != nil { + return nil, err + } + + return response, nil +} + +/** + * @brief 生成GET请求的完整URL + */ +func (c *Client) CompleteURL(url string, params map[string][]interface{}) string { + count := 1 + url += "?" + + num := 0 + for _, param := range params { + num += len(param) + } + + for index, param := range params { + for _, item := range param { + url += fmt.Sprintf("%v=%v", index, item) + if count != num { + url += "&" + } + count++ + } + } + return url +} + +/** + * @brief 获取BatchWriteResponse + */ +func GetBatchWriteResponse(response *http.Response) (*api.BatchWriteResponse, error) { + // 打印回复 + fmt.Printf("http code: %v\n", response.StatusCode) + + ret := &api.BatchWriteResponse{} + checkErr := jsonpb.Unmarshal(response.Body, ret) + if checkErr == nil { + fmt.Printf("%+v\n", ret) + } else { + fmt.Printf("%v\n", checkErr) + } + + // 检查回复 + if response.StatusCode != 200 { + return nil, errors.New("invalid http code") + } + + if checkErr == nil { + return ret, nil + } else if checkErr == io.EOF { + return nil, io.EOF + } else { + return nil, errors.New("body decode failed") + } +} + +/** + * @brief 获取BatchQueryResponse + */ +func GetBatchQueryResponse(response *http.Response) (*api.BatchQueryResponse, error) { + // 打印回复 + fmt.Printf("http code: %v\n", response.StatusCode) + + ret := &api.BatchQueryResponse{} + checkErr := jsonpb.Unmarshal(response.Body, ret) + if checkErr == nil { + fmt.Printf("%+v\n", ret) + } else { + fmt.Printf("%v\n", checkErr) + } + + // 检查回复 + if response.StatusCode != 200 { + return nil, errors.New("invalid http code") + } + + if checkErr == nil { + return ret, nil + } else if checkErr == io.EOF { + return nil, io.EOF + } else { + return nil, errors.New("body decode failed") + } +} + +/** + * @brief 获取SimpleResponse + */ +func GetSimpleResponse(response *http.Response) (*api.Response, error) { + // 打印回复 + fmt.Printf("http code: %v\n", response.StatusCode) + + ret := &api.Response{} + checkErr := jsonpb.Unmarshal(response.Body, ret) + if checkErr == nil { + fmt.Printf("%+v\n", ret) + } else { + fmt.Printf("%v\n", checkErr) + } + + // 检查回复 + if response.StatusCode != 200 { + return nil, errors.New("invalid http code") + } + + if checkErr == nil { + return ret, nil + } else if checkErr == io.EOF { + return nil, io.EOF + } else { + return nil, errors.New("body decode failed") + } +} diff --git a/test/http/instance.go b/test/http/instance.go new file mode 100644 index 000000000..609675488 --- /dev/null +++ b/test/http/instance.go @@ -0,0 +1,307 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package http + +import ( + "bytes" + "errors" + "fmt" + "io" + "reflect" + + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/golang/protobuf/jsonpb" +) + +/** + * @brief 实例数组转JSON + */ +func JSONFromInstances(instances []*api.Instance) (*bytes.Buffer, error) { + m := jsonpb.Marshaler{Indent: " "} + + buffer := bytes.NewBuffer([]byte{}) + + buffer.Write([]byte("[")) + for index, instance := range instances { + if index > 0 { + buffer.Write([]byte(",\n")) + } + err := m.Marshal(buffer, instance) + if err != nil { + return nil, err + } + } + + buffer.Write([]byte("]")) + return buffer, nil +} + +/** + * @brief 创建实例 + */ +func (c *Client) CreateInstances(instances []*api.Instance) (*api.BatchWriteResponse, error) { + fmt.Printf("\ncreate instances\n") + + url := fmt.Sprintf("http://%v/naming/%v/instances", c.Address, c.Version) + + body, err := JSONFromInstances(instances) + if err != nil { + fmt.Printf("%v\n", err) + return nil, err + } + + response, err := c.SendRequest("POST", url, body) + if err != nil { + fmt.Printf("%v\n", err) + return nil, err + } + + ret, err := GetBatchWriteResponse(response) + if err != nil { + fmt.Printf("%v\n", err) + return nil, err + } + + return checkCreateInstancesResponse(ret, instances) +} + +/** + * @brief 删除实例 + */ +func (c *Client) DeleteInstances(instances []*api.Instance) error { + fmt.Printf("\ndelete instances\n") + + url := fmt.Sprintf("http://%v/naming/%v/instances/delete", c.Address, c.Version) + + body, err := JSONFromInstances(instances) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + response, err := c.SendRequest("POST", url, body) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + _, err = GetBatchWriteResponse(response) + if err != nil { + if err == io.EOF { + return nil + } + + fmt.Printf("%v\n", err) + return err + } + + return nil +} + +/** + * @brief 更新实例 + */ +func (c *Client) UpdateInstances(instances []*api.Instance) error { + fmt.Printf("\nupdate instances\n") + + url := fmt.Sprintf("http://%v/naming/%v/instances", c.Address, c.Version) + + body, err := JSONFromInstances(instances) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + response, err := c.SendRequest("PUT", url, body) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + _, err = GetBatchWriteResponse(response) + if err != nil { + if err == io.EOF { + return nil + } + + fmt.Printf("%v\n", err) + return err + } + + return nil +} + +/** + * @brief 查询实例 + */ +func (c *Client) GetInstances(instances []*api.Instance) error { + fmt.Printf("\nget instances\n") + + url := fmt.Sprintf("http://%v/naming/%v/instances", c.Address, c.Version) + + params := map[string][]interface{}{ + "service": {instances[0].GetService().GetValue()}, + "namespace" :{instances[0].GetNamespace().GetValue()}, + } + + url = c.CompleteURL(url, params) + response, err := c.SendRequest("GET", url, nil) + if err != nil { + return err + } + + ret, err := GetBatchQueryResponse(response) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + if ret.GetCode() == nil || ret.GetCode().GetValue() != api.ExecuteSuccess { + return errors.New("invalid batch code") + } + + instancesSize := len(instances) + + if ret.GetAmount() == nil || ret.GetAmount().GetValue() != uint32(instancesSize) { + return errors.New("invalid batch amount") + } + + if ret.GetSize() == nil || ret.GetSize().GetValue() != uint32(instancesSize) { + return errors.New("invalid batch size") + } + + collection := make(map[string]*api.Instance) + for _, instance := range instances { + collection[instance.GetId().GetValue()] = instance + } + + items := ret.GetInstances() + if items == nil || len(items) != instancesSize { + return errors.New("invalid batch instances") + } + + for _, item := range items { + if correctItem, ok := collection[item.GetId().GetValue()]; ok { + if result := compareInstance(correctItem, item); !result { + return fmt.Errorf("invalid instance %v", item.GetId().GetValue()) + } + } else { + return fmt.Errorf("instance %v not found", item.GetId().GetValue()) + } + } + return nil +} + +/** + * @brief 检查创建实例的回复 + */ +func checkCreateInstancesResponse(ret *api.BatchWriteResponse, instances []*api.Instance) ( + *api.BatchWriteResponse, error) { + + switch { + case ret.GetCode().GetValue() != api.ExecuteSuccess: + return nil, errors.New("invalid batch code") + case ret.GetSize().GetValue() != uint32(len(instances)): + return nil, errors.New("invalid batch size") + case len(ret.GetResponses()) != len(instances): + return nil, errors.New("invalid batch response") + } + + return ret, checkInstancesResponseEntry(ret, instances) +} + +/** + * @brief 检查创建实例每个实例的信息 + */ +func checkInstancesResponseEntry(ret *api.BatchWriteResponse, instances []*api.Instance) error { + items := ret.GetResponses() + for index, item := range items { + instance := item.GetInstance() + switch { + case item.GetCode().GetValue() != api.ExecuteSuccess: + return errors.New("invalid code") + case item.GetInstance() == nil: + return errors.New("empty instance") + case item.GetInstance().GetId().GetValue() == "": + return errors.New("invalid instance id") + case instance.GetService().GetValue() != instances[index].GetService().GetValue(): + return errors.New("invalid service") + case instance.GetNamespace().GetValue() != instances[index].GetNamespace().GetValue(): + return errors.New("invalid namespace") + case instance.GetHost().GetValue() != instances[index].GetHost().GetValue(): + return errors.New("invalid host") + case instance.GetPort().GetValue() != instances[index].GetPort().GetValue(): + return errors.New("invalid port") + } + } + + return nil +} + +/** + * @brief 比较instance是否相等 + */ +func compareInstance(correctItem *api.Instance, item *api.Instance) bool { + // #lizard forgives + correctID := correctItem.GetId().GetValue() + correctService := correctItem.GetService().GetValue() + correctNamespace := correctItem.GetNamespace().GetValue() + correctHost := correctItem.GetHost().GetValue() + correctPort := correctItem.GetPort().GetValue() + correctProtocol := correctItem.GetProtocol().GetValue() + correctVersion := correctItem.GetVersion().GetValue() + correctPriority := correctItem.GetPriority().GetValue() + correctWeight := correctItem.GetWeight().GetValue() + correctHealthType := correctItem.GetHealthCheck().GetType() + correctHealthTTL := correctItem.GetHealthCheck().GetHeartbeat().GetTtl().GetValue() + correctHealthy := correctItem.GetHealthy().GetValue() + correctIsolate := correctItem.GetIsolate().GetValue() + correctMeta := correctItem.GetMetadata() + correctLogicSet := correctItem.GetLogicSet().GetValue() + /*correctCmdbRegion := correctItem.GetLocation().GetRegion().GetValue() + correctCmdbZone := correctItem.GetLocation().GetZone().GetValue() + correctCmdbCampus := correctItem.GetLocation().GetCampus().GetValue()*/ + + id := item.GetId().GetValue() + service := item.GetService().GetValue() + namespace := item.GetNamespace().GetValue() + host := item.GetHost().GetValue() + port := item.GetPort().GetValue() + protocol := item.GetProtocol().GetValue() + version := item.GetVersion().GetValue() + priority := item.GetPriority().GetValue() + weight := item.GetWeight().GetValue() + healthType := item.GetHealthCheck().GetType() + healthTTL := item.GetHealthCheck().GetHeartbeat().GetTtl().GetValue() + healthy := item.GetHealthy().GetValue() + isolate := item.GetIsolate().GetValue() + meta := item.GetMetadata() + logicSet := item.GetLogicSet().GetValue() + /*cmdbRegion := item.GetLocation().GetRegion().GetValue() + cmdbZone := item.GetLocation().GetZone().GetValue() + cmdbCampus := item.GetLocation().GetCampus().GetValue()*/ + + if correctID == id && correctService == service && correctNamespace == namespace && correctHost == host && + correctPort == port && correctProtocol == protocol && correctVersion == version && + correctPriority == priority && correctWeight == weight && correctHealthType == healthType && + correctHealthTTL == healthTTL && correctHealthy == healthy && correctIsolate == isolate && + reflect.DeepEqual(correctMeta, meta) && correctLogicSet == logicSet { + return true + } + return false +} diff --git a/test/http/namespace.go b/test/http/namespace.go new file mode 100644 index 000000000..483c80242 --- /dev/null +++ b/test/http/namespace.go @@ -0,0 +1,266 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package http + +import ( + "bytes" + "errors" + "fmt" + "io" + + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/golang/protobuf/jsonpb" +) + +/** + * @brief 命名空间数组转JSON + */ +func JSONFromNamespaces(namespaces []*api.Namespace) (*bytes.Buffer, error) { + m := jsonpb.Marshaler{Indent: " "} + + buffer := bytes.NewBuffer([]byte{}) + + buffer.Write([]byte("[")) + for index, namespace := range namespaces { + if index > 0 { + buffer.Write([]byte(",\n")) + } + err := m.Marshal(buffer, namespace) + if err != nil { + return nil, err + } + } + + buffer.Write([]byte("]")) + + return buffer, nil +} + +/** + * @brief 创建命名空间 + */ +func (c *Client) CreateNamespaces(namespaces []*api.Namespace) (*api.BatchWriteResponse, error) { + fmt.Printf("\ncreate namespaces\n") + + url := fmt.Sprintf("http://%v/naming/%v/namespaces", c.Address, c.Version) + + body, err := JSONFromNamespaces(namespaces) + if err != nil { + fmt.Printf("%v\n", err) + return nil, err + } + + response, err := c.SendRequest("POST", url, body) + if err != nil { + fmt.Printf("%v\n", err) + return nil, err + } + + ret, err := GetBatchWriteResponse(response) + if err != nil { + fmt.Printf("%v\n", err) + return nil, err + } + + return checkCreateNamespacesResponse(ret, namespaces) +} + +/** + * @brief 删除命名空间 + */ +func (c *Client) DeleteNamespaces(namespaces []*api.Namespace) error { + fmt.Printf("\ndelete namespaces\n") + + url := fmt.Sprintf("http://%v/naming/%v/namespaces/delete", c.Address, c.Version) + + body, err := JSONFromNamespaces(namespaces) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + response, err := c.SendRequest("POST", url, body) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + _, err = GetBatchWriteResponse(response) + if err != nil { + if err == io.EOF { + return nil + } + + fmt.Printf("%v\n", err) + return err + } + + return nil +} + +/** + * @brief 更新命名空间 + */ +func (c *Client) UpdateNamesapces(namespaces []*api.Namespace) error { + fmt.Printf("\nupdate namespaces\n") + + url := fmt.Sprintf("http://%v/naming/%v/namespaces", c.Address, c.Version) + + body, err := JSONFromNamespaces(namespaces) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + response, err := c.SendRequest("PUT", url, body) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + _, err = GetBatchWriteResponse(response) + if err != nil { + if err == io.EOF { + return nil + } + + fmt.Printf("%v\n", err) + return err + } + + return nil +} + +/** + * @brief 查询命名空间 + */ +func (c *Client) GetNamespaces(namespaces []*api.Namespace) error { + fmt.Printf("\nget namespaces\n") + + url := fmt.Sprintf("http://%v/naming/%v/namespaces", c.Address, c.Version) + + params := map[string][]interface{}{ + "name": {namespaces[0].GetName().GetValue(), namespaces[1].GetName().GetValue()}, + } + + url = c.CompleteURL(url, params) + response, err := c.SendRequest("GET", url, nil) + if err != nil { + return err + } + + ret, err := GetBatchQueryResponse(response) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + if ret.GetCode() == nil || ret.GetCode().GetValue() != api.ExecuteSuccess { + return errors.New("invalid batch code") + } + + namespacesSize := len(namespaces) + + if ret.GetAmount() == nil || ret.GetAmount().GetValue() != uint32(namespacesSize) { + return errors.New("invalid batch amount") + } + + if ret.GetSize() == nil || ret.GetSize().GetValue() != uint32(namespacesSize) { + return errors.New("invalid batch size") + } + + collection := make(map[string]*api.Namespace) + for _, namespace := range namespaces { + collection[namespace.GetName().GetValue()] = namespace + } + + items := ret.GetNamespaces() + if items == nil || len(items) != namespacesSize { + return errors.New("invalid batch namespaces") + } + + for _, item := range items { + if correctItem, ok := collection[item.GetName().GetValue()]; ok { + if result := compareNamespace(correctItem, item); !result { + return errors.New("invalid namespace") + } + } else { + return errors.New("invalid namespace") + } + } + return nil +} + +/** + * @brief 检查创建命名空间的回复 + */ +func checkCreateNamespacesResponse(ret *api.BatchWriteResponse, namespaces []*api.Namespace) ( + *api.BatchWriteResponse, error) { + if ret.GetCode() == nil || ret.GetCode().GetValue() != api.ExecuteSuccess { + return nil, errors.New("invalid batch code") + } + + namespacesSize := len(namespaces) + if ret.GetSize() == nil || ret.GetSize().GetValue() != uint32(namespacesSize) { + return nil, errors.New("invalid batch size") + } + + items := ret.GetResponses() + if items == nil || len(items) != namespacesSize { + return nil, errors.New("invalid batch response") + } + + for index, item := range items { + if item.GetCode() == nil || item.GetCode().GetValue() != api.ExecuteSuccess { + return nil, errors.New("invalid code") + } + + namespace := item.GetNamespace() + if namespace == nil { + return nil, errors.New("empty namespace") + } + + name := namespaces[index].GetName().GetValue() + if namespace.GetName() == nil || namespace.GetName().GetValue() != name { + return nil, errors.New("invalid namespace name") + } + + if namespace.GetToken() == nil || namespace.GetToken().GetValue() == "" { + return nil, errors.New("invalid namespace token") + } + } + return ret, nil +} + +/** + * @brief 比较namespace是否相等 + */ +func compareNamespace(correctItem *api.Namespace, item *api.Namespace) bool { + correctName := correctItem.GetName().GetValue() + correctComment := correctItem.GetComment().GetValue() + correctOwners := correctItem.GetOwners().GetValue() + + name := item.GetName().GetValue() + comment := item.GetComment().GetValue() + owners := item.GetOwners().GetValue() + + if correctName == name && correctComment == comment && correctOwners == owners { + return true + } + return false +} diff --git a/test/http/platform.go b/test/http/platform.go new file mode 100644 index 000000000..5e976e584 --- /dev/null +++ b/test/http/platform.go @@ -0,0 +1,265 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package http + +import ( + "bytes" + "errors" + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/golang/protobuf/jsonpb" + "io" +) + +/** + * @brief 平台数据转JSON + */ +func JSONFromPlatforms(platforms []*api.Platform) (*bytes.Buffer, error) { + m := jsonpb.Marshaler{Indent: " "} + + buffer := bytes.NewBuffer([]byte{}) + + buffer.Write([]byte("[")) + for index, service := range platforms { + if index > 0 { + buffer.Write([]byte(",\n")) + } + err := m.Marshal(buffer, service) + if err != nil { + return nil, err + } + } + + buffer.Write([]byte("]")) + return buffer, nil +} + +/** + * @brief 创建平台 + */ +func (c *Client) CreatePlatforms(platforms []*api.Platform) (*api.BatchWriteResponse, error) { + fmt.Printf("\ncreate platforms\n") + + url := fmt.Sprintf("http://%v/naming/%v/platforms", c.Address, c.Version) + + body, err := JSONFromPlatforms(platforms) + if err != nil { + fmt.Printf("%v\n", err) + return nil, err + } + + response, err := c.SendRequest("POST", url, body) + if err != nil { + fmt.Printf("%v\n", err) + return nil, err + } + + ret, err := GetBatchWriteResponse(response) + if err != nil { + fmt.Printf("%v\n", err) + return nil, err + } + + return checkCreatePlatformsResponse(ret, platforms) +} + +/** + * @brief 删除平台 + */ +func (c *Client) DeletePlatforms(platforms []*api.Platform) error { + fmt.Printf("\ndelete platforms\n") + + url := fmt.Sprintf("http://%v/naming/%v/platforms/delete", c.Address, c.Version) + + body, err := JSONFromPlatforms(platforms) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + response, err := c.SendRequest("POST", url, body) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + _, err = GetBatchWriteResponse(response) + if err != nil { + if err == io.EOF { + return nil + } + + fmt.Printf("%v\n", err) + return err + } + return nil +} + +/** + * @brief 更新平台 + */ +func (c *Client) UpdatePlatforms(platforms []*api.Platform) error { + fmt.Printf("\nupdate platforms\n") + + url := fmt.Sprintf("http://%v/naming/%v/platforms", c.Address, c.Version) + + body, err := JSONFromPlatforms(platforms) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + response, err := c.SendRequest("PUT", url, body) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + _, err = GetBatchWriteResponse(response) + if err != nil { + if err == io.EOF { + return nil + } + + fmt.Printf("%v\n", err) + return err + } + return nil +} + +/** + * @brief 查询平台 + */ +func (c *Client) GetPlatforms(platforms []*api.Platform) error { + fmt.Printf("\nget platforms\n") + + url := fmt.Sprintf("http://%v/naming/%v/platforms", c.Address, c.Version) + + params := map[string][]interface{}{ + "name": {platforms[0].GetName().GetValue()}, + } + + url = c.CompleteURL(url, params) + response, err := c.SendRequest("GET", url, nil) + if err != nil { + return err + } + + ret, err := GetBatchQueryResponse(response) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + if ret.GetCode() == nil || ret.GetCode().GetValue() != api.ExecuteSuccess { + return fmt.Errorf("invalid batch code: %v", ret.GetCode().GetValue()) + } + + platformsSize := len(platforms) + + if ret.GetAmount() == nil || ret.GetAmount().GetValue() != uint32(platformsSize) { + return fmt.Errorf("invalid batch amount: %d, expect amount is %d", + ret.GetAmount().GetValue(), platformsSize) + } + + if ret.GetSize() == nil || ret.GetSize().GetValue() != uint32(platformsSize) { + return fmt.Errorf("invalid batch size: %d, expect size is %d", + ret.GetSize().GetValue(), platformsSize) + } + + collection := make(map[string]*api.Platform) + for _, platform := range platforms { + collection[platform.GetId().GetValue()] = platform + } + + items := ret.GetPlatforms() + if items == nil || len(items) != platformsSize { + return errors.New("invalid batch platforms") + } + + for _, item := range items { + if correctItem, ok := collection[item.GetId().GetValue()]; ok { + if result, err := comparePlatform(correctItem, item); !result { + return fmt.Errorf("invalid platform. id is %s, err is %s", item.GetId().GetValue(), err.Error()) + } + } else { + return fmt.Errorf("platform not found. id is %s", item.GetId().GetValue()) + } + } + return nil +} + +/** + * @brief 检查创建平台的回复 + */ +func checkCreatePlatformsResponse(ret *api.BatchWriteResponse, platforms []*api.Platform) ( + *api.BatchWriteResponse, error) { + if ret.GetCode() == nil || ret.GetCode().GetValue() != api.ExecuteSuccess { + return nil, fmt.Errorf("invalid batch code: %v", ret.GetCode().GetValue()) + } + + platformsSize := len(platforms) + if ret.GetSize() == nil || ret.GetSize().GetValue() != uint32(platformsSize) { + return nil, fmt.Errorf("invalid batch size, expect size is %d, actual size is %d", + platformsSize, ret.GetSize().GetValue()) + } + + items := ret.GetResponses() + if items == nil || len(items) != platformsSize { + return nil, errors.New("invalid batch response") + } + + for index, item := range items { + if item.GetCode() == nil || item.GetCode().GetValue() != api.ExecuteSuccess { + return nil, fmt.Errorf("invalid code: %v", item.GetCode().GetValue()) + } + + platform := item.GetPlatform() + if platform == nil { + return nil, errors.New("empty platform") + } + if _, err := comparePlatform(platforms[index], platform); err != nil { + return nil, fmt.Errorf("invalid platform. id is %s, err is %s", + platform.GetId().GetValue(), err.Error()) + } + } + return ret, nil +} + +/** + * @brief 比较平台信息是否相等 + */ +func comparePlatform(correctItem *api.Platform, item *api.Platform) (bool, error) { + switch { + case correctItem.GetId().GetValue() != item.GetId().GetValue(): + return false, errors.New("error id") + case correctItem.GetName().GetValue() != item.GetName().GetValue(): + return false, errors.New("error name") + case correctItem.GetDomain().GetValue() != item.GetDomain().GetValue(): + return false, errors.New("error domain") + case correctItem.GetQps().GetValue() != item.GetQps().GetValue(): + return false, errors.New("error qps") + case correctItem.GetOwner().GetValue() != item.GetOwner().GetValue(): + return false, errors.New("error owner") + case correctItem.GetDepartment().GetValue() != item.GetDepartment().GetValue(): + return false, errors.New("error department") + case correctItem.GetComment().GetValue() != item.GetComment().GetValue(): + return false, errors.New("error comment") + } + return true, nil +} diff --git a/test/http/ratelimit_config.go b/test/http/ratelimit_config.go new file mode 100644 index 000000000..e564c8711 --- /dev/null +++ b/test/http/ratelimit_config.go @@ -0,0 +1,307 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package http + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/golang/protobuf/jsonpb" + "io" +) + +/** + * @brief 限流规则数组转JSON + */ +func JSONFromRateLimits(rateLimits []*api.Rule) (*bytes.Buffer, error) { + m := jsonpb.Marshaler{Indent: " "} + + buffer := bytes.NewBuffer([]byte{}) + + buffer.Write([]byte("[")) + for index, rateLimit := range rateLimits { + if index > 0 { + buffer.Write([]byte(",\n")) + } + err := m.Marshal(buffer, rateLimit) + if err != nil { + return nil, err + } + } + + buffer.Write([]byte("]")) + return buffer, nil +} + +/** + * @brief 创建限流规则 + */ +func (c *Client) CreateRateLimits(rateLimits []*api.Rule) (*api.BatchWriteResponse, error) { + fmt.Printf("\ncreate rate limits\n") + + url := fmt.Sprintf("http://%v/naming/%v/ratelimits", c.Address, c.Version) + + body, err := JSONFromRateLimits(rateLimits) + if err != nil { + fmt.Printf("%v\n", err) + return nil, err + } + + response, err := c.SendRequest("POST", url, body) + if err != nil { + fmt.Printf("%v\n", err) + return nil, err + } + + ret, err := GetBatchWriteResponse(response) + if err != nil { + fmt.Printf("%v\n", err) + return nil, err + } + + return checkCreateRateLimitsResponse(ret, rateLimits) +} + +/** + * @brief 删除限流规则 + */ +func (c *Client) DeleteRateLimits(rateLimits []*api.Rule) error { + fmt.Printf("\ndelete rate limits\n") + + url := fmt.Sprintf("http://%v/naming/%v/ratelimits/delete", c.Address, c.Version) + + body, err := JSONFromRateLimits(rateLimits) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + response, err := c.SendRequest("POST", url, body) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + _, err = GetBatchWriteResponse(response) + if err != nil { + if err == io.EOF { + return nil + } + + fmt.Printf("%v\n", err) + return err + } + return nil +} + +/** + * @brief 更新限流规则 + */ +func (c *Client) UpdateRateLimits(rateLimits []*api.Rule) error { + fmt.Printf("\nupdate rate limits\n") + + url := fmt.Sprintf("http://%v/naming/%v/ratelimits", c.Address, c.Version) + + body, err := JSONFromRateLimits(rateLimits) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + response, err := c.SendRequest("PUT", url, body) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + _, err = GetBatchWriteResponse(response) + if err != nil { + if err == io.EOF { + return nil + } + + fmt.Printf("%v\n", err) + return err + } + return nil +} + +/** + * @brief 查询限流规则 + */ +func (c *Client) GetRateLimits(rateLimits []*api.Rule) error { + fmt.Printf("\nget rate limits\n") + + url := fmt.Sprintf("http://%v/naming/%v/ratelimits", c.Address, c.Version) + + params := map[string][]interface{}{ + "namespace": {rateLimits[0].GetNamespace().GetValue()}, + } + + url = c.CompleteURL(url, params) + response, err := c.SendRequest("GET", url, nil) + if err != nil { + return err + } + + ret, err := GetBatchQueryResponse(response) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + if ret.GetCode() == nil || ret.GetCode().GetValue() != api.ExecuteSuccess { + return errors.New("invalid batch code") + } + + rateLimitsSize := len(rateLimits) + + if ret.GetAmount() == nil || ret.GetAmount().GetValue() != uint32(rateLimitsSize) { + return errors.New("invalid batch amount") + } + + if ret.GetSize() == nil || ret.GetSize().GetValue() != uint32(rateLimitsSize) { + return errors.New("invalid batch size") + } + + collection := make(map[string]*api.Rule) + for _, rateLimit := range rateLimits { + collection[rateLimit.GetService().GetValue()] = rateLimit + } + + items := ret.GetRateLimits() + if items == nil || len(items) != rateLimitsSize { + return errors.New("invalid batch rate limits") + } + + for _, item := range items { + if correctItem, ok := collection[item.GetService().GetValue()]; ok { + if result, err := compareRateLimit(correctItem, item); !result { + return fmt.Errorf("invalid rate limit. namespace is %v, service is %v, err is %s", + item.GetNamespace().GetValue(), item.GetService().GetValue(), err.Error()) + } + } else { + return fmt.Errorf("rate limit not found. namespace is %v, service is %v", + item.GetNamespace().GetValue(), item.GetService().GetValue()) + } + } + return nil +} + +/** + * @brief 检查创建限流规则的回复 + */ +func checkCreateRateLimitsResponse(ret *api.BatchWriteResponse, rateLimits []*api.Rule) ( + *api.BatchWriteResponse, error) { + switch { + case ret.GetCode().GetValue() != api.ExecuteSuccess: + return nil, errors.New("invalid batch code") + case ret.GetSize().GetValue() != uint32(len(rateLimits)): + return nil, errors.New("invalid batch size") + case len(ret.GetResponses()) != len(rateLimits): + return nil, errors.New("invalid batch response") + } + + for index, item := range ret.GetResponses() { + if item.GetCode().GetValue() != api.ExecuteSuccess { + return nil, errors.New("invalid code") + } + rateLimit := item.GetRateLimit() + if rateLimit == nil { + return nil, errors.New("empty rate limit") + } + if result, err := compareRateLimit(rateLimits[index], rateLimit); !result { + return nil, err + } + } + return ret, nil +} + +/** + * @brief 比较rate limit是否相等 + */ +func compareRateLimit(correctItem *api.Rule, item *api.Rule) (bool, error) { + switch { + case (correctItem.GetId().GetValue()) != "" && (correctItem.GetId().GetValue() != item.GetId().GetValue()): + return false, errors.New("invalid id") + case correctItem.GetService().GetValue() != item.GetService().GetValue(): + return false, errors.New("error service") + case correctItem.GetNamespace().GetValue() != item.GetNamespace().GetValue(): + return false, errors.New("error namespace") + case correctItem.GetPriority().GetValue() != item.GetPriority().GetValue(): + return false, errors.New("invalid priority") + case correctItem.GetResource() != item.GetResource(): + return false, errors.New("invalid resource") + case correctItem.GetType() != item.GetType(): + return false, errors.New("error type") + case correctItem.GetAction().GetValue() != item.GetAction().GetValue(): + return false, errors.New("error action") + case correctItem.GetDisable().GetValue() != item.GetDisable().GetValue(): + return false, errors.New("error disable") + case correctItem.GetRegexCombine().GetValue() != item.GetRegexCombine().GetValue(): + return false, errors.New("error regex combine") + case correctItem.GetAmountMode() != item.GetAmountMode(): + return false, errors.New("error amount mode") + case correctItem.GetFailover() != item.GetFailover(): + return false, errors.New("error fail over") + default: + break + } + + if equal, err := checkField(correctItem.GetSubset(), item.GetSubset(), "subset"); !equal { + return equal, err + } + if equal, err := checkField(correctItem.GetLabels(), item.GetLabels(), "labels"); !equal { + return equal, err + } + + if equal, err := checkField(correctItem.GetAmounts(), item.GetAmounts(), "amounts"); !equal { + return equal, err + } + + if equal, err := checkField(correctItem.GetReport(), item.GetReport(), "report"); !equal { + return equal, err + } + + if equal, err := checkField(correctItem.GetAdjuster(), item.GetAdjuster(), "adjuster"); !equal { + return equal, err + } + + return checkField(correctItem.GetCluster(), item.GetCluster(), "cluster") +} + +/** + * @brief 检查字段是否一致 + */ +func checkField(correctItem, actualItem interface{}, name string) (bool, error) { + expect, err := json.Marshal(correctItem) + if err != nil { + panic(err) + } + actual, err := json.Marshal(actualItem) + if err != nil { + panic(err) + } + + if string(expect) != string(actual) { + return false, fmt.Errorf("error %s", name) + } + return true, nil +} diff --git a/test/http/service.go b/test/http/service.go new file mode 100644 index 000000000..134586d86 --- /dev/null +++ b/test/http/service.go @@ -0,0 +1,291 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package http + +import ( + "bytes" + "errors" + "fmt" + "io" + "reflect" + + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/golang/protobuf/jsonpb" +) + +/** + * @brief 服务数组转JSON + */ +func JSONFromServices(services []*api.Service) (*bytes.Buffer, error) { + m := jsonpb.Marshaler{Indent: " "} + + buffer := bytes.NewBuffer([]byte{}) + + buffer.Write([]byte("[")) + for index, service := range services { + if index > 0 { + buffer.Write([]byte(",\n")) + } + err := m.Marshal(buffer, service) + if err != nil { + return nil, err + } + } + + buffer.Write([]byte("]")) + return buffer, nil +} + +/** + * @brief 创建服务 + */ +func (c *Client) CreateServices(services []*api.Service) (*api.BatchWriteResponse, error) { + fmt.Printf("\ncreate services\n") + + url := fmt.Sprintf("http://%v/naming/%v/services", c.Address, c.Version) + + body, err := JSONFromServices(services) + if err != nil { + fmt.Printf("%v\n", err) + return nil, err + } + + response, err := c.SendRequest("POST", url, body) + if err != nil { + fmt.Printf("%v\n", err) + return nil, err + } + + ret, err := GetBatchWriteResponse(response) + if err != nil { + fmt.Printf("%v\n", err) + return nil, err + } + + return checkCreateServicesResponse(ret, services) +} + +/** + * @brief 删除服务 + */ +func (c *Client) DeleteServices(services []*api.Service) error { + fmt.Printf("\ndelete services\n") + + url := fmt.Sprintf("http://%v/naming/%v/services/delete", c.Address, c.Version) + + body, err := JSONFromServices(services) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + response, err := c.SendRequest("POST", url, body) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + _, err = GetBatchWriteResponse(response) + if err != nil { + if err == io.EOF { + return nil + } + + fmt.Printf("%v\n", err) + return err + } + + return nil +} + +/** + * @brief 更新服务 + */ +func (c *Client) UpdateServices(services []*api.Service) error { + fmt.Printf("\nupdate services\n") + + url := fmt.Sprintf("http://%v/naming/%v/services", c.Address, c.Version) + + body, err := JSONFromServices(services) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + response, err := c.SendRequest("PUT", url, body) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + _, err = GetBatchWriteResponse(response) + if err != nil { + if err == io.EOF { + return nil + } + + fmt.Printf("%v\n", err) + return err + } + + return nil +} + +/** + * @brief 查询服务 + */ +func (c *Client) GetServices(services []*api.Service) error { + fmt.Printf("\nget services\n") + + url := fmt.Sprintf("http://%v/naming/%v/services", c.Address, c.Version) + + params := map[string][]interface{}{ + "namespace": {services[0].GetNamespace().GetValue()}, + } + + url = c.CompleteURL(url, params) + response, err := c.SendRequest("GET", url, nil) + if err != nil { + return err + } + + ret, err := GetBatchQueryResponse(response) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + if ret.GetCode() == nil || ret.GetCode().GetValue() != api.ExecuteSuccess { + return errors.New("invalid batch code") + } + + servicesSize := len(services) + + if ret.GetAmount() == nil || ret.GetAmount().GetValue() != uint32(servicesSize) { + return errors.New("invalid batch amount") + } + + if ret.GetSize() == nil || ret.GetSize().GetValue() != uint32(servicesSize) { + return errors.New("invalid batch size") + } + + collection := make(map[string]*api.Service) + for _, service := range services { + collection[service.GetName().GetValue()] = service + } + + items := ret.GetServices() + if items == nil || len(items) != servicesSize { + return errors.New("invalid batch services") + } + + for _, item := range items { + if correctItem, ok := collection[item.GetName().GetValue()]; ok { + if result := compareService(correctItem, item); !result { + return errors.New("invalid service") + } + } else { + return errors.New("invalid service") + } + } + return nil +} + +/** + * @brief 检查创建服务的回复 + */ +func checkCreateServicesResponse(ret *api.BatchWriteResponse, services []*api.Service) ( + // #lizard forgives + *api.BatchWriteResponse, error) { + if ret.GetCode() == nil || ret.GetCode().GetValue() != api.ExecuteSuccess { + return nil, errors.New("invalid batch code") + } + + servicesSize := len(services) + if ret.GetSize() == nil || ret.GetSize().GetValue() != uint32(servicesSize) { + return nil, errors.New("invalid batch size") + } + + items := ret.GetResponses() + if items == nil || len(items) != servicesSize { + return nil, errors.New("invalid batch response") + } + + for index, item := range items { + if item.GetCode() == nil || item.GetCode().GetValue() != api.ExecuteSuccess { + return nil, errors.New("invalid code") + } + + service := item.GetService() + if service == nil { + return nil, errors.New("empty service") + } + + name := services[index].GetName().GetValue() + if service.GetName() == nil || service.GetName().GetValue() != name { + return nil, errors.New("invalid service name") + } + + namespace := services[index].GetNamespace().GetValue() + if service.GetNamespace() == nil || service.GetNamespace().GetValue() != namespace { + return nil, errors.New("invalid namespace") + } + + if service.GetToken() == nil || service.GetToken().GetValue() == "" { + return nil, errors.New("invalid service token") + } + } + return ret, nil +} + +/** + * @brief 比较service是否相等 + */ +func compareService(correctItem *api.Service, item *api.Service) bool { + correctName := correctItem.GetName().GetValue() + correctNamespace := correctItem.GetNamespace().GetValue() + correctMeta := correctItem.GetMetadata() + correctPorts := correctItem.GetPorts().GetValue() + correctBusiness := correctItem.GetBusiness().GetValue() + correctDepartment := correctItem.GetDepartment().GetValue() + correctCmdbMod1 := correctItem.GetCmdbMod1().GetValue() + correctCmdbMod2 := correctItem.GetCmdbMod2().GetValue() + correctCmdbMod3 := correctItem.GetCmdbMod3().GetValue() + correctComment := correctItem.GetComment().GetValue() + correctOwners := correctItem.GetOwners().GetValue() + + name := item.GetName().GetValue() + namespace := item.GetNamespace().GetValue() + meta := item.GetMetadata() + ports := item.GetPorts().GetValue() + business := item.GetBusiness().GetValue() + department := item.GetDepartment().GetValue() + cmdbMod1 := item.GetCmdbMod1().GetValue() + cmdbMod2 := item.GetCmdbMod2().GetValue() + cmdbMod3 := item.GetCmdbMod3().GetValue() + comment := item.GetComment().GetValue() + owners := item.GetOwners().GetValue() + + if correctName == name && correctNamespace == namespace && reflect.DeepEqual(correctMeta, meta) && + correctPorts == ports && correctBusiness == business && correctDepartment == department && + correctCmdbMod1 == cmdbMod1 && correctCmdbMod2 == cmdbMod2 && correctCmdbMod3 == cmdbMod3 && + correctComment == comment && correctOwners == owners { + return true + } + return false +} diff --git a/test/instance_test.go b/test/instance_test.go new file mode 100644 index 000000000..8d6c8b9e9 --- /dev/null +++ b/test/instance_test.go @@ -0,0 +1,117 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package test + +import ( + "testing" + + "github.com/polarismesh/polaris-server/test/http" + "github.com/polarismesh/polaris-server/test/resource" +) + +/** + * @brief 测试增删改查服务实例 + */ +func TestInstance(t *testing.T) { + t.Log("test instance interface") + + client := http.NewClient(httpserverAddress, httpserverVersion) + + namespaces := resource.CreateNamespaces() + services := resource.CreateServices(namespaces[0]) + + // 创建命名空间 + ret, err := client.CreateNamespaces(namespaces) + if err != nil { + t.Fatalf("create namespaces fail") + } + for index, item := range ret.GetResponses() { + namespaces[index].Token = item.GetNamespace().GetToken() + } + t.Log("create namepsaces success") + + // 创建服务 + ret, err = client.CreateServices(services) + if err != nil { + t.Fatalf("create services fail") + } + for index, item := range ret.GetResponses() { + services[index].Token = item.GetService().GetToken() + } + t.Log("create services success") + + //------------------------------------------------------- + + instances := resource.CreateInstances(services[0]) + + // 创建实例 + ret, err = client.CreateInstances(instances) + if err != nil { + t.Fatalf("create instances fail") + } + for index, item := range ret.GetResponses() { + instances[index].Id = item.GetInstance().GetId() + } + t.Log("create instances success") + + // 查询实例 + err = client.GetInstances(instances) + if err != nil { + t.Fatalf("get instances fail") + } + t.Log("get instances success") + + // 更新实例 + resource.UpdateInstances(instances) + + err = client.UpdateInstances(instances) + if err != nil { + t.Fatalf("update instances fail") + } + t.Log("update instances success") + + // 查询实例 + err = client.GetInstances(instances) + if err != nil { + t.Fatalf("get instances fail") + } + t.Log("get instances success") + + // 删除实例 + err = client.DeleteInstances(instances) + if err != nil { + t.Fatalf("delete instances fail") + } + t.Log("delete instances success") + + //------------------------------------------------------- + + // 删除服务 + err = client.DeleteServices(services) + if err != nil { + t.Fatalf("delete services fail") + } + t.Log("delete services success") + + // 删除命名空间 + err = client.DeleteNamespaces(namespaces) + if err != nil { + t.Fatalf("delete namespaces fail") + } + t.Log("delete namepsaces success") +} diff --git a/test/namespace_test.go b/test/namespace_test.go new file mode 100644 index 000000000..b2b7597fc --- /dev/null +++ b/test/namespace_test.go @@ -0,0 +1,76 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package test + +import ( + "testing" + + "github.com/polarismesh/polaris-server/test/http" + "github.com/polarismesh/polaris-server/test/resource" +) + +/** + * @brief 测试增删改查命名空间 + */ +func TestNamespace(t *testing.T) { + t.Log("test namepsace interface") + + client := http.NewClient(httpserverAddress, httpserverVersion) + + namespaces := resource.CreateNamespaces() + + // 创建命名空间 + ret, err := client.CreateNamespaces(namespaces) + if err != nil { + t.Fatalf("create namespaces fail") + } + for index, item := range ret.GetResponses() { + namespaces[index].Token = item.GetNamespace().GetToken() + } + t.Log("create namepsaces success") + + // 查询命名空间 + err = client.GetNamespaces(namespaces) + if err != nil { + t.Fatalf("get namespaces fail") + } + t.Log("get namespaces success") + + // 更新命名空间 + resource.UpdateNamespaces(namespaces) + + err = client.UpdateNamesapces(namespaces) + if err != nil { + t.Fatalf("update namespaces fail") + } + t.Log("update namespaces success") + + // 查询命名空间 + err = client.GetNamespaces(namespaces) + if err != nil { + t.Fatalf("get namespaces fail") + } + t.Log("get namespaces success") + + // 删除命名空间 + err = client.DeleteNamespaces(namespaces) + if err != nil { + t.Fatalf("delete namespaces fail") + } + t.Log("delete namepsaces success") +} diff --git a/test/platform_test.go b/test/platform_test.go new file mode 100644 index 000000000..cf58cefdc --- /dev/null +++ b/test/platform_test.go @@ -0,0 +1,75 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package test + +import ( + "github.com/polarismesh/polaris-server/test/http" + "github.com/polarismesh/polaris-server/test/resource" + "testing" +) + +/** + * @brief 测试增删改查平台 + */ +func TestPlatform(t *testing.T) { + t.Log("test platform interface") + + client := http.NewClient(httpserverAddress, httpserverVersion) + + platforms := resource.CreatePlatforms() + + // 创建平台 + ret, err := client.CreatePlatforms(platforms) + if err != nil { + t.Fatalf("create platforms fail: %s", err.Error()) + } + for index, item := range ret.GetResponses() { + platforms[index].Token = item.GetPlatform().GetToken() + } + t.Log("create platforms success") + + // 查询平台 + err = client.GetPlatforms(platforms) + if err != nil { + t.Fatalf("get platforms fail: %s", err.Error()) + } + t.Log("get platforms success") + + // 更新平台 + resource.UpdatePlatforms(platforms) + + err = client.UpdatePlatforms(platforms) + if err != nil { + t.Fatalf("update platforms fail: %s", err.Error()) + } + t.Log("update platforms success") + + // 查询平台 + err = client.GetPlatforms(platforms) + if err != nil { + t.Fatalf("get platforms fail: %s", err.Error()) + } + t.Log("get platforms success") + + // 删除平台 + err = client.DeletePlatforms(platforms) + if err != nil { + t.Fatalf("delete platforms fail: %s", err.Error()) + } + t.Log("delete platforms success") +} diff --git a/test/ratelimit_config_test.go b/test/ratelimit_config_test.go new file mode 100644 index 000000000..7c46bd106 --- /dev/null +++ b/test/ratelimit_config_test.go @@ -0,0 +1,120 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package test + +import ( + "github.com/polarismesh/polaris-server/test/http" + "github.com/polarismesh/polaris-server/test/resource" + "testing" +) + +/** + * @brief 测试增删改查限流规则 + */ + +/** + * @brief 测试增删改查限流规则 + */ +func TestRateLimit(t *testing.T) { + t.Log("test rate limit interface") + + client := http.NewClient(httpserverAddress, httpserverVersion) + + namespaces := resource.CreateNamespaces() + services := resource.CreateServices(namespaces[0]) + + // 创建命名空间 + ret, err := client.CreateNamespaces(namespaces) + if err != nil { + t.Fatalf("create namespaces fail, err is %s", err.Error()) + } + for index, item := range ret.GetResponses() { + namespaces[index].Token = item.GetNamespace().GetToken() + } + t.Log("create namepsaces success") + + // 创建服务 + ret, err = client.CreateServices(services) + if err != nil { + t.Fatalf("create services fail, err is %s", err.Error()) + } + for index, item := range ret.GetResponses() { + services[index].Token = item.GetService().GetToken() + } + t.Log("create services success") + + //------------------------------------------------------- + + rateLimits := resource.CreateRateLimits(services) + + // 创建限流规则 + ret, err = client.CreateRateLimits(rateLimits) + if err != nil { + t.Fatalf("create rate limits fail, err is %s", err.Error()) + } + for index, item := range ret.GetResponses() { + rateLimits[index].Id = item.GetRateLimit().GetId() + } + t.Log("create rate limits success") + + // 查询限流规则 + err = client.GetRateLimits(rateLimits) + if err != nil { + t.Fatalf("get rate limits fail, err is %s", err.Error()) + } + t.Log("get rate limits success") + + // 更新限流规则 + resource.UpdateRateLimits(rateLimits) + + err = client.UpdateRateLimits(rateLimits) + if err != nil { + t.Fatalf("update rate limits fail, err is %s", err.Error()) + } + t.Log("update rate limits success") + + // 查询限流规则 + err = client.GetRateLimits(rateLimits) + if err != nil { + t.Fatalf("get rate limits fail, err is %s", err.Error()) + } + t.Log("get rate limits success") + + // 删除限流规则 + err = client.DeleteRateLimits(rateLimits) + if err != nil { + t.Fatalf("delete rate limits fail, err is %s", err.Error()) + } + t.Log("delete rate limits success") + + //------------------------------------------------------- + + // 删除服务 + err = client.DeleteServices(services) + if err != nil { + t.Fatalf("delete services fail, err is %s", err.Error()) + } + t.Log("delete services success") + + // 删除命名空间 + err = client.DeleteNamespaces(namespaces) + if err != nil { + t.Fatalf("delete namespaces fail, err is %s", err.Error()) + } + t.Log("delete namespaces success") +} diff --git a/test/resource/circuitbreaker_config.go b/test/resource/circuitbreaker_config.go new file mode 100644 index 000000000..9218753cb --- /dev/null +++ b/test/resource/circuitbreaker_config.go @@ -0,0 +1,165 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package resource + +import ( + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/utils" + "github.com/golang/protobuf/ptypes/duration" +) + +const ( + circuitBreakerName = "test-name-%v" + circuitBreakerNamespace = "test-namespace-%v" +) + +/** + * @brief 创建测试熔断规则 + */ +func CreateCircuitBreakers(namespace *api.Namespace) []*api.CircuitBreaker { + var circuitBreakers []*api.CircuitBreaker + for index := 0; index < 2; index++ { + circuitBreaker := &api.CircuitBreaker{ + Name: utils.NewStringValue(fmt.Sprintf(circuitBreakerName, index)), + Namespace: namespace.GetName(), + Business: utils.NewStringValue("test"), + Department: utils.NewStringValue("test"), + Owners: utils.NewStringValue("test"), + Comment: utils.NewStringValue("test"), + } + ruleNum := 2 + // 填充source规则 + sources := make([]*api.SourceMatcher, 0, ruleNum) + for i := 0; i < ruleNum; i++ { + source := &api.SourceMatcher{ + Service: utils.NewStringValue(fmt.Sprintf("service-test-%d", i)), + Namespace: utils.NewStringValue(fmt.Sprintf("namespace-test-%d", i)), + Labels: map[string]*api.MatchString{ + fmt.Sprintf("name-%d", i): { + Type: api.MatchString_EXACT, + Value: utils.NewStringValue(fmt.Sprintf("value-%d", i)), + }, + fmt.Sprintf("name-%d", i+1): { + Type: api.MatchString_REGEX, + Value: utils.NewStringValue(fmt.Sprintf("value-%d", i+1)), + }, + }, + } + sources = append(sources, source) + } + + // 填充destination规则 + destinations := make([]*api.DestinationSet, 0, ruleNum) + for i := 0; i < ruleNum; i++ { + destination := &api.DestinationSet{ + Service: utils.NewStringValue(fmt.Sprintf("service-test-%d", i)), + Namespace: utils.NewStringValue(fmt.Sprintf("namespace-test-%d", i)), + Metadata: map[string]*api.MatchString{ + fmt.Sprintf("name-%d", i): { + Type: api.MatchString_EXACT, + Value: utils.NewStringValue(fmt.Sprintf("value-%d", i)), + }, + fmt.Sprintf("name-%d", i+1): { + Type: api.MatchString_REGEX, + Value: utils.NewStringValue(fmt.Sprintf("value-%d", i+1)), + }, + }, + Resource: 0, + Type: 0, + Scope: 0, + MetricWindow: &duration.Duration{ + Seconds: int64(i), + }, + MetricPrecision: utils.NewUInt32Value(uint32(i)), + UpdateInterval: &duration.Duration{ + Seconds: int64(i), + }, + } + destinations = append(destinations, destination) + } + + // 填充inbound规则 + inbounds := make([]*api.CbRule, 0, ruleNum) + for i := 0; i < ruleNum; i++ { + inbound := &api.CbRule{ + Sources: sources, + Destinations: destinations, + } + inbounds = append(inbounds, inbound) + } + // 填充outbound规则 + outbounds := make([]*api.CbRule, 0, ruleNum) + for i := 0; i < ruleNum; i++ { + outbound := &api.CbRule{ + Sources: sources, + Destinations: destinations, + } + outbounds = append(outbounds, outbound) + } + circuitBreaker.Inbounds = inbounds + circuitBreaker.Outbounds = outbounds + circuitBreakers = append(circuitBreakers, circuitBreaker) + } + return circuitBreakers +} + +/** + * @brief 更新测试熔断规则 + */ +func UpdateCircuitBreakers(circuitBreakers []*api.CircuitBreaker) { + for _, item := range circuitBreakers { + item.Inbounds = []*api.CbRule{} + item.Outbounds = []*api.CbRule{} + } +} + +/** + * @brief 创建熔断规则版本 + */ +func CreateCircuitBreakerVersions(circuitBreakers []*api.CircuitBreaker) []*api.CircuitBreaker { + var newCircuitBreakers []*api.CircuitBreaker + + for index, item := range circuitBreakers { + newCircuitBreaker := &api.CircuitBreaker{ + Id: item.GetId(), + Name: item.GetName(), + Namespace: item.GetNamespace(), + Token: item.GetToken(), + Version: utils.NewStringValue(fmt.Sprintf("test-version-%d", index)), + } + newCircuitBreakers = append(newCircuitBreakers, newCircuitBreaker) + } + + return newCircuitBreakers +} + +/** + * @brief 创建测试发布熔断规则 + */ +func CreateConfigRelease(services []*api.Service, circuitBreakers []*api.CircuitBreaker) []*api.ConfigRelease { + var configReleases []*api.ConfigRelease + for index := 0; index < 2; index++ { + configRelease := &api.ConfigRelease{ + Service: services[index], + CircuitBreaker: circuitBreakers[index], + } + configReleases = append(configReleases, configRelease) + } + return configReleases +} diff --git a/test/resource/client.go b/test/resource/client.go new file mode 100644 index 000000000..cc802ffa0 --- /dev/null +++ b/test/resource/client.go @@ -0,0 +1,40 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package resource + +import ( + "fmt" + + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/utils" +) + +const ( + clientHost = "%v.%v.%v.%v" +) + +/** + * @brief 创建客户端 + */ +func CreateClient(index uint32) *api.Client { + return &api.Client{ + Host: utils.NewStringValue(fmt.Sprintf(clientHost, index, index, index, index)), + Type: api.Client_SDK, + Version: utils.NewStringValue("8.8.8"), + } +} diff --git a/test/resource/instance.go b/test/resource/instance.go new file mode 100644 index 000000000..86ef58393 --- /dev/null +++ b/test/resource/instance.go @@ -0,0 +1,77 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package resource + +import ( + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/utils" +) + +const ( + instanceHost = "%v.%v.%v.%v" +) + +/** + * @brief 创建测试服务实例 + */ +func CreateInstances(service *api.Service) []*api.Instance { + var instances []*api.Instance + for index := 0; index < 2; index++ { + host := fmt.Sprintf(instanceHost, index, index, index, index) + + instance := &api.Instance{ + Service: service.GetName(), + Namespace: service.GetNamespace(), + Host: utils.NewStringValue(host), + Port: utils.NewUInt32Value(8), + Protocol: utils.NewStringValue("test"), + Version: utils.NewStringValue("8.8.8"), + Priority: utils.NewUInt32Value(8), + Weight: utils.NewUInt32Value(8), + HealthCheck: &api.HealthCheck{ + Type: api.HealthCheck_HEARTBEAT, + Heartbeat: &api.HeartbeatHealthCheck{ + Ttl: utils.NewUInt32Value(8), + }, + }, + Healthy: utils.NewBoolValue(false), + Isolate: utils.NewBoolValue(false), + Metadata: map[string]string{"test": "test"}, + LogicSet: utils.NewStringValue("test"), + ServiceToken: service.GetToken(), + } + instances = append(instances, instance) + } + + return instances +} + +/** + * @brief 更新测试服务实例 + */ +func UpdateInstances(instances []*api.Instance) { + for _, instance := range instances { + instance.Protocol = utils.NewStringValue("update") + instance.Version = utils.NewStringValue("4.4.4") + instance.Priority = utils.NewUInt32Value(4) + instance.Weight = utils.NewUInt32Value(4) + instance.Metadata = map[string]string{"update": "update"} + instance.LogicSet = utils.NewStringValue("update") + } +} diff --git a/test/resource/namespace.go b/test/resource/namespace.go new file mode 100644 index 000000000..4b48b850b --- /dev/null +++ b/test/resource/namespace.go @@ -0,0 +1,61 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package resource + +import ( + "fmt" + "time" + + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/utils" +) + +const ( + namespaceName = "test-ns-%v-%v" +) + +/** + * @brief 创建测试命名空间 + */ +func CreateNamespaces() []*api.Namespace { + nowStr := time.Now().Format("2006-01-02T15:04:05.000000") + + var namespaces []*api.Namespace + for index := 0; index < 2; index++ { + name := fmt.Sprintf(namespaceName, nowStr, index) + + namespace := &api.Namespace{ + Name: utils.NewStringValue(name), + Comment: utils.NewStringValue("test"), + Owners: utils.NewStringValue("test"), + } + namespaces = append(namespaces, namespace) + } + + return namespaces +} + +/** + * @brief 更新测试命名空间 + */ +func UpdateNamespaces(namespaces []*api.Namespace) { + for _, namespace := range namespaces { + namespace.Comment = utils.NewStringValue("update") + namespace.Owners = utils.NewStringValue("update") + } +} diff --git a/test/resource/platform.go b/test/resource/platform.go new file mode 100644 index 000000000..0c2bd4c77 --- /dev/null +++ b/test/resource/platform.go @@ -0,0 +1,61 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package resource + +import ( + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/utils" + "time" +) + +/** + * @brief 创建测试平台 + */ +func CreatePlatforms() []*api.Platform { + nowStr := time.Now().Format("2006-01-02T15-04-05") + var platforms []*api.Platform + for index := 1; index <= 2; index++ { + platform := &api.Platform{ + Id: utils.NewStringValue(fmt.Sprintf("id-%v-%v", index, nowStr)), + Name: utils.NewStringValue(fmt.Sprintf("name-%v", nowStr)), + Domain: utils.NewStringValue(fmt.Sprintf("domain-%v-%v", index, nowStr)), + Qps: utils.NewUInt32Value(uint32(index)), + Owner: utils.NewStringValue(fmt.Sprintf("owner-%v-%v", index, nowStr)), + Department: utils.NewStringValue(fmt.Sprintf("department-%v-%v", index, nowStr)), + Comment: utils.NewStringValue(fmt.Sprintf("comment-%v-%v", index, nowStr)), + } + platforms = append(platforms, platform) + } + + return platforms +} + +/** + * @brief 更新测试平台 + */ +func UpdatePlatforms(platforms []*api.Platform) { + for _, platform := range platforms { + platform.Name = utils.NewStringValue("update-name") + platform.Domain = utils.NewStringValue("update-domain") + platform.Qps = utils.NewUInt32Value(platform.GetQps().GetValue() + 1) + platform.Owner = utils.NewStringValue("update-owner") + platform.Department = utils.NewStringValue("update-department") + platform.Comment = utils.NewStringValue("update-comment") + } +} diff --git a/test/resource/ratelimit_config.go b/test/resource/ratelimit_config.go new file mode 100644 index 000000000..ab2dcf6bf --- /dev/null +++ b/test/resource/ratelimit_config.go @@ -0,0 +1,114 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package resource + +import ( + "fmt" + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/utils" + "github.com/golang/protobuf/ptypes/duration" +) + +/** + * @brief 创建测试限流规则 + */ +func CreateRateLimits(services []*api.Service) []*api.Rule { + var rateLimits []*api.Rule + for index := 0; index < 2; index++ { + rateLimit := &api.Rule{ + Service: services[index].GetName(), + Namespace: services[index].GetNamespace(), + Subset: map[string]*api.MatchString{ + fmt.Sprintf("name-%d", index): { + Type: api.MatchString_REGEX, + Value: utils.NewStringValue(fmt.Sprintf("value-%d", index)), + }, + fmt.Sprintf("name-%d", index+1): { + Type: api.MatchString_EXACT, + Value: utils.NewStringValue(fmt.Sprintf("value-%d", index+1)), + }, + }, + Priority: utils.NewUInt32Value(uint32(index)), + Resource: api.Rule_CONCURRENCY, + Type: api.Rule_LOCAL, + Labels: map[string]*api.MatchString{ + fmt.Sprintf("name-%d", index): { + Type: api.MatchString_REGEX, + Value: utils.NewStringValue(fmt.Sprintf("value-%d", index)), + }, + fmt.Sprintf("name-%d", index+1): { + Type: api.MatchString_EXACT, + Value: utils.NewStringValue(fmt.Sprintf("value-%d", index+1)), + }, + }, + Amounts: []*api.Amount{ + { + MaxAmount: utils.NewUInt32Value(uint32(index)), + ValidDuration: &duration.Duration{ + Seconds: int64(index), + Nanos: int32(index), + }, + }, + }, + Action: utils.NewStringValue("REJECT"), + Disable: utils.NewBoolValue(true), + Report: &api.Report{ + Interval: &duration.Duration{ + Seconds: int64(index), + Nanos: int32(index), + }, + AmountPercent: utils.NewUInt32Value(uint32(index)), + }, + Adjuster: &api.AmountAdjuster{ + Climb: &api.ClimbConfig{ + Enable: utils.NewBoolValue(true), + Metric: &api.ClimbConfig_MetricConfig{ + Window: &duration.Duration{ + Seconds: int64(index), + Nanos: int32(index), + }, + Precision: utils.NewUInt32Value(uint32(index)), + ReportInterval: &duration.Duration{ + Seconds: int64(index), + Nanos: int32(index), + }, + }, + }, + }, + RegexCombine: utils.NewBoolValue(true), + AmountMode: api.Rule_SHARE_EQUALLY, + Failover: api.Rule_FAILOVER_PASS, + Cluster: &api.RateLimitCluster{ + Service: services[index].GetName(), + Namespace: services[index].GetNamespace(), + }, + ServiceToken: services[index].GetToken(), + } + rateLimits = append(rateLimits, rateLimit) + } + return rateLimits +} + +/** + * @brief 更新测试限流规则 + */ +func UpdateRateLimits(rateLimits []*api.Rule) { + for _, rateLimit := range rateLimits { + rateLimit.Labels = map[string]*api.MatchString{} + } +} diff --git a/test/resource/service.go b/test/resource/service.go new file mode 100644 index 000000000..fd9f0f055 --- /dev/null +++ b/test/resource/service.go @@ -0,0 +1,76 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package resource + +import ( + "fmt" + "time" + + api "github.com/polarismesh/polaris-server/common/api/v1" + "github.com/polarismesh/polaris-server/common/utils" +) + +const ( + serviceName = "test-service-%v-%v" +) + +/** + * @brief 创建测试服务 + */ +func CreateServices(namespace *api.Namespace) []*api.Service { + nowStr := time.Now().Format("2006-01-02T15:04:05.000000") + + var services []*api.Service + for index := 0; index < 2; index++ { + name := fmt.Sprintf(serviceName, nowStr, index) + + service := &api.Service{ + Name: utils.NewStringValue(name), + Namespace: namespace.GetName(), + Metadata: map[string]string{"test": "test"}, + Ports: utils.NewStringValue("8,8"), + Business: utils.NewStringValue("test"), + Department: utils.NewStringValue("test"), + CmdbMod1: utils.NewStringValue("test"), + CmdbMod2: utils.NewStringValue("test"), + CmdbMod3: utils.NewStringValue("test"), + Comment: utils.NewStringValue("test"), + Owners: utils.NewStringValue("test"), + } + services = append(services, service) + } + + return services +} + +/** + * @brief 更新测试服务 + */ +func UpdateServices(services []*api.Service) { + for _, service := range services { + service.Metadata = map[string]string{"update": "update"} + service.Ports = utils.NewStringValue("4,4") + service.Business = utils.NewStringValue("update") + service.Department = utils.NewStringValue("update") + service.CmdbMod1 = utils.NewStringValue("update") + service.CmdbMod2 = utils.NewStringValue("update") + service.CmdbMod3 = utils.NewStringValue("update") + service.Comment = utils.NewStringValue("update") + service.Owners = utils.NewStringValue("update") + } +} diff --git a/test/service_test.go b/test/service_test.go new file mode 100644 index 000000000..d020cc729 --- /dev/null +++ b/test/service_test.go @@ -0,0 +1,99 @@ +/** + * Tencent is pleased to support the open source community by making Polaris available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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. + */ + +package test + +import ( + "testing" + + "github.com/polarismesh/polaris-server/test/http" + "github.com/polarismesh/polaris-server/test/resource" +) + +/** + * @brief 测试增删改查服务 + */ +func TestService(t *testing.T) { + t.Log("test service interface") + + client := http.NewClient(httpserverAddress, httpserverVersion) + + namespaces := resource.CreateNamespaces() + + // 创建命名空间 + ret, err := client.CreateNamespaces(namespaces) + if err != nil { + t.Fatalf("create namespaces fail") + } + for index, item := range ret.GetResponses() { + namespaces[index].Token = item.GetNamespace().GetToken() + } + t.Log("create namepsaces success") + + //------------------------------------------------------- + + services := resource.CreateServices(namespaces[0]) + + // 创建服务 + ret, err = client.CreateServices(services) + if err != nil { + t.Fatalf("create services fail") + } + for index, item := range ret.GetResponses() { + services[index].Token = item.GetService().GetToken() + } + t.Log("create services success") + + // 查询服务 + err = client.GetServices(services) + if err != nil { + t.Fatalf("get services fail") + } + t.Log("get services success") + + // 更新服务 + resource.UpdateServices(services) + + err = client.UpdateServices(services) + if err != nil { + t.Fatalf("update services fail") + } + t.Log("update services success") + + // 查询服务 + err = client.GetServices(services) + if err != nil { + t.Fatalf("get services fail") + } + t.Log("get services success") + + // 删除服务 + err = client.DeleteServices(services) + if err != nil { + t.Fatalf("delete services fail") + } + t.Log("delete services success") + + //------------------------------------------------------- + + // 删除命名空间 + err = client.DeleteNamespaces(namespaces) + if err != nil { + t.Fatalf("delete namespaces fail") + } + t.Log("delete namepsaces success") +} diff --git a/tool/check.sh b/tool/check.sh new file mode 100644 index 000000000..ec10bb59f --- /dev/null +++ b/tool/check.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +curpath=$(pwd) + +if [ "${0:0:1}" == "/" ]; then + dir=$(dirname "$0") +else + dir=$(pwd)/$(dirname "$0") +fi + +cd $dir/.. +workdir=$(pwd) + +#------------------------------------------------------ +source tool/include + +pids=$(ps -e -o pid,cmd | grep -w "$cmdline" | grep -v "grep" | awk '{print $1}') +array=($pids) +if [ "${#array[@]}" == "0" ]; then + start +fi + +#------------------------------------------------------ + +cd $curpath diff --git a/tool/include b/tool/include new file mode 100644 index 000000000..8bad5517d --- /dev/null +++ b/tool/include @@ -0,0 +1,84 @@ +server_name="polaris-server" +cmdline="./polaris-server start" + +function log_date() { + echo $(date "+%Y-%m-%dT%H:%M:%S") +} + +function log_error() { + echo -e "\033[31m\033[01m$(log_date)\terror\t$1 \033[0m" +} + +function log_info() { + echo -e "\033[32m\033[01m$(log_date)\tinfo\t$1 \033[0m" +} + +function del_file() { + log_info "del file for $server_name" + + rm -rf ./log + rm -rf ./statis + rm -rf ./discover-statis +} + +function start() { + log_info "start $server_name" + + if [ ! -d "./log" ]; then + mkdir ./log + fi + + if [ ! -d "./statis" ]; then + mkdir ./statis + fi + + ulimit -n 409600 + nohup $cmdline >>./log/stdout 2>&1 & +} + +function stop() { + pids=$(ps -e -o pid,cmd | grep -w "$cmdline" | grep -v "grep" | awk '{print $1}') + array=($pids) + for pid in ${array[@]}; do + log_info "stop $server_name: pid=$pid" + + kill -15 $pid + done +} + +function reload() { + pids=$(ps -e -o pid,cmd | grep -w "$cmdline" | grep -v "grep" | awk '{print $1}') + array=($pids) + for pid in ${array[@]}; do + log_info "reload $server_name: pid=$pid" + + kill -10 $pid + done +} + +function add_cron() { + item="$workdir/tool/check.sh >>$workdir/log/check.log 2>&1" + exist=$(crontab -l | grep "$item" | grep -v "#" | wc -l) + if [ "$exist" == "0" ]; then + log_info "add cron for $server_name" + + cron=$(mktemp) + crontab -l > $cron + echo "*/1 * * * * $item" >> $cron + crontab $cron + rm -f $cron + fi +} + +function del_cron() { + item="$workdir/tool/check.sh >>$workdir/log/check.log 2>&1" + exist=$(crontab -l | grep "$item" | grep -v "#" | wc -l) + if [ "$exist" != "0" ]; then + log_info "del cron for $server_name" + + cron=$(mktemp) + crontab -l | grep -v "$item" > $cron + crontab $cron + rm -f $cron + fi +} diff --git a/tool/install-standalone.sh b/tool/install-standalone.sh new file mode 100644 index 000000000..46a0b3df6 --- /dev/null +++ b/tool/install-standalone.sh @@ -0,0 +1,576 @@ +# REQUIRED OS Centos7.8 + +#!/bin/bash +if [ "${0:0:1}" == "/" ]; then + install_path=$(dirname "$0") +else + install_path=$(pwd)/$(dirname "$0") +fi + +# 脚本名称 +program_name="install_standalone" +# 数据库使用密码 +general_password="Tse@123456" +# 是否启用数据库 +db_enable=false +db_name="polaris_server" +network_name="eth0" +db_ip="" +db_port="" +db_username="" +db_password="" +redis_enable=false +redis_ip="" +redis_port="" +redis_password="" + +function Usage(){ + echo "HELP: " + echo -e "$program_name [-h] [-v] [-e db_enable] [-i ] [-p ] [-u ] [-w ] [-n ]" +} + +function showParam() { + echo "PARAM INFO: " + echo -e "\t -db option" + echo -e "\t\t db_enable: $db_enable; db_ip: $db_ip; db_port: $db_port; db_username: $db_username; db_password: $db_password;" +} + +function installMysql() { + if [ $(command -v mysql) ]; then + echo "mysql has installed, please use \"$program_name -e\" to install polaris." + Usage + exit -1 + fi + + local target_mysql_rpm=mysql57-community-release-el7-11.noarch.rpm + if [ ! -f $target_mysql_rpm ]; then + wget -T10 -t3 https://repo.mysql.com//${target_mysql_rpm} + if [ $? -ne 0 ]; then + echo "download $target_mysql_rpm to $install_path fail, exit." + exit -1 + else + echo "download $target_mysql_rpm success." + fi + fi + + # 安装rpm + yum -y module disable mysql + yum -y install ${target_mysql_rpm} + yum -y install mysql-community-server + systemctl start mysqld + + # 检查mysql是否存在 + local mysql_num=$(ps -ef | grep mysql | grep -v grep | wc -l) + if [ $mysql_num -eq 0 ]; then + echo "mysql is not started, exit." + exit -1 + fi + + local pwdText=$(cat /var/log/mysqld.log |grep "temporary password") + if [ -z "$pwdText" ]; then + echo "cannot get tempopary password, exit." + exit -1 + fi + + local tmpPwd=${pwdText#*root@localhost: } + echo "replace old password $tmpPwd with general password $general_password ." + mysql -uroot -p$tmpPwd -e"ALTER USER USER() IDENTIFIED BY '$general_password'" --connect-expired-password + mysql -uroot -p$general_password -e "Grant all privileges on *.* to 'root'@'%' identified by '${general_password}' with grant option" + if [ $? -ne 0 ]; then + echo "replace password failed, exit." + exit -1 + fi + + db_enable=true + db_ip=$(ifconfig ${network_name} | grep inet |grep -v inet6|awk '{print $2}'|tr -d "addr:") + db_port="3306" + db_username="root" + db_password=$general_password + if [ $? -ne 0 ]; then + echo "failed to configure variable, exit." + exit -1 + fi + + if [ -z "$db_ip" ]; then + echo "failed to get ip, exit." + exit -1 + fi + + cd $install_path +} + +function paramCheck() { + if [ $db_enable = "true" ] + then + if [ -z $db_ip ] || [ -z $db_port ] || [ -z $db_username ] || [ -z $db_password ] + then + echo -e "not all required database options \033[31m[db_ip, db_port, db_username, db_password]\033[0m are configured, exit." + exit -1 + else + echo -e "database parameter check success." + fi + else + echo -e "database not enable, skip." + fi + + if [ $redis_enable = "true" ] + then + if [ -z $redis_ip ] || [ -z $redis_port ] || [ -z $redis_password ] + then + echo -e "not all required redis options \033[31m[redis_ip, redis_port, redis_password]\033[0m are configured, exit." + exit -1 + else + echo -e "redis parameter check success." + fi + else + echo -e "redis not enable, skip." + fi + + cd $install_path +} + +function connectCheck() { + mysql -u$db_username -h$db_ip -p$db_password -P$db_port -e";" + if [ $? -ne 0 ]; then + echo "mysql connect check fail, exit." + exit -1 + else + echo "mysql connect check success." + fi + + cd $install_path +} + +function importSchema() { + echo -e "import schema ..." + cd $install_path/database + + local database_num=$(mysql -u$db_username -h$db_ip -p$db_password -P$db_port -e "select count(*) from information_schema.SCHEMATA where SCHEMA_NAME=\"${db_name}\";") + if [ ${database_num:0-1:1} == 0 ]; + then + mysql -u$db_username -h$db_ip -p$db_password -P$db_port < polaris_server.sql + local result=$? + if [ "$result" == "0" ]; + then + echo -e "install database finish. " + else + echo -e "install database encountered err, exit." + exit $? + fi + else + echo -e "database has created, skip." + fi + + cd $install_path +} + +function installPolarisServer() { + echo -e "install polaris server ... " + local polaris_server_num=$(ps -ef | grep polaris-server | grep -v grep | wc -l) + if [ $polaris_server_num -ge 1 ]; then + echo -e "polaris-server is running, skip." + return + fi + + local polaris_server_tarnum=$(find . -name "polaris-server-release*.tar.gz" | wc -l) + if [ $polaris_server_tarnum != 1 ]; then + echo -e "number of polaris server tar not equal 1, exit." + exit -1 + fi + + local polaris_server_tarname=$(find . -name "polaris-server-release*.tar.gz") + local polaris_server_config_filename="polaris-server.yaml" + local polaris_server_dirname=$(basename ${polaris_server_tarname} .tar.gz) + if [ ! -e $polaris_server_dirname ] + then + tar -xf $polaris_server_tarname + else + echo -e "polaris-server-release.tar.gz has been decompressed, skip." + fi + + cd $polaris_server_dirname + sed -i "s/##DB_ADDR##/${db_ip}:${db_port}/g" $polaris_server_config_filename + sed -i "s/##DB_USER##/${db_username}/g" $polaris_server_config_filename + sed -i "s/##DB_PWD##/${db_password}/g" $polaris_server_config_filename + sed -i "s/##DB_NAME##/${db_name}/g" $polaris_server_config_filename + if [ $? != 0 ]; then + echo -e "error happen when prepare polaris server config file, exit." + exit $? + fi + + /bin/bash ./tool/install.sh + echo -e "install polaris server finish." + cd $install_path +} + +function installPolarisConsole() { + echo -e "install polaris console ... " + local polaris_console_num=$(ps -ef | grep polaris-console | grep -v grep | wc -l) + if [ $polaris_console_num -ge 1 ]; then + echo -e "polaris-console is running, skip." + return + fi + + local polaris_console_tarnum=$(find . -name "polaris-console-release*.tar.gz" | wc -l) + if [ $polaris_console_tarnum != 1 ]; then + echo -e "number of polaris console tar not equal 1, exit." + exit -1 + fi + + local polaris_console_tarname=$(find . -name "polaris-console-release*.tar.gz") + local polaris_console_dirname=$(basename ${polaris_console_tarname} .tar.gz) + if [ ! -e $polaris_console_dirname ] + then + tar -xf $polaris_console_tarname + else + echo -e "polaris-console-release.tar.gz has been decompressed, skip." + fi + + cd $polaris_console_dirname + /bin/bash ./tool/install.sh + echo -e "install polaris console finish." + cd $install_path +} + +function installPrometheus() { + echo -e "install prometheus ... " + local promethues_num=$(ps -ef | grep prometheus | grep -v grep | wc -l) + if [ $promethues_num -ge 1 ] + then + echo -e "prometheus is running, skip." + return + fi + + local target_prometheus="prometheus-2.28.0.linux-amd64.tar.gz" + if [ ! -f $target_prometheus ] + then + wget -T10 -t3 https://github.com/prometheus/prometheus/releases/download/v2.28.0/${target_prometheus} --no-check-certificate + if [ $? -ne 0 ]; then + echo "download $target_prometheus to $install_path fail, exit." + exit -1 + else + echo "download $target_prometheus success." + fi + fi + + tar -xf $target_prometheus + cd prometheus-2.28.0.linux-amd64 + echo "" >> prometheus.yml + echo " - job_name: 'push-metrics'" >> prometheus.yml + echo " static_configs:" >> prometheus.yml + echo " - targets: ['localhost:9091']" >> prometheus.yml + echo " honor_labels: true" >> prometheus.yml + nohup ./prometheus --web.enable-lifecycle --web.enable-admin-api >> prometheus.out 2>&1 & + + echo "install prometheus success." + cd $install_path +} + +function installPushGateway() { + echo -e "install pushgateway ... " + local pgw_num=$(ps -ef | grep pushgateway | grep -v grep | wc -l) + if [ $pgw_num -ge 1 ]; then + echo -e "pushgateway is running, skip." + return + fi + + local target_pgw=pushgateway-1.4.1.linux-amd64.tar.gz + if [ ! -f "$target_pgw" ]; then + wget -T10 -t3 https://github.com/prometheus/pushgateway/releases/download/v1.4.1/${target_pgw} --no-check-certificate + if [ $? -ne 0 ]; then + echo "download $target_pgw to $install_path fail, exit." + exit -1 + else + echo "download $target_pgw success." + fi + fi + + tar -xf $target_pgw + cd pushgateway-1.4.1.linux-amd64 + nohup ./pushgateway --web.enable-lifecycle --web.enable-admin-api >> pgw.out 2>&1 & + + echo "install pushgateway success." + cd $install_path +} + +function createPolarisService() { + local create_discover_rsp=$(curl -w "##%{http_code}" -H 'Content-Type: application/json;charset=UTF-8' -d '[{"name":"polaris-server","namespace":"Polaris","owners":"polaris","business":"polaris server","comment":"","metadata":{}}]' http://127.0.0.1/naming/v1/services) + local result=$? + if [ "$result" == "0" ]; + then + local http_code=${create_discover_rsp##*##} + local rsp_body=${create_discover_rsp%%##*} + echo -e "create polaris-server service response: $(echo ${rsp_body%%##*} | sed ":a;N;s/[ \t \n]//g;ta" )" + + if [ "$http_code" == "200" ]; + then + echo -e "create polaris-server service success." + else + local rsp_check=$(echo ${rsp_body%%##*}| grep "existed resource" | wc -l) + if [ $rsp_check -ge 1 ];then + echo "polaris-server is existed, skip." + return + fi + + echo -e "create polaris-server service fail, http_code=$http_code" + exit -1 + fi + else + echo -e "curl create polaris service fail: ret=$result" + exit -1 + fi + cd $install_path +} + +function aliasDiscover() { + local alias_rsp=$(curl -w "##%{http_code}" -H 'Content-Type: application/json;charset=UTF-8' -d '{"service":"polaris-server","namespace":"Polaris","type":0,"alias":"polaris.discover"}' http://127.0.0.1/naming/v1/service/alias) + local result=$? + if [ "$result" == "0" ]; + then + local http_code=${alias_rsp##*##} + local rsp_body=${alias_rsp%%##*} + echo -e "alias polaris.discover service response: $(echo ${rsp_body%%##*} | sed ":a;N;s/[ \t \n]//g;ta" )" + + if [ "$http_code" == "200" ]; + then + echo -e "alias polaris.discover service success." + else + local rsp_check=$(echo ${rsp_body%%##*}| grep "existed resource" | wc -l) + if [ $rsp_check -ge 1 ];then + echo "polaris.discover is existed, skip." + return + fi + + echo -e "alias polaris.discover service fail, http_code=$http_code" + exit -1 + fi + else + echo -e "curl alias discover service fail: ret=$result" + exit -1 + fi + cd $install_path +} + +function aliasHealthCheck() { + local alias_rsp=$(curl -w "##%{http_code}" -H 'Content-Type: application/json;charset=UTF-8' -d '{"service":"polaris-server","namespace":"Polaris","type":0,"alias":"polaris.healthcheck"}' http://127.0.0.1/naming/v1/service/alias) + local result=$? + if [ "$result" == "0" ]; + then + local http_code=${alias_rsp##*##} + local rsp_body=${alias_rsp%%##*} + echo -e "alias polaris.healthcheck service response: $(echo ${rsp_body%%##*} | sed ":a;N;s/[ \t \n]//g;ta" )" + + if [ "$http_code" == "200" ]; + then + echo -e "alias polaris.healthcheck service success." + else + local rsp_check=$(echo ${rsp_body%%##*}| grep "existed resource" | wc -l) + if [ $rsp_check -ge 1 ];then + echo "alias polaris.healthcheck is existed, skip." + return + fi + + echo -e "alias healthcheck service fail, http_code=$http_code" + exit -1 + fi + else + echo -e "alias healthcheck service fail: ret=$result" + exit -1 + fi + cd $install_path +} + +function createPushGatewayService() { + local pgw_rsp=$(curl -w "##%{http_code}" -H 'Content-Type: application/json;charset=UTF-8' -d '[{"name":"polaris.monitor","namespace":"Polaris","owners":"polaris","business":"polaris monitor","comment":"","metadata":{}}]' http://127.0.0.1/naming/v1/services) + local result=$? + if [ "$result" == "0" ]; + then + local http_code=${pgw_rsp##*##} + local rsp_body=${pgw_rsp%%##*} + echo -e "create push gateway service response: $(echo ${rsp_body%%##*} | sed ":a;N;s/[ \t \n]//g;ta" )" + + if [ "$http_code" == "200" ]; + then + echo -e "create push gateway service success." + else + local rsp_check=$(echo ${rsp_body%%##*}| grep "existed resource" | wc -l) + if [ $rsp_check -ge 1 ];then + echo "push gateway service is existed, skip." + return + fi + + echo -e "create push gateway fail, http_code=$http_code" + exit -1 + fi + else + echo -e "create push gateway fail: ret=$result" + exit -1 + fi + cd $install_path +} + +function registerPushGateway() { + local local_host=$(ifconfig ${network_name} | grep inet |grep -v inet6|awk '{print $2}'|tr -d "addr:") + if [ -z "$local_host" ];then + echo "get ip fail, exit." + exit + fi + + local pgw_rsp=$(curl -w "##%{http_code}" -H 'Content-Type: application/json;charset=UTF-8' -d '[{"service":"polaris.monitor","namespace":"Polaris","weight":100,"healthy":true,"isolate":false,"port":9091,"host":"'${local_host}'","enable_health_check":false,"metadata":{}}]' http://127.0.0.1/naming/v1/instances) + local result=$? + if [ "$result" == "0" ]; + then + local http_code=${pgw_rsp##*##} + local rsp_body=${pgw_rsp%%##*} + echo -e "register push gateway service response: $(echo ${rsp_body%%##*} | sed ":a;N;s/[ \t \n]//g;ta" )" + + if [ "$http_code" == "200" ]; + then + echo -e "register push gateway service success." + else + local rsp_check=$(echo ${rsp_body%%##*}| grep "existed resource" | wc -l) + if [ $rsp_check -ge 1 ];then + echo "register push gateway is existed, skip." + return + fi + + echo -e "register push gateway fail, http_code=$http_code" + exit -1 + fi + else + echo -e "register push gateway fail: ret=$result" + exit -1 + fi + cd $install_path +} + +function addRouteStrategy() { + local add_route_req_json='[{"namespace":"Polaris","service":"polaris-server","inbounds":'$(cat ${install_path}/route-rule/rule.yaml | sed ":a;N;s/[ \t \n]//g;ta")',"outbounds":[]}]' + local add_route_rsp=$(curl -w "##%{http_code}" -H 'Content-Type: application/json;charset=UTF-8' -d $add_route_req_json http://127.0.0.1/naming/v1/routings) + local result=$? + if [ "$result" == "0" ]; + then + local http_code=${add_route_rsp##*##} + local rsp_body=${add_route_rsp%%##*} + echo -e "add route response: $(echo ${rsp_body%%##*} | sed ":a;N;s/[ \t \n]//g;ta" )" + + if [ "$http_code" == "200" ]; + then + echo -e "add route success." + else + local rsp_check=$(echo ${rsp_body%%##*}| grep "existed resource" | wc -l) + if [ $rsp_check -ge 1 ];then + echo "polaris.discover route is existed, skip." + return + fi + + echo -e "add route fail, http_code=$http_code, exit." + exit -1 + fi + else + echo -e "curl route fail: ret=$result, exit." + exit -1 + fi + cd $install_path +} + +function restartPolarisServer() { + cd $install_path + local polaris_discover_tarname=$(find . -name "polaris-server-release*.tar.gz") + local polaris_discover_config_filename="polaris-server.yaml" + local polaris_discover_dirname=$(basename ${polaris_discover_tarname} .tar.gz) + cd $polaris_discover_dirname + + local enable_register_line=$(grep -n "enable_register:" $polaris_discover_config_filename | awk -F ":" '{print $1}') + sed -i ''$enable_register_line's/enable_register: false/enable_register: true/' $polaris_discover_config_filename + sed -i 's/isolated: true/isolated: false/' $polaris_discover_config_filename + + echo -e "restart polaris server... " + /bin/bash ./tool/stop.sh + sleep 2s + /bin/bash ./tool/start.sh + cd $install_path +} + +while getopts ':hvn:ei:p:u:w:EI:P:W:' varname; do + case $varname in + h) + Usage + exit 1 + ;; + v) + echo "version 1.0" + exit 1 + ;; + n) + network_name=$OPTARG + ;; + e) + db_enable=true + ;; + i) + db_ip=$OPTARG + ;; + p) + db_port=$OPTARG + ;; + u) + db_username=$OPTARG + ;; + w) + db_password=$OPTARG + ;; + E) + redis_enable=true + ;; + I) + redis_ip=$OPTARG + ;; + P) + redis_port=$OPTARG + ;; + W) + redis_password=$OPTARG + ;; + *) + echo "unexpected option '-$OPTARG'" + exit -1 + ;; + esac +done + +# 确认mysql是否已经安装 +if [ $db_enable = "false" ] +then + # 如果mysql已安装,则要求使用 -e 选项启动安装脚本 + # 如果mysql未安装,则安装mysql并初始化必要的变量 + installMysql +fi + +if [ $db_enable = "true" ] +then + # 参数检查 + paramCheck + # 数据库连通性检查 + connectCheck + # 导入数据 + importSchema + # 安装server + installPolarisServer + # 安装console + installPolarisConsole + # 配置server + createPolarisService + aliasDiscover + aliasHealthCheck + addRouteStrategy + restartPolarisServer + # 安装Prometheus和PushGateWay + installPrometheus + installPushGateway + createPushGatewayService + registerPushGateway +else + echo -e "database config needed." + exit -1 +fi diff --git a/tool/install.sh b/tool/install.sh new file mode 100644 index 000000000..695245965 --- /dev/null +++ b/tool/install.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +curpath=$(pwd) + +if [ "${0:0:1}" == "/" ]; then + dir=$(dirname "$0") +else + dir=$(pwd)/$(dirname "$0") +fi + +cd $dir/.. +workdir=$(pwd) + +#------------------------------------------------------ +source tool/include + +chmod 755 tool/*.sh $server_name + +item="$workdir/tool/check.sh >>$workdir/log/check.log 2>&1" +exist=$(crontab -l | grep "$item" | grep -v "#" | wc -l) +if [ "$exist" == "0" ]; then + start + add_cron +fi + +#------------------------------------------------------ + +cd $curpath diff --git a/tool/p.sh b/tool/p.sh new file mode 100644 index 000000000..36bd41e59 --- /dev/null +++ b/tool/p.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +curpath=$(pwd) + +if [ "${0:0:1}" == "/" ]; then + dir=$(dirname "$0") +else + dir=$(pwd)/$(dirname "$0") +fi + +cd $dir/.. +workdir=$(pwd) + +#------------------------------------------------------ +source tool/include + +ps -ef | grep -w "$cmdline" | grep -v "grep" + +#------------------------------------------------------ + +cd $curpath diff --git a/tool/reload.sh b/tool/reload.sh new file mode 100644 index 000000000..6e656c9f5 --- /dev/null +++ b/tool/reload.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +curpath=$(pwd) + +if [ "${0:0:1}" == "/" ]; then + dir=$(dirname "$0") +else + dir=$(pwd)/$(dirname "$0") +fi + +cd $dir/.. +workdir=$(pwd) + +#------------------------------------------------------ +source tool/include + +reload + +#------------------------------------------------------ + +cd $curpath + diff --git a/tool/start.sh b/tool/start.sh new file mode 100644 index 000000000..038133fbe --- /dev/null +++ b/tool/start.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +curpath=$(pwd) + +if [ "${0:0:1}" == "/" ]; then + dir=$(dirname "$0") +else + dir=$(pwd)/$(dirname "$0") +fi + +cd $dir/.. +workdir=$(pwd) + +#------------------------------------------------------ +source tool/include + +pids=$(ps -e -o pid,cmd | grep -w "$cmdline" | grep -v "grep" | awk '{print $1}') +array=($pids) +if [ "${#array[@]}" == "0" ]; then + start + add_cron +fi + +#------------------------------------------------------ + +cd $curpath diff --git a/tool/stop.sh b/tool/stop.sh new file mode 100644 index 000000000..38b4ac34b --- /dev/null +++ b/tool/stop.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +curpath=$(pwd) + +if [ "${0:0:1}" == "/" ]; then + dir=$(dirname "$0") +else + dir=$(pwd)/$(dirname "$0") +fi + +cd $dir/.. +workdir=$(pwd) + +#------------------------------------------------------ +source tool/include + +del_cron +stop + +#------------------------------------------------------ + +cd $curpath diff --git a/tool/uninstall.sh b/tool/uninstall.sh new file mode 100644 index 000000000..0d9ee4348 --- /dev/null +++ b/tool/uninstall.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +curpath=$(pwd) + +if [ "${0:0:1}" == "/" ]; then + dir=$(dirname "$0") +else + dir=$(pwd)/$(dirname "$0") +fi + +cd $dir/.. +workdir=$(pwd) + +#------------------------------------------------------ +source tool/include + +del_cron +stop +del_file + +#------------------------------------------------------ + +cd $curpath diff --git a/version b/version new file mode 100644 index 000000000..60453e690 --- /dev/null +++ b/version @@ -0,0 +1 @@ +v1.0.0 \ No newline at end of file diff --git a/vert.sh b/vert.sh new file mode 100644 index 000000000..b3f28f044 --- /dev/null +++ b/vert.sh @@ -0,0 +1,120 @@ +#!/bin/bash + +set -ex # Exit on error; debugging enabled. +set -o pipefail # Fail a pipe if any sub-command fails. + +# not makes sure the command passed to it does not exit with a return code of 0. +not() { + # This is required instead of the earlier (! $COMMAND) because subshells and + # pipefail don't work the same on Darwin as in Linux. + ! "$@" +} + +die() { + echo "$@" >&2 + exit 1 +} + +fail_on_output() { + tee /dev/stderr | not read +} + +# Check to make sure it's safe to modify the user's git repo. +git status --porcelain | fail_on_output + +# Undo any edits made by this script. +cleanup() { + git reset --hard HEAD +} +trap cleanup EXIT + +PATH="${HOME}/go/bin:${GOROOT}/bin:${PATH}" +go version + +if [[ "$1" = "-install" ]]; then + # Install the pinned versions as defined in module tools. + pushd ./test/tools + go install \ + golang.org/x/lint/golint \ + golang.org/x/tools/cmd/goimports \ + honnef.co/go/tools/cmd/staticcheck \ + github.com/client9/misspell/cmd/misspell + popd + exit 0 +elif [[ "$#" -ne 0 ]]; then + die "Unknown argument(s): $*" +fi + +# - Ensure all source files contain a copyright message. +not git grep -L "\(Copyright (C) [0-9]\{4,\} THL A29 Limited, a Tencent company. All rights reserved.\)\|DO NOT EDIT" -- '*.go' + +# - Make sure all tests use leakcheck via Teardown. +not grep -r 'func Test[^(]' test/*.go + +# - Do not import x/net/context. +git grep -l 'x/net/context' -- "*.go" | not grep -v ".pb.go" + +# - Do not import math/rand for real library code. Use internal/grpcrand for +# thread safety. +git grep -l '"math/rand"' -- "*.go" 2>&1 | not grep -v 'scalable_rand.go\|scalable_rand_test.go\|^benchmark\|_suite.go' + +misspell -error . + +# - gofmt, goimports, golint (with exceptions for generated code), go vet, +# go mod tidy. +# Perform these checks on each module inside polaris-go. +for MOD_FILE in $(find . -name 'go.mod'); do + MOD_DIR=$(dirname ${MOD_FILE}) + pushd ${MOD_DIR} + go vet -all ./... | fail_on_output + #gofmt -s -d -l . 2>&1 | fail_on_output + #goimports -l . 2>&1 | not grep -vE "\.pb\.go" + #golint ./... 2>&1 | not grep -vE "\.pb\.go" + + go mod tidy + git status --porcelain 2>&1 | fail_on_output || \ + (git status; git --no-pager diff; exit 1) + popd +done + +# - Collection of static analysis checks +# +# TODO(dfawley): don't use deprecated functions in examples or first-party +# plugins. +#SC_OUT="$(mktemp)" +#staticcheck -go 1.9 -checks 'inherit,-ST1015' ./... > "${SC_OUT}" || true +## Error if anything other than deprecation warnings are printed. +#not grep -v "is deprecated:.*SA1019" "${SC_OUT}" + +## - special golint on package comments. +#lint_package_comment_per_package() { +# # Number of files in this go package. +# fileCount=$(go list -f '{{len .GoFiles}}' $1) +# if [ ${fileCount} -eq 0 ]; then +# return 0 +# fi +# # Number of package errors generated by golint. +# lintPackageCommentErrorsCount=$(golint --min_confidence 0 $1 | grep -c "should have a package comment") +# # golint complains about every file that's missing the package comment. If the +# # number of files for this package is greater than the number of errors, there's +# # at least one file with package comment, good. Otherwise, fail. +# if [ ${fileCount} -le ${lintPackageCommentErrorsCount} ]; then +# echo "Package $1 (with ${fileCount} files) is missing package comment" +# return 1 +# fi +#} +#lint_package_comment() { +# set +ex +# +# count=0 +# for i in $(go list ./...); do +# lint_package_comment_per_package "$i" +# ((count += $?)) +# done +# +# set -ex +# return $count +#} +#lint_package_comment + +echo SUCCESS \ No newline at end of file