├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── 1-bug-report.md │ └── 2-feature-request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── build.yml │ ├── check.yml │ ├── doc.yml │ ├── lints.yml │ ├── nightly_lints.yml │ └── test.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── Makefile.toml ├── README.md ├── artillery-core ├── .gitignore ├── Cargo.toml ├── examples │ ├── cball_ap_cluster.rs │ ├── cball_infection.rs │ ├── cball_mdns_sd_infection.rs │ └── cball_sd_infection.rs ├── kaos-tests │ ├── base.rs │ ├── chaotic-epidemic-periodic-index.rs │ ├── epidemic-periodic-index.rs │ ├── epidemic-state-change-tail-follow.rs │ ├── launcher.rs │ ├── mdns-protocol.rs │ ├── mod.rs │ ├── udp-anycast-dgram-oob.rs │ └── udp-anycast-reply-dgram-oob.rs └── src │ ├── cluster │ ├── ap.rs │ └── mod.rs │ ├── constants.rs │ ├── epidemic │ ├── cluster.rs │ ├── cluster_config.rs │ ├── member.rs │ ├── membership.rs │ ├── mod.rs │ └── state.rs │ ├── errors.rs │ ├── lib.rs │ └── service_discovery │ ├── mdns │ ├── discovery_config.rs │ ├── mod.rs │ ├── sd.rs │ └── state.rs │ ├── mod.rs │ └── udp_anycast │ ├── discovery_config.rs │ ├── mod.rs │ ├── sd.rs │ └── state.rs ├── artillery-ddata ├── .gitignore ├── Cargo.toml ├── benches │ └── craq_bencher.rs ├── examples │ └── craq_node.rs └── src │ ├── craq │ ├── chain.rs │ ├── chain_node.rs │ ├── client.rs │ ├── craq_config.rs │ ├── errors.rs │ ├── erwlock.rs │ ├── mod.rs │ ├── node.rs │ ├── proto.rs │ ├── protocol │ │ └── proto.thrift │ └── server.rs │ └── lib.rs ├── artillery-hierman ├── .gitignore ├── Cargo.toml └── src │ └── lib.rs ├── checks.sh ├── ddata-tests ├── shutdown.sh └── test.sh ├── deployment-tests ├── artillery-deployment.yaml ├── cluster-mdns-ap-test.sh └── kind-with-registry.sh ├── doc_deploy.sh ├── docs ├── .nojekyll ├── 404.html ├── CNAME ├── assets │ ├── css │ │ └── 0.styles.40eef163.css │ └── js │ │ ├── 1.2f71ac0b.js │ │ ├── 10.e2c4e6dd.js │ │ ├── 3.294f849a.js │ │ ├── 4.b92d7a52.js │ │ ├── 5.b60eac8b.js │ │ ├── 6.d6285a75.js │ │ ├── 7.9042a811.js │ │ ├── 8.f2fbc9e9.js │ │ ├── 9.7cb4df56.js │ │ └── app.7413490f.js ├── building-blocks │ └── primitives.html ├── examples │ └── cluster-examples.html ├── getting-started │ └── getting-started.html └── index.html ├── img ├── artillery_512.png └── artillery_cropped.png └── site ├── .vuepress └── config.js ├── README.md ├── building-blocks └── primitives.md ├── examples └── cluster-examples.md └── getting-started └── getting-started.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: bastion 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 15 | 16 | * **Version**: 17 | * **Platform**: 18 | * **Subsystem**: 19 | 20 | 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1-bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Bug report" 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | 19 | 20 | * **Version**: 21 | * **Platform**: 22 | * **Subsystem**: 23 | 24 | 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2-feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F680 Feature request" 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | 12 | 13 | **Is your feature request related to a problem? Please describe.** 14 | Please describe the problem you are trying to solve. 15 | 16 | **Describe the solution you'd like** 17 | Please describe the desired behavior. 18 | 19 | **Describe alternatives you've considered** 20 | Please describe alternative solutions or features you have considered. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | ##### Checklist 11 | 12 | 13 | - [ ] tests are passing with `cargo test`. 14 | - [ ] tests and/or benchmarks are included 15 | - [ ] documentation is changed or added 16 | - [ ] commit message is clear 17 | 18 | 45 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | jobs: 11 | build: 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | toolchain: 16 | - x86_64-pc-windows-msvc 17 | - x86_64-pc-windows-gnu 18 | - i686-pc-windows-msvc 19 | - x86_64-unknown-linux-gnu 20 | - x86_64-apple-darwin 21 | version: 22 | - stable 23 | - nightly 24 | include: 25 | - toolchain: x86_64-pc-windows-msvc 26 | os: windows-latest 27 | - toolchain: x86_64-pc-windows-gnu 28 | os: windows-latest 29 | - toolchain: i686-pc-windows-msvc 30 | os: windows-latest 31 | - toolchain: x86_64-unknown-linux-gnu 32 | os: ubuntu-latest 33 | - toolchain: x86_64-apple-darwin 34 | os: macOS-latest 35 | 36 | name: ${{ matrix.version }} - ${{ matrix.toolchain }} 37 | runs-on: ${{ matrix.os }} 38 | 39 | steps: 40 | - uses: actions/checkout@master 41 | 42 | - name: Install ${{ matrix.version }} 43 | uses: actions-rs/toolchain@v1 44 | with: 45 | toolchain: ${{ matrix.version }}-${{ matrix.toolchain }} 46 | default: true 47 | 48 | - name: build 49 | uses: actions-rs/cargo@v1 50 | with: 51 | command: build 52 | args: --all-targets --all-features --verbose 53 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | jobs: 11 | check: 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | toolchain: 16 | - x86_64-pc-windows-msvc 17 | - x86_64-pc-windows-gnu 18 | - i686-pc-windows-msvc 19 | - x86_64-unknown-linux-gnu 20 | - x86_64-apple-darwin 21 | version: 22 | - stable 23 | - nightly 24 | include: 25 | - toolchain: x86_64-pc-windows-msvc 26 | os: windows-latest 27 | - toolchain: x86_64-pc-windows-gnu 28 | os: windows-latest 29 | - toolchain: i686-pc-windows-msvc 30 | os: windows-latest 31 | - toolchain: x86_64-unknown-linux-gnu 32 | os: ubuntu-latest 33 | - toolchain: x86_64-apple-darwin 34 | os: macOS-latest 35 | 36 | name: ${{ matrix.version }} - ${{ matrix.toolchain }} 37 | runs-on: ${{ matrix.os }} 38 | 39 | steps: 40 | - uses: actions/checkout@master 41 | 42 | - name: Install ${{ matrix.version }} 43 | uses: actions-rs/toolchain@v1 44 | with: 45 | toolchain: ${{ matrix.version }}-${{ matrix.toolchain }} 46 | default: true 47 | 48 | - name: check 49 | uses: actions-rs/cargo@v1 50 | with: 51 | command: check 52 | args: --all-targets --all-features --verbose 53 | -------------------------------------------------------------------------------- /.github/workflows/doc.yml: -------------------------------------------------------------------------------- 1 | on: pull_request 2 | 3 | name: Doc 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@master 9 | - uses: actions-rs/toolchain@v1 10 | with: 11 | toolchain: stable 12 | override: true 13 | - uses: actions-rs/cargo@v1 14 | with: 15 | command: doc -------------------------------------------------------------------------------- /.github/workflows/lints.yml: -------------------------------------------------------------------------------- 1 | on: pull_request 2 | 3 | name: Lints (Stable) 4 | jobs: 5 | fmt: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@master 9 | - uses: actions-rs/toolchain@v1 10 | with: 11 | toolchain: stable 12 | override: true 13 | - uses: actions-rs/cargo@v1 14 | with: 15 | command: fmt 16 | args: --all -- --check 17 | clippy: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@master 21 | - uses: actions-rs/toolchain@v1 22 | with: 23 | toolchain: stable 24 | override: true 25 | - uses: actions-rs/clippy-check@v1 26 | with: 27 | token: ${{ secrets.GITHUB_TOKEN }} 28 | args: --all-targets --all-features -- -D warnings 29 | -------------------------------------------------------------------------------- /.github/workflows/nightly_lints.yml: -------------------------------------------------------------------------------- 1 | on: pull_request 2 | 3 | name: Lints (Nightly) 4 | jobs: 5 | fmt: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@master 9 | - uses: actions-rs/toolchain@v1 10 | with: 11 | toolchain: nightly 12 | override: true 13 | - uses: actions-rs/cargo@v1 14 | continue-on-error: true 15 | with: 16 | command: fmt 17 | args: --all -- --check 18 | clippy: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@master 22 | - uses: actions-rs/toolchain@v1 23 | with: 24 | toolchain: nightly 25 | override: true 26 | - uses: actions-rs/clippy-check@v1 27 | continue-on-error: true 28 | with: 29 | token: ${{ secrets.GITHUB_TOKEN }} 30 | args: --all-targets --all-features -- -D warnings 31 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | jobs: 11 | build: 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | toolchain: 16 | - x86_64-pc-windows-msvc 17 | - x86_64-pc-windows-gnu 18 | - i686-pc-windows-msvc 19 | - x86_64-unknown-linux-gnu 20 | - x86_64-apple-darwin 21 | version: 22 | - stable 23 | - nightly 24 | include: 25 | - toolchain: x86_64-pc-windows-msvc 26 | os: windows-latest 27 | - toolchain: x86_64-pc-windows-gnu 28 | os: windows-latest 29 | - toolchain: i686-pc-windows-msvc 30 | os: windows-latest 31 | - toolchain: x86_64-unknown-linux-gnu 32 | os: ubuntu-latest 33 | - toolchain: x86_64-apple-darwin 34 | os: macOS-latest 35 | 36 | name: ${{ matrix.version }} - ${{ matrix.toolchain }} 37 | runs-on: ${{ matrix.os }} 38 | 39 | steps: 40 | - uses: actions/checkout@master 41 | 42 | - name: Install ${{ matrix.version }} 43 | uses: actions-rs/toolchain@v1 44 | with: 45 | toolchain: ${{ matrix.version }}-${{ matrix.toolchain }} 46 | default: true 47 | 48 | - name: test 49 | uses: actions-rs/cargo@v1 50 | with: 51 | command: test 52 | args: --all-targets --all-features --verbose 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | **/target 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | /target 14 | **/*.rs.bk 15 | 16 | *.bc 17 | 18 | bcs 19 | 20 | # Intellij stuff 21 | .idea/ 22 | 23 | # Cluster related files to be ignored 24 | artillery-core/statedata 25 | 26 | # Test outputs 27 | deployment-tests/node_state -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "artillery-ddata", 5 | "artillery-core", 6 | "artillery-hierman", 7 | ] 8 | 9 | [profile.release] 10 | lto = "fat" 11 | codegen-units = 1 12 | 13 | 14 | # [profile.bench] 15 | # debug = true -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [2019] [Mahmut Bulut] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Mahmut Bulut 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile.toml: -------------------------------------------------------------------------------- 1 | [config] 2 | default_to_workspace = false 3 | 4 | [env] 5 | CRAQ_DIR = "artillery-ddata/src/craq" 6 | 7 | [tasks.compile-craq] 8 | script = [ 9 | ''' 10 | thrift -out $CRAQ_DIR --gen rs $CRAQ_DIR/protocol/proto.thrift 11 | ''' 12 | ] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 | 15 | ----------------- 16 | 17 |

Artillery: Cluster management & Distributed data protocol

18 | 19 | -------------------------------------------------------------------------------- /artillery-core/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /artillery-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "artillery-core" 3 | version = "0.1.2" 4 | authors = ["Mahmut Bulut "] 5 | description = "Fire-forged cluster management & Distributed data protocol" 6 | keywords = ["cluster", "distributed", "data", "replication"] 7 | categories = ["network-programming", "database-implementations"] 8 | homepage = "https://artillery.bastion.rs/" 9 | repository = "https://github.com/bastion-rs/artillery" 10 | documentation = "https://docs.rs/artillery-core" 11 | edition = "2018" 12 | license = "Apache-2.0/MIT" 13 | 14 | [dependencies] 15 | log = "0.4.11" 16 | failure = "0.1.8" 17 | failure_derive = "0.1.8" 18 | bastion-utils = "0.3.2" 19 | cuneiform-fields = "0.1.0" 20 | serde = { version = "1.0.114", features = ["derive"] } 21 | serde_json = "1.0.56" 22 | uuid = { version = "0.8.1", features = ["serde", "v4"] } 23 | chrono = { version = "0.4.13", features = ["serde"] } 24 | rand = "0.7.3" 25 | mio = { version = "0.7.0", features = ["os-poll", "udp"] } 26 | futures = "0.3.5" 27 | pin-utils = "0.1.0" 28 | libp2p = { version = "0.22.0", default-features = false, features = ["mdns"] } 29 | bastion-executor = "0.3.5" 30 | lightproc = "0.3.5" 31 | crossbeam-channel = "0.4.2" 32 | kaos = "0.1.1-alpha.2" 33 | 34 | [dev-dependencies] 35 | bincode = "1.3.1" 36 | clap = "2.33.1" 37 | pretty_env_logger = "0.4.0" 38 | once_cell = "1.4.0" 39 | criterion = "0.3.3" 40 | 41 | [[test]] 42 | name = "chaos_tests" 43 | path = "kaos-tests/launcher.rs" 44 | -------------------------------------------------------------------------------- /artillery-core/examples/cball_ap_cluster.rs: -------------------------------------------------------------------------------- 1 | extern crate pretty_env_logger; 2 | 3 | #[macro_use] 4 | extern crate log; 5 | 6 | use std::net::ToSocketAddrs; 7 | 8 | use uuid::Uuid; 9 | 10 | use artillery_core::epidemic::prelude::*; 11 | use artillery_core::service_discovery::mdns::prelude::*; 12 | 13 | use artillery_core::cluster::ap::*; 14 | use futures::future; 15 | 16 | use bastion_executor::prelude::*; 17 | 18 | use lightproc::proc_stack::ProcStack; 19 | use std::sync::Arc; 20 | 21 | fn main() { 22 | pretty_env_logger::init(); 23 | 24 | // Let's find a broadcast port 25 | let port = get_port(); 26 | 27 | // Initialize our cluster configuration 28 | let ap_cluster_config = ArtilleryAPClusterConfig { 29 | app_name: String::from("artillery-ap"), 30 | node_id: Uuid::new_v4(), 31 | sd_config: { 32 | let mut config = MDNSServiceDiscoveryConfig::default(); 33 | config.local_service_addr.set_port(port); 34 | config 35 | }, 36 | cluster_config: { 37 | let listen_addr = format!("127.0.0.1:{}", port); 38 | 39 | ClusterConfig { 40 | listen_addr: (&listen_addr as &str) 41 | .to_socket_addrs() 42 | .unwrap() 43 | .next() 44 | .unwrap(), 45 | ..Default::default() 46 | } 47 | }, 48 | }; 49 | 50 | // Configure our cluster node 51 | let ap_cluster = Arc::new(ArtilleryAPCluster::new(ap_cluster_config).unwrap()); 52 | 53 | // Launch the cluster node 54 | run( 55 | async { 56 | let cluster_stack = ProcStack::default().with_pid(2); 57 | let events_stack = ProcStack::default().with_pid(3); 58 | 59 | let ap_events = ap_cluster.clone(); 60 | 61 | // Detach cluster launch 62 | let cluster_handle = 63 | spawn_blocking(async move { ap_cluster.launch().await }, cluster_stack); 64 | 65 | // Detach event consumption 66 | let events_handle = spawn_blocking( 67 | async move { 68 | warn!("STARTED: Event Poller"); 69 | for (members, event) in ap_events.cluster().events.iter() { 70 | warn!(""); 71 | warn!(" CLUSTER EVENT "); 72 | warn!("==============="); 73 | warn!("{:?}", event); 74 | warn!(""); 75 | 76 | for member in members { 77 | info!("MEMBER {:?}", member); 78 | } 79 | } 80 | warn!("STOPPED: Event Poller"); 81 | }, 82 | events_stack, 83 | ); 84 | 85 | future::join(events_handle, cluster_handle).await 86 | }, 87 | ProcStack::default().with_pid(1), 88 | ); 89 | } 90 | 91 | fn get_port() -> u16 { 92 | use rand::{thread_rng, Rng}; 93 | 94 | let mut rng = thread_rng(); 95 | let port: u16 = rng.gen(); 96 | if port > 1025 && port < 65535 { 97 | port 98 | } else { 99 | get_port() 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /artillery-core/examples/cball_infection.rs: -------------------------------------------------------------------------------- 1 | extern crate pretty_env_logger; 2 | 3 | #[macro_use] 4 | extern crate log; 5 | 6 | use clap::*; 7 | use std::convert::TryInto; 8 | use std::fs::File; 9 | use std::io::{Read, Write}; 10 | use std::net::ToSocketAddrs; 11 | use std::path::Path; 12 | use uuid::Uuid; 13 | 14 | use artillery_core::epidemic::prelude::*; 15 | use std::str::FromStr; 16 | 17 | fn main() { 18 | pretty_env_logger::init(); 19 | let matches = App::new("Cannonball :: Epidemic") 20 | .author("Mahmut Bulut, vertexclique [ta] gmail [tod] com") 21 | .version(crate_version!()) 22 | .about("Artillery Epidemic Protocol Tester") 23 | .arg( 24 | Arg::with_name("data-folder") 25 | .index(1) 26 | .long("data-folder") 27 | .aliases(&["data-folder"]) 28 | .required(true) 29 | .help("State Data Folder"), 30 | ) 31 | .arg( 32 | Arg::with_name("cluster-key") 33 | .index(2) 34 | .long("cluster-key") 35 | .aliases(&["cluster-key"]) 36 | .required(true) 37 | .help("Cluster Key"), 38 | ) 39 | .arg( 40 | Arg::with_name("listen-addr") 41 | .index(3) 42 | .long("listen-addr") 43 | .aliases(&["listen-addr"]) 44 | .required(true) 45 | .help("Listen Address"), 46 | ) 47 | .arg( 48 | Arg::with_name("seed-node") 49 | .index(4) 50 | .long("seed-node") 51 | .aliases(&["seed-node"]) 52 | .help("Seed Node"), 53 | ) 54 | .after_help( 55 | "Enables Artillery epidemic protocol to be tested \ 56 | in the cluster configuration", 57 | ) 58 | .get_matches(); 59 | 60 | let data_folder = matches 61 | .value_of("data-folder") 62 | .expect("Can't be None, required"); 63 | 64 | let data_folder_path = Path::new(&data_folder); 65 | let host_key = read_host_key(&data_folder_path); 66 | warn!("Host key: {}", host_key.to_hyphenated()); 67 | 68 | let cluster_key = matches 69 | .value_of("cluster-key") 70 | .expect("Can't be None, required"); 71 | let listen_addr = matches 72 | .value_of("listen-addr") 73 | .expect("Can't be None, required"); 74 | let seed_node = matches.value_of("seed-node"); 75 | 76 | let config = ClusterConfig { 77 | cluster_key: cluster_key.as_bytes().to_vec(), 78 | listen_addr: (&listen_addr as &str) 79 | .to_socket_addrs() 80 | .unwrap() 81 | .next() 82 | .unwrap(), 83 | ..Default::default() 84 | }; 85 | 86 | let (cluster, _cluster_handle) = Cluster::new_cluster(host_key, config).unwrap(); 87 | 88 | if let Some(seed_node) = seed_node { 89 | cluster.add_seed_node(FromStr::from_str(&seed_node).unwrap()); 90 | } 91 | 92 | warn!("STARTED: Event Poller"); 93 | for (members, event) in cluster.events.iter() { 94 | warn!(""); 95 | warn!(" CLUSTER EVENT "); 96 | warn!("==============="); 97 | warn!("{:?}", event); 98 | warn!(""); 99 | 100 | for member in members { 101 | info!("MEMBER {:?}", member); 102 | } 103 | } 104 | warn!("STOPPED: Event Poller"); 105 | } 106 | 107 | fn read_host_key(root_folder: &Path) -> Uuid { 108 | let host_key_path = root_folder.join("host_key"); 109 | 110 | if let Ok(mut config_file) = File::open(&host_key_path) { 111 | let mut host_key_contents = Vec::::new(); 112 | config_file.read_to_end(&mut host_key_contents).unwrap(); 113 | 114 | let u: [u8; 16] = host_key_contents.as_slice().try_into().unwrap(); 115 | return Uuid::from_bytes(u); 116 | } 117 | 118 | let host_key = Uuid::new_v4(); 119 | dbg!(host_key_path.clone()); 120 | let mut host_key_file = File::create(&host_key_path).unwrap(); 121 | host_key_file.write_all(host_key.as_bytes()).unwrap(); 122 | host_key 123 | } 124 | -------------------------------------------------------------------------------- /artillery-core/examples/cball_mdns_sd_infection.rs: -------------------------------------------------------------------------------- 1 | extern crate pretty_env_logger; 2 | 3 | #[macro_use] 4 | extern crate log; 5 | 6 | use clap::*; 7 | use std::convert::TryInto; 8 | use std::fs::File; 9 | use std::io::{Read, Write}; 10 | use std::net::ToSocketAddrs; 11 | use std::path::Path; 12 | use uuid::Uuid; 13 | 14 | use artillery_core::epidemic::prelude::*; 15 | use artillery_core::service_discovery::mdns::prelude::*; 16 | 17 | use once_cell::sync::OnceCell; 18 | use serde::*; 19 | 20 | use std::thread; 21 | use std::time::Duration; 22 | 23 | #[derive(Serialize, Deserialize, Debug, Clone, Ord, PartialOrd, Eq, PartialEq)] 24 | struct ExampleSDReply { 25 | ip: String, 26 | port: u16, 27 | } 28 | 29 | fn main() { 30 | pretty_env_logger::init(); 31 | let matches = App::new("Cannonball :: MDNS + Epidemic") 32 | .author("Mahmut Bulut, vertexclique [ta] gmail [tod] com") 33 | .version(crate_version!()) 34 | .about("Artillery Epidemic Protocol Tester") 35 | .arg( 36 | Arg::with_name("node-data") 37 | .index(1) 38 | .long("node-data") 39 | .aliases(&["data-folder"]) 40 | .required(true) 41 | .help("Node State Data Folder"), 42 | ) 43 | .after_help( 44 | "Enables Artillery MDNS Service Discovery + Epidemic Protocol to be tested \ 45 | in the cluster configuration", 46 | ) 47 | .get_matches(); 48 | 49 | let data_folder = matches 50 | .value_of("node-data") 51 | .expect("Can't be None, required"); 52 | 53 | let data_folder_path = Path::new(&data_folder); 54 | let host_key = read_host_key(&data_folder_path); 55 | warn!("Host key: {}", host_key.to_hyphenated()); 56 | 57 | let this_node_cluster_port = get_port(); 58 | let sd_config = { 59 | let mut config = MDNSServiceDiscoveryConfig::default(); 60 | config.local_service_addr.set_port(this_node_cluster_port); 61 | config 62 | }; 63 | let sd = MDNSServiceDiscovery::new_service_discovery(sd_config).unwrap(); 64 | 65 | let this_node_cluster_listen_addr = format!("127.0.0.1:{}", this_node_cluster_port); 66 | let cluster = get_cluster(this_node_cluster_listen_addr.as_str(), host_key); 67 | 68 | std::thread::Builder::new() 69 | .name("cluster-event-poller".to_string()) 70 | .spawn(move || poll_cluster_events(this_node_cluster_listen_addr.as_str(), host_key)) 71 | .expect("cannot start cluster-event-poller"); 72 | 73 | thread::sleep(Duration::from_secs(1)); 74 | for discovery in sd.events().iter() { 75 | if discovery.get().port() != this_node_cluster_port { 76 | cluster.add_seed_node(discovery.get()); 77 | } 78 | } 79 | } 80 | 81 | fn poll_cluster_events(listen_addr: &str, host_key: Uuid) { 82 | warn!("STARTED: Event Poller"); 83 | for (members, event) in get_cluster(listen_addr, host_key).events.iter() { 84 | warn!(""); 85 | warn!(" CLUSTER EVENT "); 86 | warn!("==============="); 87 | warn!("{:?}", event); 88 | warn!(""); 89 | 90 | for member in members { 91 | info!("MEMBER {:?}", member); 92 | } 93 | } 94 | warn!("STOPPED: Event Poller"); 95 | } 96 | 97 | fn read_host_key(root_folder: &Path) -> Uuid { 98 | let host_key_path = root_folder.join("host_key"); 99 | 100 | if let Ok(mut config_file) = File::open(&host_key_path) { 101 | let mut host_key_contents = Vec::::new(); 102 | config_file.read_to_end(&mut host_key_contents).unwrap(); 103 | 104 | let u: [u8; 16] = host_key_contents.as_slice().try_into().unwrap(); 105 | return Uuid::from_bytes(u); 106 | } 107 | 108 | let host_key = Uuid::new_v4(); 109 | dbg!(host_key_path.clone()); 110 | let mut host_key_file = File::create(&host_key_path).unwrap(); 111 | host_key_file.write_all(host_key.as_bytes()).unwrap(); 112 | host_key 113 | } 114 | 115 | fn get_port() -> u16 { 116 | use rand::{thread_rng, Rng}; 117 | 118 | let mut rng = thread_rng(); 119 | let port: u16 = rng.gen(); 120 | if port > 1025 && port < 65535 { 121 | port 122 | } else { 123 | get_port() 124 | } 125 | } 126 | 127 | #[inline] 128 | fn get_cluster(listen_addr: &str, host_key: Uuid) -> &'static Cluster { 129 | static CLUSTER: OnceCell = OnceCell::new(); 130 | CLUSTER.get_or_init(|| { 131 | let config = ClusterConfig { 132 | cluster_key: b"artillery_local".to_vec(), 133 | listen_addr: (&listen_addr as &str) 134 | .to_socket_addrs() 135 | .unwrap() 136 | .next() 137 | .unwrap(), 138 | ..Default::default() 139 | }; 140 | 141 | let (cluster, _) = Cluster::new_cluster(host_key, config).unwrap(); 142 | cluster 143 | }) 144 | } 145 | -------------------------------------------------------------------------------- /artillery-core/examples/cball_sd_infection.rs: -------------------------------------------------------------------------------- 1 | extern crate pretty_env_logger; 2 | 3 | #[macro_use] 4 | extern crate log; 5 | 6 | use clap::*; 7 | use std::convert::TryInto; 8 | use std::fs::File; 9 | use std::io::{Read, Write}; 10 | use std::net::{SocketAddr, ToSocketAddrs}; 11 | use std::path::Path; 12 | use uuid::Uuid; 13 | 14 | use artillery_core::constants::*; 15 | use artillery_core::epidemic::prelude::*; 16 | use artillery_core::service_discovery::udp_anycast::prelude::*; 17 | 18 | use chrono::Duration; 19 | use once_cell::sync::OnceCell; 20 | use serde::*; 21 | use std::str::FromStr; 22 | use std::sync::mpsc::channel; 23 | 24 | #[derive(Serialize, Deserialize, Debug, Clone, Ord, PartialOrd, Eq, PartialEq)] 25 | struct ExampleSDReply { 26 | ip: String, 27 | port: u16, 28 | } 29 | 30 | fn main() { 31 | pretty_env_logger::init(); 32 | let matches = App::new("Cannonball :: UDP-SD + Epidemic") 33 | .author("Mahmut Bulut, vertexclique [ta] gmail [tod] com") 34 | .version(crate_version!()) 35 | .about("Artillery Epidemic Protocol Tester") 36 | .arg( 37 | Arg::with_name("node-data") 38 | .index(1) 39 | .long("node-data") 40 | .aliases(&["data-folder"]) 41 | .required(true) 42 | .help("Node State Data Folder"), 43 | ) 44 | .arg( 45 | Arg::with_name("seeker") 46 | .index(2) 47 | .long("seeker") 48 | .aliases(&["seeker"]) 49 | .help("Seeker or Listener"), 50 | ) 51 | .after_help( 52 | "Enables Artillery Service Discovery + Epidemic Protocol to be tested \ 53 | in the cluster configuration", 54 | ) 55 | .get_matches(); 56 | 57 | let data_folder = matches 58 | .value_of("node-data") 59 | .expect("Can't be None, required"); 60 | let seeker = matches.value_of("seeker"); 61 | 62 | let data_folder_path = Path::new(&data_folder); 63 | let host_key = read_host_key(&data_folder_path); 64 | warn!("Host key: {}", host_key.to_hyphenated()); 65 | 66 | let service_discovery = { 67 | let sd_port = get_port(); 68 | if seeker.is_some() { 69 | MulticastServiceDiscoveryConfig { 70 | timeout_delta: Duration::seconds(1), 71 | discovery_addr: SocketAddr::from(([0, 0, 0, 0], sd_port)), 72 | seeking_addr: SocketAddr::from(([0, 0, 0, 0], CONST_SERVICE_DISCOVERY_PORT)), 73 | } 74 | } else { 75 | MulticastServiceDiscoveryConfig { 76 | timeout_delta: Duration::seconds(1), 77 | discovery_addr: SocketAddr::from(([0, 0, 0, 0], CONST_SERVICE_DISCOVERY_PORT)), 78 | seeking_addr: SocketAddr::from(([0, 0, 0, 0], sd_port)), 79 | } 80 | } 81 | }; 82 | 83 | let epidemic_sd_config = ExampleSDReply { 84 | ip: "127.0.0.1".into(), 85 | port: get_port(), 86 | }; 87 | 88 | let reply = ServiceDiscoveryReply { 89 | serialized_data: serde_json::to_string(&epidemic_sd_config).unwrap(), 90 | }; 91 | 92 | let sd = MulticastServiceDiscovery::new_service_discovery(service_discovery, reply).unwrap(); 93 | 94 | let listen_addr = format!("{}:{}", "127.0.0.1", epidemic_sd_config.port); 95 | let _listen_addr_sd = listen_addr.clone(); 96 | let cluster = get_cluster(listen_addr.as_str(), host_key); 97 | 98 | let (tx, discoveries) = channel(); 99 | sd.register_seeker(tx).unwrap(); 100 | if seeker.is_some() { 101 | sd.seek_peers().unwrap(); 102 | } else { 103 | sd.set_listen_for_peers(true).unwrap(); 104 | } 105 | 106 | std::thread::Builder::new() 107 | .name("cluster-event-poller".to_string()) 108 | .spawn(move || poll_cluster_events(listen_addr.as_str(), host_key)) 109 | .expect("cannot start cluster-event-poller"); 110 | 111 | for discovery in discoveries.iter() { 112 | let discovery: ExampleSDReply = serde_json::from_str(&discovery.serialized_data).unwrap(); 113 | if discovery.port != epidemic_sd_config.port { 114 | debug!("Seed node address came"); 115 | let seed_node = format!("{}:{}", epidemic_sd_config.ip, discovery.port); 116 | cluster.add_seed_node(FromStr::from_str(&seed_node).unwrap()); 117 | } 118 | } 119 | } 120 | 121 | fn poll_cluster_events(listen_addr: &str, host_key: Uuid) { 122 | warn!("STARTED: Event Poller"); 123 | for (members, event) in get_cluster(listen_addr, host_key).events.iter() { 124 | warn!(""); 125 | warn!(" CLUSTER EVENT "); 126 | warn!("==============="); 127 | warn!("{:?}", event); 128 | warn!(""); 129 | 130 | for member in members { 131 | info!("MEMBER {:?}", member); 132 | } 133 | } 134 | warn!("STOPPED: Event Poller"); 135 | } 136 | 137 | fn read_host_key(root_folder: &Path) -> Uuid { 138 | let host_key_path = root_folder.join("host_key"); 139 | 140 | if let Ok(mut config_file) = File::open(&host_key_path) { 141 | let mut host_key_contents = Vec::::new(); 142 | config_file.read_to_end(&mut host_key_contents).unwrap(); 143 | 144 | let u: [u8; 16] = host_key_contents.as_slice().try_into().unwrap(); 145 | return Uuid::from_bytes(u); 146 | } 147 | 148 | let host_key = Uuid::new_v4(); 149 | let mut host_key_file = File::create(&host_key_path).unwrap(); 150 | host_key_file.write_all(host_key.as_bytes()).unwrap(); 151 | host_key 152 | } 153 | 154 | fn get_port() -> u16 { 155 | use rand::{thread_rng, Rng}; 156 | 157 | let mut rng = thread_rng(); 158 | let port: u16 = rng.gen(); 159 | if port > 1025 && port < 65535 { 160 | port 161 | } else { 162 | get_port() 163 | } 164 | } 165 | 166 | #[inline] 167 | fn get_cluster(listen_addr: &str, host_key: Uuid) -> &'static Cluster { 168 | static CLUSTER: OnceCell = OnceCell::new(); 169 | CLUSTER.get_or_init(|| { 170 | let config = ClusterConfig { 171 | cluster_key: b"artillery_local".to_vec(), 172 | listen_addr: (&listen_addr as &str) 173 | .to_socket_addrs() 174 | .unwrap() 175 | .next() 176 | .unwrap(), 177 | ..Default::default() 178 | }; 179 | 180 | let (cluster, _) = Cluster::new_cluster(host_key, config).unwrap(); 181 | cluster 182 | }) 183 | } 184 | -------------------------------------------------------------------------------- /artillery-core/kaos-tests/base.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! cluster_init { 3 | () => { 4 | use std::sync::Once; 5 | 6 | // 7 | use kaos::*; 8 | 9 | use std::net::ToSocketAddrs; 10 | 11 | use uuid::Uuid; 12 | 13 | use artillery_core::epidemic::prelude::*; 14 | use artillery_core::service_discovery::mdns::prelude::*; 15 | 16 | use artillery_core::cluster::ap::*; 17 | use futures::future; 18 | 19 | use bastion_executor::prelude::*; 20 | 21 | use lightproc::prelude::*; 22 | use lightproc::proc_stack::ProcStack; 23 | use std::sync::Arc; 24 | use std::time::{Duration, Instant}; 25 | 26 | fn node_setup( 27 | port: u16, 28 | ) -> ( 29 | Arc, 30 | RecoverableHandle<()>, 31 | RecoverableHandle<()>, 32 | ) { 33 | // Initialize our cluster configuration 34 | let ap_cluster_config = ArtilleryAPClusterConfig { 35 | app_name: String::from("artillery-ap"), 36 | node_id: Uuid::new_v4(), 37 | sd_config: { 38 | let mut config = MDNSServiceDiscoveryConfig::default(); 39 | config.local_service_addr.set_port(port); 40 | config 41 | }, 42 | cluster_config: { 43 | let listen_addr = format!("127.0.0.1:{}", port); 44 | 45 | ClusterConfig { 46 | listen_addr: (&listen_addr as &str) 47 | .to_socket_addrs() 48 | .unwrap() 49 | .next() 50 | .unwrap(), 51 | ..Default::default() 52 | } 53 | }, 54 | }; 55 | 56 | // Configure our cluster node 57 | let cluster = ArtilleryAPCluster::new(ap_cluster_config).unwrap(); 58 | let ap_cluster = Arc::new(cluster); 59 | 60 | // Launch the cluster node 61 | let cluster_stack = ProcStack::default().with_pid(2); 62 | let events_stack = ProcStack::default().with_pid(3); 63 | 64 | let ap_events = ap_cluster.clone(); 65 | let ap_ref = ap_cluster.clone(); 66 | 67 | // Detach cluster launch 68 | let cluster_handle = spawn_blocking(async move { ap_cluster.launch().await }, cluster_stack); 69 | 70 | // Detach event consumption 71 | let events_handle = spawn_blocking( 72 | async move { 73 | warn!("STARTED: Event Poller"); 74 | for (members, event) in ap_events.cluster().events.iter() { 75 | warn!(""); 76 | warn!(" CLUSTER EVENT "); 77 | warn!("==============="); 78 | warn!("{:?}", event); 79 | warn!(""); 80 | 81 | for member in members { 82 | info!("MEMBER {:?}", member); 83 | } 84 | } 85 | warn!("STOPPED: Event Poller"); 86 | }, 87 | events_stack, 88 | ); 89 | 90 | (ap_ref, events_handle, cluster_handle) 91 | } 92 | 93 | fn get_port() -> u16 { 94 | use rand::{thread_rng, Rng}; 95 | 96 | let mut rng = thread_rng(); 97 | let port: u16 = rng.gen(); 98 | if port > 1025 && port < 65535 { 99 | port 100 | } else { 101 | get_port() 102 | } 103 | } 104 | 105 | static LOGGER_INIT: Once = Once::new(); 106 | LOGGER_INIT.call_once(|| pretty_env_logger::init()); 107 | }; 108 | } 109 | 110 | #[macro_export] 111 | macro_rules! ap_events_check_node_spawn { 112 | ($node_handle:ident) => { 113 | let $node_handle = spawn_blocking( 114 | async { 115 | let (c, events, cluster_handle) = node_setup(get_port()); 116 | match events.await { 117 | Some(a) => { 118 | // Test passed. 119 | warn!("This node is leaving."); 120 | c.shutdown(); 121 | warn!("Stopping the setup"); 122 | }, 123 | _ => { 124 | assert!(false); 125 | } 126 | } 127 | }, 128 | ProcStack::default(), 129 | ); 130 | } 131 | } 132 | 133 | 134 | #[macro_export] 135 | macro_rules! ap_sd_check_node_spawn { 136 | ($node_handle:ident) => { 137 | let $node_handle = spawn_blocking( 138 | async { 139 | let (c, events, cluster_handle) = node_setup(get_port()); 140 | match cluster_handle.await { 141 | Some(a) => { 142 | // Test passed. 143 | warn!("This node is leaving."); 144 | c.shutdown(); 145 | warn!("Stopping the setup"); 146 | }, 147 | _ => { 148 | assert!(false); 149 | } 150 | } 151 | }, 152 | ProcStack::default(), 153 | ); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /artillery-core/kaos-tests/chaotic-epidemic-periodic-index.rs: -------------------------------------------------------------------------------- 1 | extern crate pretty_env_logger; 2 | 3 | #[macro_use] 4 | extern crate log; 5 | 6 | mod base; 7 | use base::*; 8 | 9 | 10 | fn main() { 11 | cluster_init!(); 12 | 13 | kaostest!("epidemic-periodic-index-fp", 14 | { 15 | // What Bastion does is doing this asynchronously. Thanks. 16 | let mut restarts = 0; 17 | 18 | loop { 19 | restarts += 1; 20 | ap_events_check_node_spawn!(node1); 21 | ap_events_check_node_spawn!(node2); 22 | ap_events_check_node_spawn!(node3); 23 | 24 | run( 25 | async { 26 | future::join_all( 27 | vec![node1, node2, node3] 28 | ).await 29 | }, 30 | ProcStack::default(), 31 | ); 32 | 33 | if restarts == 10 { 34 | break; 35 | } 36 | } 37 | } 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /artillery-core/kaos-tests/epidemic-periodic-index.rs: -------------------------------------------------------------------------------- 1 | extern crate pretty_env_logger; 2 | 3 | #[macro_use] 4 | extern crate log; 5 | 6 | mod base; 7 | use base::*; 8 | 9 | 10 | fn main() { 11 | cluster_init!(); 12 | 13 | kaostest!("epidemic-periodic-index-fp", 14 | { 15 | ap_events_check_node_spawn!(node1); 16 | ap_events_check_node_spawn!(node2); 17 | ap_events_check_node_spawn!(node3); 18 | 19 | run( 20 | async { 21 | future::join_all( 22 | vec![node1, node2, node3] 23 | ).await 24 | }, 25 | ProcStack::default(), 26 | ); 27 | } 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /artillery-core/kaos-tests/epidemic-state-change-tail-follow.rs: -------------------------------------------------------------------------------- 1 | extern crate pretty_env_logger; 2 | 3 | #[macro_use] 4 | extern crate log; 5 | 6 | mod base; 7 | use base::*; 8 | 9 | fn main() { 10 | cluster_init!(); 11 | 12 | kaostest!("epidemic-state-change-tail-follow-fp", 13 | { 14 | ap_events_check_node_spawn!(node1); 15 | ap_events_check_node_spawn!(node2); 16 | ap_events_check_node_spawn!(node3); 17 | 18 | run( 19 | async { 20 | future::join_all( 21 | vec![node1, node2, node3] 22 | ).await 23 | }, 24 | ProcStack::default(), 25 | ); 26 | } 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /artillery-core/kaos-tests/launcher.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn chaos_tests() { 3 | use std::fs; 4 | use std::time::Duration; 5 | 6 | let k = kaos::Runs::new(); 7 | 8 | for entry in fs::read_dir("kaos-tests").unwrap() { 9 | let entry = entry.unwrap(); 10 | let path = entry.path(); 11 | dbg!(path.clone()); 12 | if !path 13 | .clone() 14 | .into_os_string() 15 | .into_string() 16 | .unwrap() 17 | .contains("launcher") // Filter out itself 18 | && !path 19 | .clone() 20 | .into_os_string() 21 | .into_string() 22 | .unwrap() 23 | .contains("mod") // Filter out module hierarchy 24 | && !path 25 | .clone() 26 | .into_os_string() 27 | .into_string() 28 | .unwrap() 29 | .contains("base") 30 | // Filter out common code as test 31 | { 32 | if path 33 | .clone() 34 | .into_os_string() 35 | .into_string() 36 | .unwrap() 37 | .contains("chaotic") 38 | // Chaotic test rather than availability 39 | { 40 | // Let's have 5 varying runs. 41 | let run_count = 5; 42 | 43 | // Minimum availability to expect as milliseconds for the runs. 44 | // Which corresponds as maximum surge between service runs. 45 | // Let's have it 10 seconds. 46 | let max_surge = 10 * 1000; 47 | 48 | // Run chaotic test. 49 | k.chaotic(path, run_count, max_surge); 50 | } else { 51 | // Every service run should be available at least 2 seconds 52 | k.available(path, Duration::from_secs(2)); 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /artillery-core/kaos-tests/mdns-protocol.rs: -------------------------------------------------------------------------------- 1 | extern crate pretty_env_logger; 2 | 3 | #[macro_use] 4 | extern crate log; 5 | 6 | mod base; 7 | use base::*; 8 | 9 | fn main() { 10 | cluster_init!(); 11 | 12 | kaostest!("mdns-protocol-fp", 13 | { 14 | ap_sd_check_node_spawn!(node1); 15 | ap_sd_check_node_spawn!(node2); 16 | ap_sd_check_node_spawn!(node3); 17 | 18 | run( 19 | async { 20 | future::join_all( 21 | vec![node1, node2, node3] 22 | ).await 23 | }, 24 | ProcStack::default(), 25 | ); 26 | } 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /artillery-core/kaos-tests/mod.rs: -------------------------------------------------------------------------------- 1 | extern crate pretty_env_logger; 2 | 3 | #[macro_use] 4 | extern crate log; 5 | 6 | mod base; 7 | -------------------------------------------------------------------------------- /artillery-core/kaos-tests/udp-anycast-dgram-oob.rs: -------------------------------------------------------------------------------- 1 | extern crate pretty_env_logger; 2 | 3 | #[macro_use] 4 | extern crate log; 5 | 6 | mod base; 7 | use base::*; 8 | 9 | fn main() { 10 | // cluster_init!(); 11 | // "udp-anycast-dgram-oop-fp" 12 | 13 | // TODO: This will obviously pass because AP cluster doesn't use UDP anycast by default. 14 | // Fix it after having different prepared cluster. 15 | std::thread::sleep(std::time::Duration::from_secs(3)); 16 | } 17 | -------------------------------------------------------------------------------- /artillery-core/kaos-tests/udp-anycast-reply-dgram-oob.rs: -------------------------------------------------------------------------------- 1 | extern crate pretty_env_logger; 2 | 3 | #[macro_use] 4 | extern crate log; 5 | 6 | mod base; 7 | use base::*; 8 | 9 | fn main() { 10 | // cluster_init!(); 11 | // "udp-anycast-reply-dgram-oop-fp" 12 | 13 | // TODO: This will obviously pass because AP cluster doesn't use UDP anycast by default. 14 | // Fix it after having different prepared cluster. 15 | std::thread::sleep(std::time::Duration::from_secs(3)); 16 | } 17 | -------------------------------------------------------------------------------- /artillery-core/src/cluster/ap.rs: -------------------------------------------------------------------------------- 1 | use crate::epidemic::prelude::*; 2 | use crate::errors::*; 3 | use crate::service_discovery::mdns::prelude::*; 4 | 5 | use lightproc::prelude::*; 6 | 7 | use futures::{select, FutureExt}; 8 | use pin_utils::pin_mut; 9 | use std::{cell::Cell, sync::Arc}; 10 | use uuid::Uuid; 11 | 12 | #[derive(Default, Debug, Clone)] 13 | pub struct ArtilleryAPClusterConfig { 14 | pub app_name: String, 15 | pub node_id: Uuid, 16 | pub cluster_config: ClusterConfig, 17 | pub sd_config: MDNSServiceDiscoveryConfig, 18 | } 19 | 20 | pub struct ArtilleryAPCluster { 21 | config: ArtilleryAPClusterConfig, 22 | cluster: Arc, 23 | sd: Arc, 24 | cluster_ev_loop_handle: Cell>, 25 | } 26 | 27 | unsafe impl Send for ArtilleryAPCluster {} 28 | unsafe impl Sync for ArtilleryAPCluster {} 29 | 30 | pub type DiscoveryLaunch = RecoverableHandle<()>; 31 | 32 | impl ArtilleryAPCluster { 33 | pub fn new(config: ArtilleryAPClusterConfig) -> Result { 34 | let sd = MDNSServiceDiscovery::new_service_discovery(config.sd_config.clone())?; 35 | 36 | let (cluster, cluster_listener) = 37 | Cluster::new_cluster(config.node_id, config.cluster_config.clone())?; 38 | 39 | Ok(Self { 40 | config, 41 | cluster: Arc::new(cluster), 42 | sd: Arc::new(sd), 43 | cluster_ev_loop_handle: Cell::new(cluster_listener), 44 | }) 45 | } 46 | 47 | pub fn cluster(&self) -> Arc { 48 | self.cluster.clone() 49 | } 50 | 51 | pub fn service_discovery(&self) -> Arc { 52 | self.sd.clone() 53 | } 54 | 55 | pub fn shutdown(&self) { 56 | self.cluster().leave_cluster(); 57 | } 58 | 59 | pub async fn launch(&self) { 60 | let (_, eh) = LightProc::recoverable(async {}, |_| (), ProcStack::default()); 61 | let ev_loop_handle = self.cluster_ev_loop_handle.replace(eh); 62 | 63 | // do fusing 64 | let ev_loop_handle = ev_loop_handle.fuse(); 65 | let discover_nodes_handle = self.discover_nodes().fuse(); 66 | 67 | pin_mut!(ev_loop_handle); 68 | pin_mut!(discover_nodes_handle); 69 | 70 | select! { 71 | ev_loop_res = ev_loop_handle => { dbg!(ev_loop_res); ev_loop_res.unwrap() }, 72 | _ = discover_nodes_handle => panic!("Node discovery unexpectedly shutdown.") 73 | }; 74 | } 75 | 76 | async fn discover_nodes(&self) { 77 | self.service_discovery() 78 | .events() 79 | .iter() 80 | .filter(|discovery| { 81 | discovery.get().port() != self.config.sd_config.local_service_addr.port() 82 | }) 83 | .for_each(|discovery| self.cluster.add_seed_node(discovery.get())) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /artillery-core/src/cluster/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod ap; 2 | -------------------------------------------------------------------------------- /artillery-core/src/constants.rs: -------------------------------------------------------------------------------- 1 | // DISCO = 34726 2 | /// Default Service Discovery Port 3 | pub const CONST_SERVICE_DISCOVERY_PORT: u16 = 34726; 4 | 5 | // ARTIL = 27845 6 | /// Default Epidemic Port 7 | pub const CONST_INFECTION_PORT: u16 = 27845; 8 | 9 | // Not sure MIO handles this correctly. 10 | // Behave like this is the size. Normally 512 is enough. 11 | /// Default UDP cast packet size 12 | pub const CONST_PACKET_SIZE: usize = 1 << 16; 13 | -------------------------------------------------------------------------------- /artillery-core/src/epidemic/cluster.rs: -------------------------------------------------------------------------------- 1 | use super::state::ArtilleryEpidemic; 2 | use crate::epidemic::cluster_config::ClusterConfig; 3 | use crate::epidemic::state::{ArtilleryClusterEvent, ArtilleryClusterRequest}; 4 | use crate::errors::*; 5 | use bastion_executor::prelude::*; 6 | use lightproc::{proc_stack::ProcStack, recoverable_handle::RecoverableHandle}; 7 | use std::convert::AsRef; 8 | use std::net::SocketAddr; 9 | use std::{ 10 | future::Future, 11 | pin::Pin, 12 | sync::mpsc::{channel, Receiver, Sender}, 13 | task::{Context, Poll}, 14 | }; 15 | use uuid::Uuid; 16 | 17 | #[derive(Debug)] 18 | pub struct Cluster { 19 | pub events: Receiver, 20 | comm: Sender, 21 | } 22 | 23 | impl Cluster { 24 | pub fn new_cluster( 25 | host_key: Uuid, 26 | config: ClusterConfig, 27 | ) -> Result<(Self, RecoverableHandle<()>)> { 28 | let (event_tx, event_rx) = channel::(); 29 | let (internal_tx, mut internal_rx) = channel::(); 30 | 31 | let (poll, state) = 32 | ArtilleryEpidemic::new(host_key, config, event_tx, internal_tx.clone())?; 33 | 34 | debug!("Starting Artillery Cluster"); 35 | let cluster_handle = spawn_blocking( 36 | async move { 37 | ArtilleryEpidemic::event_loop(&mut internal_rx, poll, state) 38 | .expect("Failed to create event loop"); 39 | }, 40 | ProcStack::default(), 41 | ); 42 | 43 | Ok(( 44 | Self { 45 | events: event_rx, 46 | comm: internal_tx, 47 | }, 48 | cluster_handle, 49 | )) 50 | } 51 | 52 | pub fn add_seed_node(&self, addr: SocketAddr) { 53 | let _ = self.comm.send(ArtilleryClusterRequest::AddSeed(addr)); 54 | } 55 | 56 | pub fn send_payload>(&self, id: Uuid, msg: T) { 57 | self.comm 58 | .send(ArtilleryClusterRequest::Payload( 59 | id, 60 | msg.as_ref().to_string(), 61 | )) 62 | .unwrap(); 63 | } 64 | 65 | pub fn leave_cluster(&self) { 66 | let _ = self.comm.send(ArtilleryClusterRequest::LeaveCluster); 67 | } 68 | } 69 | 70 | impl Future for Cluster { 71 | type Output = ArtilleryClusterEvent; 72 | 73 | fn poll(self: Pin<&mut Self>, _cx: &mut Context) -> Poll { 74 | match self.events.recv() { 75 | Ok(kv) => Poll::Ready(kv), 76 | Err(_) => Poll::Pending, 77 | } 78 | } 79 | } 80 | 81 | unsafe impl Send for Cluster {} 82 | unsafe impl Sync for Cluster {} 83 | 84 | impl Drop for Cluster { 85 | fn drop(&mut self) { 86 | let (tx, rx) = channel(); 87 | 88 | let _ = self.comm.send(ArtilleryClusterRequest::Exit(tx)); 89 | 90 | rx.recv().unwrap(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /artillery-core/src/epidemic/cluster_config.rs: -------------------------------------------------------------------------------- 1 | use crate::constants::*; 2 | use chrono::Duration; 3 | use std::net::{SocketAddr, ToSocketAddrs}; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct ClusterConfig { 7 | pub cluster_key: Vec, 8 | pub ping_interval: Duration, 9 | pub network_mtu: usize, 10 | pub ping_request_host_count: usize, 11 | pub ping_timeout: Duration, 12 | pub listen_addr: SocketAddr, 13 | } 14 | 15 | impl Default for ClusterConfig { 16 | fn default() -> Self { 17 | let directed = SocketAddr::from(([127, 0, 0, 1], CONST_INFECTION_PORT)); 18 | 19 | ClusterConfig { 20 | cluster_key: b"default".to_vec(), 21 | ping_interval: Duration::seconds(1), 22 | network_mtu: CONST_PACKET_SIZE, 23 | ping_request_host_count: 3, 24 | ping_timeout: Duration::seconds(3), 25 | listen_addr: directed.to_socket_addrs().unwrap().next().unwrap(), 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /artillery-core/src/epidemic/member.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Ordering; 2 | use std::fmt; 3 | use std::fmt::{Debug, Formatter}; 4 | use std::net::SocketAddr; 5 | 6 | use chrono::{DateTime, Duration, Utc}; 7 | use serde::*; 8 | use uuid::Uuid; 9 | 10 | #[derive(Serialize, Deserialize, Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Copy)] 11 | pub enum ArtilleryMemberState { 12 | /// Looks alive as in the original paper 13 | #[serde(rename = "a")] 14 | Alive, 15 | /// Suspect from the given node 16 | #[serde(rename = "s")] 17 | Suspect, 18 | /// AKA `Confirm` in the original paper 19 | #[serde(rename = "d")] 20 | Down, 21 | /// Left the cluster 22 | #[serde(rename = "l")] 23 | Left, 24 | } 25 | 26 | #[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] 27 | pub struct ArtilleryMember { 28 | #[serde(rename = "h")] 29 | host_key: Uuid, 30 | #[serde(rename = "r")] 31 | remote_host: Option, 32 | #[serde(rename = "i")] 33 | incarnation_number: u64, 34 | #[serde(rename = "m")] 35 | member_state: ArtilleryMemberState, 36 | #[serde(rename = "t")] 37 | last_state_change: DateTime, 38 | } 39 | 40 | #[derive(Serialize, Deserialize, Debug, Clone, PartialOrd, Ord, PartialEq, Eq)] 41 | pub struct ArtilleryStateChange { 42 | member: ArtilleryMember, 43 | } 44 | 45 | impl ArtilleryMember { 46 | pub fn new( 47 | host_key: Uuid, 48 | remote_host: SocketAddr, 49 | incarnation_number: u64, 50 | known_state: ArtilleryMemberState, 51 | ) -> Self { 52 | ArtilleryMember { 53 | host_key, 54 | remote_host: Some(remote_host), 55 | incarnation_number, 56 | member_state: known_state, 57 | last_state_change: Utc::now(), 58 | } 59 | } 60 | 61 | pub fn current(host_key: Uuid) -> Self { 62 | ArtilleryMember { 63 | host_key, 64 | remote_host: None, 65 | incarnation_number: 0, 66 | member_state: ArtilleryMemberState::Alive, 67 | last_state_change: Utc::now(), 68 | } 69 | } 70 | 71 | pub fn host_key(&self) -> Uuid { 72 | self.host_key 73 | } 74 | 75 | pub fn remote_host(&self) -> Option { 76 | self.remote_host 77 | } 78 | 79 | pub fn is_remote(&self) -> bool { 80 | self.remote_host.is_some() 81 | } 82 | 83 | pub fn is_current(&self) -> bool { 84 | self.remote_host.is_none() 85 | } 86 | 87 | pub fn state_change_older_than(&self, duration: Duration) -> bool { 88 | self.last_state_change + duration < Utc::now() 89 | } 90 | 91 | pub fn state(&self) -> ArtilleryMemberState { 92 | self.member_state 93 | } 94 | 95 | pub fn set_state(&mut self, state: ArtilleryMemberState) { 96 | if self.member_state != state { 97 | self.member_state = state; 98 | self.last_state_change = Utc::now(); 99 | } 100 | } 101 | 102 | pub fn member_by_changing_host(&self, remote_host: SocketAddr) -> ArtilleryMember { 103 | ArtilleryMember { 104 | remote_host: Some(remote_host), 105 | ..self.clone() 106 | } 107 | } 108 | 109 | pub fn reincarnate(&mut self) { 110 | self.incarnation_number += 1 111 | } 112 | } 113 | 114 | impl ArtilleryStateChange { 115 | pub fn new(member: ArtilleryMember) -> ArtilleryStateChange { 116 | ArtilleryStateChange { member } 117 | } 118 | 119 | pub fn member(&self) -> &ArtilleryMember { 120 | &self.member 121 | } 122 | 123 | pub fn update(&mut self, member: ArtilleryMember) { 124 | self.member = member 125 | } 126 | } 127 | 128 | impl PartialOrd for ArtilleryMember { 129 | fn partial_cmp(&self, rhs: &ArtilleryMember) -> Option { 130 | let t1 = ( 131 | self.host_key.as_bytes(), 132 | format!("{:?}", self.remote_host), 133 | self.incarnation_number, 134 | self.member_state, 135 | ); 136 | 137 | let t2 = ( 138 | rhs.host_key.as_bytes(), 139 | format!("{:?}", rhs.remote_host), 140 | rhs.incarnation_number, 141 | rhs.member_state, 142 | ); 143 | 144 | t1.partial_cmp(&t2) 145 | } 146 | } 147 | 148 | impl Ord for ArtilleryMember { 149 | fn cmp(&self, rhs: &ArtilleryMember) -> Ordering { 150 | self.partial_cmp(rhs).unwrap() 151 | } 152 | } 153 | 154 | impl Debug for ArtilleryMember { 155 | fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { 156 | fmt.debug_struct("ArtilleryMember") 157 | .field("incarnation_number", &self.incarnation_number) 158 | .field("host", &self.host_key) 159 | .field("state", &self.member_state) 160 | .field( 161 | "drift_time_ms", 162 | &(Utc::now() - self.last_state_change).num_milliseconds(), 163 | ) 164 | .field( 165 | "remote_host", 166 | &self 167 | .remote_host 168 | .map_or(String::from("(current)"), |r| format!("{}", r)) 169 | .as_str(), 170 | ) 171 | .finish() 172 | } 173 | } 174 | 175 | pub fn most_uptodate_member_data<'a>( 176 | lhs: &'a ArtilleryMember, 177 | rhs: &'a ArtilleryMember, 178 | ) -> &'a ArtilleryMember { 179 | // Don't apply clippy here. 180 | // It's important bit otherwise we won't understand. 181 | #![allow(clippy::match_same_arms)] 182 | 183 | let lhs_overrides = match ( 184 | lhs.member_state, 185 | lhs.incarnation_number, 186 | rhs.member_state, 187 | rhs.incarnation_number, 188 | ) { 189 | (ArtilleryMemberState::Alive, i, ArtilleryMemberState::Suspect, j) => i > j, 190 | (ArtilleryMemberState::Alive, i, ArtilleryMemberState::Alive, j) => i > j, 191 | (ArtilleryMemberState::Suspect, i, ArtilleryMemberState::Suspect, j) => i > j, 192 | (ArtilleryMemberState::Suspect, i, ArtilleryMemberState::Alive, j) => i >= j, 193 | (ArtilleryMemberState::Down, _, ArtilleryMemberState::Alive, _) => true, 194 | (ArtilleryMemberState::Down, _, ArtilleryMemberState::Suspect, _) => true, 195 | (ArtilleryMemberState::Left, _, _, _) => true, 196 | _ => false, 197 | }; 198 | 199 | if lhs_overrides { 200 | lhs 201 | } else { 202 | rhs 203 | } 204 | } 205 | 206 | #[cfg(test)] 207 | mod test { 208 | use std::str::FromStr; 209 | 210 | use super::{ArtilleryMember, ArtilleryMemberState}; 211 | use chrono::{Duration, Utc}; 212 | 213 | use uuid; 214 | 215 | #[test] 216 | fn test_member_encode_decode() { 217 | let member = ArtilleryMember { 218 | host_key: uuid::Uuid::new_v4(), 219 | remote_host: Some(FromStr::from_str("127.0.0.1:1337").unwrap()), 220 | incarnation_number: 123, 221 | member_state: ArtilleryMemberState::Alive, 222 | last_state_change: Utc::now() - Duration::days(1), 223 | }; 224 | 225 | let encoded = bincode::serialize(&member).unwrap(); 226 | dbg!(encoded.len()); 227 | 228 | let decoded: ArtilleryMember = bincode::deserialize(&encoded).unwrap(); 229 | 230 | let json_encoded = serde_json::to_string(&member).unwrap(); 231 | dbg!(json_encoded); 232 | 233 | assert_eq!(decoded, member); 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /artillery-core/src/epidemic/membership.rs: -------------------------------------------------------------------------------- 1 | use std::collections::hash_map::Entry; 2 | use std::collections::{HashMap, HashSet}; 3 | use std::net::SocketAddr; 4 | 5 | use chrono::Duration; 6 | use uuid::Uuid; 7 | 8 | use super::member::{ArtilleryMember, ArtilleryMemberState, ArtilleryStateChange}; 9 | use crate::epidemic::member; 10 | use bastion_utils::math; 11 | 12 | use kaos::flunk; 13 | 14 | pub struct ArtilleryMemberList { 15 | members: Vec, 16 | periodic_index: usize, 17 | } 18 | 19 | impl ArtilleryMemberList { 20 | pub fn new(current: ArtilleryMember) -> Self { 21 | ArtilleryMemberList { 22 | members: vec![current], 23 | periodic_index: 0, 24 | } 25 | } 26 | 27 | pub fn available_nodes(&self) -> Vec { 28 | self.members 29 | .iter() 30 | .filter(|m| m.state() != ArtilleryMemberState::Left) 31 | .cloned() 32 | .collect() 33 | } 34 | 35 | pub fn to_map(&self) -> HashMap { 36 | self.members 37 | .iter() 38 | .map(|m| (m.host_key(), (*m).clone())) 39 | .collect() 40 | } 41 | 42 | fn mut_myself(&mut self) -> &mut ArtilleryMember { 43 | for member in &mut self.members { 44 | if member.is_current() { 45 | return member; 46 | } 47 | } 48 | 49 | panic!("Could not find this instance as registered member"); 50 | } 51 | 52 | pub fn reincarnate_self(&mut self) -> ArtilleryMember { 53 | let myself = self.mut_myself(); 54 | myself.reincarnate(); 55 | 56 | myself.clone() 57 | } 58 | 59 | pub fn leave(&mut self) -> ArtilleryMember { 60 | let myself = self.mut_myself(); 61 | myself.set_state(ArtilleryMemberState::Left); 62 | myself.reincarnate(); 63 | 64 | myself.clone() 65 | } 66 | 67 | pub fn next_random_member(&mut self) -> Option { 68 | if self.periodic_index == 0 { 69 | math::shuffle_linear(&mut self.members); 70 | } 71 | 72 | let other_members: Vec<_> = self.members.iter().filter(|&m| m.is_remote()).collect(); 73 | 74 | if other_members.is_empty() { 75 | None 76 | } else { 77 | flunk!("epidemic-periodic-index-fp"); 78 | self.periodic_index = (self.periodic_index + 1) % other_members.len(); 79 | Some(other_members[self.periodic_index].clone()) 80 | } 81 | } 82 | 83 | pub fn time_out_nodes( 84 | &mut self, 85 | expired_hosts: &HashSet, 86 | ) -> (Vec, Vec) { 87 | let mut suspect_members = Vec::new(); 88 | let mut down_members = Vec::new(); 89 | 90 | for member in &mut self.members { 91 | if let Some(remote_host) = member.remote_host() { 92 | if !expired_hosts.contains(&remote_host) { 93 | continue; 94 | } 95 | 96 | match member.state() { 97 | ArtilleryMemberState::Alive => { 98 | member.set_state(ArtilleryMemberState::Suspect); 99 | suspect_members.push(member.clone()); 100 | } 101 | // TODO: Config suspect timeout 102 | ArtilleryMemberState::Suspect 103 | if member.state_change_older_than(Duration::seconds(3)) => 104 | { 105 | member.set_state(ArtilleryMemberState::Down); 106 | down_members.push(member.clone()); 107 | } 108 | ArtilleryMemberState::Suspect 109 | | ArtilleryMemberState::Down 110 | | ArtilleryMemberState::Left => {} 111 | } 112 | } 113 | } 114 | 115 | (suspect_members, down_members) 116 | } 117 | 118 | pub fn mark_node_alive(&mut self, src_addr: &SocketAddr) -> Option { 119 | for member in &mut self.members { 120 | if member.remote_host() == Some(*src_addr) 121 | && member.state() != ArtilleryMemberState::Alive 122 | { 123 | member.set_state(ArtilleryMemberState::Alive); 124 | 125 | return Some(member.clone()); 126 | } 127 | } 128 | 129 | None 130 | } 131 | 132 | pub fn apply_state_changes( 133 | &mut self, 134 | state_changes: Vec, 135 | from: &SocketAddr, 136 | ) -> (Vec, Vec) { 137 | let mut current_members = self.to_map(); 138 | 139 | let mut changed_nodes = Vec::new(); 140 | let mut new_nodes = Vec::new(); 141 | 142 | let my_host_key = self.mut_myself().host_key(); 143 | 144 | for state_change in state_changes { 145 | let new_member_data = state_change.member(); 146 | let old_member_data = current_members.entry(new_member_data.host_key()); 147 | 148 | if new_member_data.host_key() == my_host_key { 149 | if new_member_data.state() != ArtilleryMemberState::Alive { 150 | let myself = self.reincarnate_self(); 151 | changed_nodes.push(myself.clone()); 152 | } 153 | } else { 154 | match old_member_data { 155 | Entry::Occupied(mut entry) => { 156 | let new_member = 157 | member::most_uptodate_member_data(new_member_data, entry.get()).clone(); 158 | let new_host = new_member 159 | .remote_host() 160 | .or_else(|| entry.get().remote_host()) 161 | .unwrap(); 162 | let new_member = new_member.member_by_changing_host(new_host); 163 | 164 | if new_member.state() != entry.get().state() { 165 | entry.insert(new_member.clone()); 166 | changed_nodes.push(new_member); 167 | } 168 | } 169 | Entry::Vacant(entry) => { 170 | let new_host = new_member_data.remote_host().unwrap_or(*from); 171 | let new_member = new_member_data.member_by_changing_host(new_host); 172 | 173 | entry.insert(new_member.clone()); 174 | new_nodes.push(new_member); 175 | } 176 | }; 177 | } 178 | } 179 | 180 | self.members = current_members.values().cloned().collect(); 181 | 182 | (new_nodes, changed_nodes) 183 | } 184 | 185 | /// 186 | /// 187 | /// Random ping enqueuing 188 | pub fn hosts_for_indirect_ping( 189 | &self, 190 | host_count: usize, 191 | target: &SocketAddr, 192 | ) -> Vec { 193 | let mut possible_members: Vec<_> = self 194 | .members 195 | .iter() 196 | .filter_map(|m| { 197 | if m.state() == ArtilleryMemberState::Alive 198 | && m.is_remote() 199 | && m.remote_host() != Some(*target) 200 | { 201 | m.remote_host() 202 | } else { 203 | None 204 | } 205 | }) 206 | .collect(); 207 | 208 | math::shuffle_linear(&mut possible_members); 209 | 210 | possible_members.iter().take(host_count).cloned().collect() 211 | } 212 | 213 | pub fn has_member(&self, remote_host: &SocketAddr) -> bool { 214 | self.members 215 | .iter() 216 | .any(|m| m.remote_host() == Some(*remote_host)) 217 | } 218 | 219 | pub fn add_member(&mut self, member: ArtilleryMember) { 220 | self.members.push(member) 221 | } 222 | 223 | /// 224 | /// `get_member` will return artillery member if the given uuid is matches with any of the 225 | /// member in the cluster. 226 | pub fn get_member(&self, id: &Uuid) -> Option { 227 | let member: Vec<_> = self 228 | .members 229 | .iter() 230 | .filter(|&m| m.host_key() == *id) 231 | .collect(); 232 | 233 | if member.is_empty() { 234 | return None; 235 | } 236 | Some(member[0].clone()) 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /artillery-core/src/epidemic/mod.rs: -------------------------------------------------------------------------------- 1 | // As you swim lazily through the milieu, 2 | // The secrets of the world will infect you. 3 | 4 | pub mod cluster; 5 | pub mod cluster_config; 6 | pub mod member; 7 | pub mod membership; 8 | pub mod state; 9 | 10 | pub mod prelude { 11 | pub use super::cluster::*; 12 | pub use super::cluster_config::*; 13 | pub use super::member::*; 14 | pub use super::membership::*; 15 | pub use super::state::*; 16 | } 17 | -------------------------------------------------------------------------------- /artillery-core/src/errors.rs: -------------------------------------------------------------------------------- 1 | use failure::*; 2 | use std::io; 3 | 4 | use std::result; 5 | use std::sync::mpsc::{RecvError, SendError}; 6 | 7 | /// Result type for operations that could result in an `ArtilleryError` 8 | pub type Result = result::Result; 9 | 10 | #[derive(Fail, Debug)] 11 | pub enum ArtilleryError { 12 | // General Error Types 13 | #[fail(display = "Artillery :: Orphan Node Error: {}", _0)] 14 | OrphanNode(String), 15 | #[fail(display = "Artillery :: I/O error occurred: {}", _0)] 16 | Io(io::Error), 17 | #[fail(display = "Artillery :: Cluster Message Decode Error: {}", _0)] 18 | ClusterMessageDecode(String), 19 | #[fail(display = "Artillery :: Message Send Error: {}", _0)] 20 | Send(String), 21 | #[fail(display = "Artillery :: Message Receive Error: {}", _0)] 22 | Receive(String), 23 | #[fail(display = "Artillery :: Unexpected Error: {}", _0)] 24 | Unexpected(String), 25 | #[fail(display = "Artillery :: Decoding Error: {}", _0)] 26 | Decoding(String), 27 | #[fail(display = "Artillery :: Numeric Cast Error: {}", _0)] 28 | NumericCast(String), 29 | } 30 | 31 | impl From for ArtilleryError { 32 | fn from(e: io::Error) -> Self { 33 | ArtilleryError::Io(e) 34 | } 35 | } 36 | 37 | impl From for ArtilleryError { 38 | fn from(e: serde_json::error::Error) -> Self { 39 | ArtilleryError::ClusterMessageDecode(e.to_string()) 40 | } 41 | } 42 | 43 | impl From> for ArtilleryError { 44 | fn from(e: SendError) -> Self { 45 | ArtilleryError::Send(e.to_string()) 46 | } 47 | } 48 | 49 | impl From for ArtilleryError { 50 | fn from(e: RecvError) -> Self { 51 | ArtilleryError::Receive(e.to_string()) 52 | } 53 | } 54 | 55 | impl From for ArtilleryError { 56 | fn from(e: std::str::Utf8Error) -> Self { 57 | ArtilleryError::Decoding(e.to_string()) 58 | } 59 | } 60 | 61 | impl From for ArtilleryError { 62 | fn from(e: std::num::TryFromIntError) -> Self { 63 | ArtilleryError::NumericCast(e.to_string()) 64 | } 65 | } 66 | 67 | #[macro_export] 68 | macro_rules! bail { 69 | ($kind:expr, $e:expr) => { 70 | return Err($kind($e.to_owned())); 71 | }; 72 | ($kind:expr, $fmt:expr, $($arg:tt)+) => { 73 | return Err($kind(format!($fmt, $($arg)+).to_owned())); 74 | }; 75 | } 76 | -------------------------------------------------------------------------------- /artillery-core/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny( 2 | clippy::unimplemented, 3 | clippy::wildcard_enum_match_arm, 4 | clippy::else_if_without_else, 5 | clippy::float_arithmetic, 6 | // clippy::indexing_slicing, // XXX: Enable for failpoint discovery 7 | clippy::cast_lossless, 8 | clippy::cast_possible_truncation, 9 | clippy::cast_possible_wrap, 10 | clippy::cast_precision_loss, 11 | clippy::cast_sign_loss, 12 | clippy::checked_conversions, 13 | clippy::decimal_literal_representation, 14 | clippy::doc_markdown, 15 | clippy::empty_enum, 16 | clippy::expl_impl_clone_on_copy, 17 | clippy::explicit_into_iter_loop, 18 | clippy::explicit_iter_loop, 19 | clippy::fallible_impl_from, 20 | clippy::filter_map, 21 | clippy::filter_map_next, 22 | clippy::find_map, 23 | clippy::get_unwrap, 24 | clippy::if_not_else, 25 | clippy::inline_always, 26 | clippy::invalid_upcast_comparisons, 27 | clippy::items_after_statements, 28 | clippy::map_flatten, 29 | clippy::match_same_arms, 30 | clippy::maybe_infinite_iter, 31 | clippy::mem_forget, 32 | clippy::multiple_inherent_impl, 33 | // clippy::mut_mut, // TODO: because select macro does this. Not us. Sigh. 34 | clippy::needless_borrow, 35 | clippy::needless_continue, 36 | clippy::needless_pass_by_value, 37 | clippy::non_ascii_literal, 38 | clippy::option_map_unwrap_or, 39 | clippy::option_map_unwrap_or_else, 40 | clippy::path_buf_push_overwrite, 41 | clippy::print_stdout, 42 | clippy::pub_enum_variant_names, 43 | clippy::redundant_closure_for_method_calls, 44 | clippy::replace_consts, 45 | clippy::result_map_unwrap_or_else, 46 | clippy::shadow_reuse, 47 | clippy::shadow_same, 48 | clippy::shadow_unrelated, 49 | clippy::single_match_else, 50 | clippy::string_add, 51 | clippy::string_add_assign, 52 | clippy::type_repetition_in_bounds, 53 | clippy::unicode_not_nfc, 54 | clippy::unseparated_literal_suffix, 55 | clippy::used_underscore_binding, 56 | clippy::wildcard_dependencies, 57 | clippy::wrong_pub_self_convention, 58 | 59 | // TODO: Write docs and constantization. Nice word, constantization. I found it. Thanks. 60 | // clippy::missing_const_for_fn, 61 | // clippy::missing_docs_in_private_items, 62 | )] 63 | 64 | #[macro_use] 65 | extern crate log; 66 | 67 | #[macro_use] 68 | pub mod errors; 69 | 70 | /// Constants of the Artillery 71 | pub mod constants; 72 | 73 | /// Infection-style clustering 74 | pub mod epidemic; 75 | 76 | /// Service discovery strategies 77 | pub mod service_discovery; 78 | 79 | /// Cluster types 80 | pub mod cluster; 81 | -------------------------------------------------------------------------------- /artillery-core/src/service_discovery/mdns/discovery_config.rs: -------------------------------------------------------------------------------- 1 | use crate::constants::*; 2 | use std::net::SocketAddr; 3 | use std::time::Duration; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct MDNSServiceDiscoveryConfig { 7 | pub reply_ttl: Duration, 8 | pub local_service_addr: SocketAddr, 9 | } 10 | 11 | impl Default for MDNSServiceDiscoveryConfig { 12 | fn default() -> Self { 13 | let local_service_addr = SocketAddr::from(([127, 0, 0, 1], CONST_INFECTION_PORT)); 14 | 15 | Self { 16 | reply_ttl: Duration::from_secs(120), 17 | local_service_addr, 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /artillery-core/src/service_discovery/mdns/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod discovery_config; 2 | pub mod sd; 3 | pub mod state; 4 | 5 | pub mod prelude { 6 | pub use super::discovery_config::*; 7 | pub use super::sd::*; 8 | pub use super::state::*; 9 | } 10 | -------------------------------------------------------------------------------- /artillery-core/src/service_discovery/mdns/sd.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::*; 2 | use crate::service_discovery::mdns::discovery_config::MDNSServiceDiscoveryConfig; 3 | use crate::service_discovery::mdns::state::MDNSServiceDiscoveryEvent; 4 | use bastion_executor::blocking::spawn_blocking; 5 | 6 | use libp2p::mdns::service::*; 7 | use libp2p::multiaddr::Protocol; 8 | use libp2p::{identity, Multiaddr, PeerId}; 9 | use lightproc::proc_stack::ProcStack; 10 | 11 | use crossbeam_channel::{unbounded, Receiver}; 12 | use kaos::flunk; 13 | use std::future::Future; 14 | use std::pin::Pin; 15 | use std::sync::Arc; 16 | use std::task::{Context, Poll}; 17 | 18 | pub struct MDNSServiceDiscovery { 19 | events: Arc>, 20 | } 21 | 22 | unsafe impl Send for MDNSServiceDiscovery {} 23 | unsafe impl Sync for MDNSServiceDiscovery {} 24 | 25 | impl MDNSServiceDiscovery { 26 | pub fn new_service_discovery(config: MDNSServiceDiscoveryConfig) -> Result { 27 | let (event_tx, event_rx) = unbounded::(); 28 | 29 | let peer_id = PeerId::from(identity::Keypair::generate_ed25519().public()); 30 | 31 | let _discovery_handle = spawn_blocking( 32 | async move { 33 | let mut service = MdnsService::new().expect("Can't launch the MDNS service"); 34 | 35 | loop { 36 | let (mut srv, packet) = service.next().await; 37 | match packet { 38 | MdnsPacket::Query(query) => { 39 | debug!("Query from {:?}", query.remote_addr()); 40 | let address: Multiaddr = format!( 41 | "/ip4/{}/udp/{}", 42 | config.local_service_addr.ip().to_string(), 43 | config.local_service_addr.port() 44 | ) 45 | .parse() 46 | .unwrap(); 47 | let resp = build_query_response( 48 | query.query_id(), 49 | peer_id.clone(), 50 | vec![address].into_iter(), 51 | config.reply_ttl, 52 | ) 53 | .unwrap(); 54 | srv.enqueue_response(resp); 55 | } 56 | MdnsPacket::Response(response) => { 57 | // We detected a libp2p mDNS response on the network. Responses are for 58 | // everyone and not just for the requester, which makes it possible to 59 | // passively listen. 60 | for peer in response.discovered_peers() { 61 | debug!("Discovered peer {:?}", peer.id()); 62 | // These are the self-reported addresses of the peer we just discovered. 63 | for addr in peer.addresses() { 64 | debug!(" Address = {:?}", addr); 65 | let components = addr.iter().collect::>(); 66 | flunk!("mdns-protocol-fp"); 67 | if let Protocol::Ip4(discovered_ip) = components[0] { 68 | if let Protocol::Udp(discovered_port) = components[1] { 69 | let discovered = 70 | format!("{}:{}", discovered_ip, discovered_port) 71 | .parse() 72 | .unwrap(); 73 | 74 | event_tx 75 | .send(MDNSServiceDiscoveryEvent(discovered)) 76 | .unwrap(); 77 | } else { 78 | error!( 79 | "Unexpected protocol received: {}", 80 | components[1] 81 | ); 82 | } 83 | } else { 84 | error!("Unexpected IP received: {}", components[0]); 85 | } 86 | } 87 | } 88 | } 89 | MdnsPacket::ServiceDiscovery(query) => { 90 | // The last possibility is a service detection query from DNS-SD. 91 | // Just like `Query`, in a real application you probably want to call 92 | // `query.respond`. 93 | debug!("Detected service query from {:?}", query.remote_addr()); 94 | } 95 | } 96 | service = srv 97 | } 98 | }, 99 | ProcStack::default(), 100 | ); 101 | 102 | Ok(Self { 103 | events: Arc::new(event_rx), 104 | }) 105 | } 106 | 107 | pub fn events(&self) -> Arc> { 108 | self.events.clone() 109 | } 110 | } 111 | 112 | impl Future for MDNSServiceDiscovery { 113 | type Output = MDNSServiceDiscoveryEvent; 114 | 115 | fn poll(self: Pin<&mut Self>, _cx: &mut Context) -> Poll { 116 | match self.events.recv() { 117 | Ok(kv) => Poll::Ready(kv), 118 | Err(_) => Poll::Pending, 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /artillery-core/src/service_discovery/mdns/state.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | pub struct MDNSServiceDiscoveryEvent(pub SocketAddr); 4 | 5 | unsafe impl Send for MDNSServiceDiscoveryEvent {} 6 | unsafe impl Sync for MDNSServiceDiscoveryEvent {} 7 | 8 | impl MDNSServiceDiscoveryEvent { 9 | pub fn get(&self) -> SocketAddr { 10 | self.0 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /artillery-core/src/service_discovery/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod mdns; 2 | pub mod udp_anycast; 3 | -------------------------------------------------------------------------------- /artillery-core/src/service_discovery/udp_anycast/discovery_config.rs: -------------------------------------------------------------------------------- 1 | use crate::constants::*; 2 | use chrono::Duration; 3 | use std::net::{SocketAddr, ToSocketAddrs}; 4 | 5 | pub struct MulticastServiceDiscoveryConfig { 6 | pub timeout_delta: Duration, 7 | pub seeking_addr: SocketAddr, 8 | pub discovery_addr: SocketAddr, 9 | } 10 | 11 | impl Default for MulticastServiceDiscoveryConfig { 12 | fn default() -> Self { 13 | let discovery_addr = SocketAddr::from(([0, 0, 0, 0], CONST_SERVICE_DISCOVERY_PORT)); 14 | let seeking_addr = SocketAddr::from(([255, 255, 255, 255], CONST_SERVICE_DISCOVERY_PORT)); 15 | 16 | Self { 17 | timeout_delta: Duration::seconds(1), 18 | seeking_addr: seeking_addr.to_socket_addrs().unwrap().next().unwrap(), 19 | discovery_addr: discovery_addr.to_socket_addrs().unwrap().next().unwrap(), 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /artillery-core/src/service_discovery/udp_anycast/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod discovery_config; 2 | pub mod state; 3 | 4 | pub mod sd; 5 | 6 | pub mod prelude { 7 | pub use super::discovery_config::*; 8 | pub use super::sd::*; 9 | pub use super::state::*; 10 | } 11 | -------------------------------------------------------------------------------- /artillery-core/src/service_discovery/udp_anycast/sd.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::*; 2 | use crate::service_discovery::udp_anycast::discovery_config::MulticastServiceDiscoveryConfig; 3 | use crate::service_discovery::udp_anycast::state::MulticastServiceDiscoveryState; 4 | use crate::service_discovery::udp_anycast::state::{ 5 | ServiceDiscoveryReply, ServiceDiscoveryRequest, 6 | }; 7 | use bastion_executor::blocking::spawn_blocking; 8 | use cuneiform_fields::arch::ArchPadding; 9 | use lightproc::proc_stack::ProcStack; 10 | use std::sync::mpsc; 11 | use std::sync::mpsc::{channel, Sender}; 12 | 13 | pub struct MulticastServiceDiscovery { 14 | comm: ArchPadding>, 15 | } 16 | 17 | impl MulticastServiceDiscovery { 18 | pub fn new_service_discovery( 19 | config: MulticastServiceDiscoveryConfig, 20 | discovery_reply: ServiceDiscoveryReply, 21 | ) -> Result { 22 | let (internal_tx, mut internal_rx) = channel::(); 23 | let (poll, state) = MulticastServiceDiscoveryState::new(config, discovery_reply)?; 24 | 25 | debug!("Starting Artillery Multicast SD"); 26 | let _multicast_sd_handle = spawn_blocking( 27 | async move { 28 | MulticastServiceDiscoveryState::event_loop(&mut internal_rx, poll, state) 29 | .expect("Failed to create event loop"); 30 | }, 31 | ProcStack::default(), 32 | ); 33 | 34 | Ok(Self { 35 | comm: ArchPadding::new(internal_tx), 36 | }) 37 | } 38 | 39 | /// Register a new observer to be notified whenever we 40 | /// successfully find peers by interrogating the network. 41 | pub fn register_seeker(&self, observer: mpsc::Sender) -> Result<()> { 42 | let observer = ArchPadding::new(observer); 43 | Ok(self 44 | .comm 45 | .send(ServiceDiscoveryRequest::RegisterObserver(observer))?) 46 | } 47 | 48 | /// Enable or disable listening and responding to peers searching for us. This will 49 | /// correspondingly allow or disallow others from finding us by interrogating the network. 50 | pub fn set_listen_for_peers(&self, listen: bool) -> Result<()> { 51 | Ok(self 52 | .comm 53 | .send(ServiceDiscoveryRequest::SetBroadcastListen(listen))?) 54 | } 55 | 56 | /// Explore the network to find nodes using `udp_anycast` SD. 57 | pub fn seek_peers(&self) -> Result<()> { 58 | Ok(self.comm.send(ServiceDiscoveryRequest::SeekPeers)?) 59 | } 60 | 61 | /// Shutdown Service Discovery 62 | pub fn shutdown(&mut self) -> Result<()> { 63 | self.discovery_exit(); 64 | Ok(()) 65 | } 66 | 67 | fn discovery_exit(&mut self) { 68 | let (tx, rx) = channel(); 69 | self.comm.send(ServiceDiscoveryRequest::Exit(tx)).unwrap(); 70 | rx.recv().unwrap(); 71 | } 72 | } 73 | 74 | unsafe impl Send for MulticastServiceDiscovery {} 75 | unsafe impl Sync for MulticastServiceDiscovery {} 76 | 77 | impl Drop for MulticastServiceDiscovery { 78 | fn drop(&mut self) { 79 | self.discovery_exit(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /artillery-core/src/service_discovery/udp_anycast/state.rs: -------------------------------------------------------------------------------- 1 | use crate::constants::*; 2 | use crate::errors::*; 3 | use crate::service_discovery::udp_anycast::discovery_config::MulticastServiceDiscoveryConfig; 4 | use std::convert::TryFrom; 5 | 6 | use cuneiform_fields::arch::ArchPadding; 7 | use mio::net::UdpSocket; 8 | use mio::{Events, Interest, Poll, Token}; 9 | 10 | use serde::*; 11 | use std::collections::VecDeque; 12 | 13 | use std::net::SocketAddr; 14 | 15 | use std::sync::mpsc::{Receiver, Sender}; 16 | use std::time::{Duration, Instant}; 17 | 18 | use kaos::flunk; 19 | 20 | #[derive(Serialize, Deserialize, Debug, Clone, PartialOrd, PartialEq, Ord, Eq)] 21 | /// Default acknowledgement reply for the Discovery. 22 | pub struct ServiceDiscoveryReply { 23 | /// Serialized data which can be contained in replies. 24 | pub serialized_data: String, 25 | } 26 | 27 | impl Default for ServiceDiscoveryReply { 28 | fn default() -> Self { 29 | Self { 30 | serialized_data: "DONE".into(), 31 | } 32 | } 33 | } 34 | 35 | pub(crate) enum ServiceDiscoveryRequest { 36 | RegisterObserver(ArchPadding>), 37 | SetBroadcastListen(bool), 38 | SeekPeers, 39 | Exit(Sender<()>), 40 | } 41 | 42 | #[derive(Clone, Debug, Serialize, Deserialize)] 43 | #[serde(tag = "t", content = "c")] 44 | enum ServiceDiscoveryMessage { 45 | Request, 46 | Response { 47 | uid: u32, 48 | content: ServiceDiscoveryReply, 49 | }, 50 | } 51 | 52 | const ON_DISCOVERY: Token = Token(0); 53 | const SEEK_NODES: Token = Token(1); 54 | 55 | pub struct MulticastServiceDiscoveryState { 56 | config: MulticastServiceDiscoveryConfig, 57 | server_socket: UdpSocket, 58 | seek_request: Vec, 59 | observers: Vec>>, 60 | seeker_replies: VecDeque, 61 | default_reply: ServiceDiscoveryReply, 62 | uid: u32, 63 | running: bool, 64 | listen: bool, 65 | } 66 | 67 | pub type ServiceDiscoveryReactor = (Poll, MulticastServiceDiscoveryState); 68 | 69 | impl MulticastServiceDiscoveryState { 70 | pub(crate) fn new( 71 | config: MulticastServiceDiscoveryConfig, 72 | discovery_reply: ServiceDiscoveryReply, 73 | ) -> Result { 74 | let poll: Poll = Poll::new()?; 75 | 76 | let mut server_socket = UdpSocket::bind(config.discovery_addr)?; 77 | server_socket.set_broadcast(true)?; 78 | 79 | poll.registry() 80 | .register(&mut server_socket, ON_DISCOVERY, get_interests())?; 81 | 82 | let uid = rand::random(); 83 | let seek_request = serde_json::to_string(&ServiceDiscoveryMessage::Request)?; 84 | 85 | let state = MulticastServiceDiscoveryState { 86 | config, 87 | server_socket, 88 | seek_request: seek_request.as_bytes().into(), 89 | observers: Vec::new(), 90 | seeker_replies: VecDeque::new(), 91 | default_reply: discovery_reply, 92 | uid, 93 | listen: false, 94 | running: true, 95 | }; 96 | 97 | Ok((poll, state)) 98 | } 99 | 100 | fn readable(&mut self, buf: &mut [u8], poll: &mut Poll) -> Result<()> { 101 | if let Ok((_bytes_read, peer_addr)) = self.server_socket.recv_from(buf) { 102 | let serialized = std::str::from_utf8(buf)?.to_string().trim().to_string(); 103 | let serialized = serialized.trim_matches(char::from(0x00)); 104 | let msg: ServiceDiscoveryMessage = if let Ok(msg) = serde_json::from_str(serialized) { 105 | msg 106 | } else { 107 | return Ok(()); 108 | }; 109 | 110 | match msg { 111 | ServiceDiscoveryMessage::Request => { 112 | if self.listen { 113 | self.seeker_replies.push_back(peer_addr); 114 | poll.registry().reregister( 115 | &mut self.server_socket, 116 | ON_DISCOVERY, 117 | Interest::WRITABLE, 118 | )?; 119 | } else { 120 | poll.registry().reregister( 121 | &mut self.server_socket, 122 | ON_DISCOVERY, 123 | Interest::READABLE, 124 | )?; 125 | } 126 | } 127 | ServiceDiscoveryMessage::Response { uid, content } => { 128 | if uid != self.uid { 129 | self.observers 130 | .retain(|observer| observer.send(content.clone()).is_ok()); 131 | } 132 | poll.registry().reregister( 133 | &mut self.server_socket, 134 | ON_DISCOVERY, 135 | Interest::READABLE, 136 | )?; 137 | } 138 | } 139 | } 140 | 141 | Ok(()) 142 | } 143 | 144 | fn writable(&mut self, poll: &mut Poll, token: Token) -> Result<()> { 145 | match token { 146 | ON_DISCOVERY => { 147 | let reply = ServiceDiscoveryMessage::Response { 148 | uid: self.uid, 149 | content: self.default_reply.clone(), 150 | }; 151 | let discovery_reply = serde_json::to_vec(&reply)?; 152 | 153 | while let Some(peer_addr) = self.seeker_replies.pop_front() { 154 | let mut sent_bytes = 0; 155 | while sent_bytes != discovery_reply.len() { 156 | flunk!("udp-anycast-reply-dgram-oob-fp"); 157 | if let Ok(bytes_tx) = self 158 | .server_socket 159 | .send_to(&discovery_reply[sent_bytes..], peer_addr) 160 | { 161 | sent_bytes += bytes_tx; 162 | } else { 163 | poll.registry().reregister( 164 | &mut self.server_socket, 165 | ON_DISCOVERY, 166 | Interest::WRITABLE, 167 | )?; 168 | return Ok(()); 169 | } 170 | } 171 | } 172 | } 173 | SEEK_NODES => { 174 | let mut sent_bytes = 0; 175 | while sent_bytes != self.seek_request.len() { 176 | flunk!("udp-anycast-dgram-oob-fp"); 177 | if let Ok(bytes_tx) = self 178 | .server_socket 179 | .send_to(&self.seek_request[sent_bytes..], self.config.seeking_addr) 180 | { 181 | sent_bytes += bytes_tx; 182 | } else { 183 | poll.registry().reregister( 184 | &mut self.server_socket, 185 | SEEK_NODES, 186 | Interest::WRITABLE, 187 | )?; 188 | return Ok(()); 189 | } 190 | } 191 | } 192 | _ => (), 193 | } 194 | 195 | Ok(poll 196 | .registry() 197 | .reregister(&mut self.server_socket, ON_DISCOVERY, Interest::WRITABLE)?) 198 | } 199 | 200 | pub(crate) fn event_loop( 201 | receiver: &mut Receiver, 202 | mut poll: Poll, 203 | mut state: MulticastServiceDiscoveryState, 204 | ) -> Result<()> { 205 | let mut events = Events::with_capacity(1); 206 | let mut buf = [0_u8; CONST_PACKET_SIZE]; 207 | 208 | let mut start = Instant::now(); 209 | let timeout = Duration::from_millis(u64::try_from( 210 | state.config.timeout_delta.num_milliseconds(), 211 | )?); 212 | 213 | // Our event loop. 214 | loop { 215 | let elapsed = start.elapsed(); 216 | 217 | if elapsed >= timeout { 218 | start = Instant::now(); 219 | } 220 | 221 | if !state.running { 222 | debug!("Stopping artillery udp_anycast service discovery evloop"); 223 | break; 224 | } 225 | 226 | // Poll to check if we have events waiting for us. 227 | if let Some(remaining) = timeout.checked_sub(elapsed) { 228 | trace!("Polling events in SD evloop"); 229 | poll.poll(&mut events, Some(remaining))?; 230 | } 231 | 232 | // Process our own events that are submitted to event loop 233 | // This is internal state machinery. 234 | while let Ok(msg) = receiver.try_recv() { 235 | let exit_tx = state.process_internal_request(&mut poll, msg); 236 | 237 | if let Some(exit_tx) = exit_tx { 238 | debug!("Exit received!"); 239 | state.running = false; 240 | exit_tx.send(()).unwrap(); 241 | } 242 | } 243 | 244 | // Process inbound events 245 | for event in events.iter() { 246 | if event.is_readable() && event.token() == ON_DISCOVERY { 247 | if let Err(err) = state.readable(&mut buf, &mut poll) { 248 | error!("Service discovery error in READABLE: {:?}", err); 249 | break; 250 | } 251 | } 252 | 253 | if event.is_writable() { 254 | if let Err(err) = state.writable(&mut poll, event.token()) { 255 | error!("Service discovery error in WRITABLE: {:?}", err); 256 | break; 257 | } 258 | } 259 | } 260 | } 261 | 262 | info!("Exiting..."); 263 | Ok(()) 264 | } 265 | 266 | fn process_internal_request( 267 | &mut self, 268 | poll: &mut Poll, 269 | msg: ServiceDiscoveryRequest, 270 | ) -> Option> { 271 | use ServiceDiscoveryRequest::*; 272 | 273 | match msg { 274 | RegisterObserver(sender) => self.observers.push(sender), 275 | SetBroadcastListen(bcast_listen) => { 276 | self.listen = bcast_listen; 277 | } 278 | SeekPeers => { 279 | match self 280 | .server_socket 281 | .send_to(&self.seek_request, self.config.seeking_addr) 282 | { 283 | Ok(_) => { 284 | if let Err(err) = poll.registry().reregister( 285 | &mut self.server_socket, 286 | ON_DISCOVERY, 287 | Interest::READABLE, 288 | ) { 289 | error!("Reregistry error for Discovery: {:?}", err); 290 | self.running = false; 291 | } 292 | } 293 | Err(_err) => { 294 | if let Err(err) = poll.registry().reregister( 295 | &mut self.server_socket, 296 | SEEK_NODES, 297 | Interest::WRITABLE, 298 | ) { 299 | error!("Reregistry error for Seeking: {:?}", err); 300 | self.running = false; 301 | } 302 | } 303 | } 304 | } 305 | Exit(tx) => return Some(tx), 306 | }; 307 | 308 | None 309 | } 310 | } 311 | 312 | #[inline] 313 | fn get_interests() -> Interest { 314 | Interest::READABLE.add(Interest::WRITABLE) 315 | } 316 | -------------------------------------------------------------------------------- /artillery-ddata/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /artillery-ddata/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "artillery-ddata" 3 | version = "0.1.0" 4 | authors = ["Mahmut Bulut "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | log = "0.4" 9 | failure = "0.1.7" 10 | thrift = "0.13.0" 11 | t1ha = "0.1" 12 | crossbeam-channel = "0.4" 13 | 14 | 15 | [dev-dependencies] 16 | clap = "2.33.0" 17 | pretty_env_logger = "0.4.0" 18 | bastion-executor = "0.3.5" 19 | lightproc = "0.3.5" 20 | rand = "0.7" 21 | criterion = "0.3" 22 | futures = "0.3" 23 | 24 | 25 | [[bench]] 26 | name = "craq_bencher" 27 | harness = false 28 | -------------------------------------------------------------------------------- /artillery-ddata/benches/craq_bencher.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | 3 | use artillery_ddata::craq::prelude::*; 4 | 5 | use rand::distributions::Alphanumeric; 6 | use rand::prelude::*; 7 | 8 | pub fn entry_bench_read(sip: String, args: Vec<&str>) -> Vec { 9 | // Connect to servers 10 | let num_clients: usize = args[0].parse::().unwrap(); 11 | let num_bytes: usize = args[1].parse::().unwrap(); 12 | let _trials: usize = args[2].parse::().unwrap(); 13 | let num_servers = args.len() - 3; 14 | 15 | let hpc: Vec<&str> = sip.split(":").collect(); 16 | let mut hosts = vec![(hpc[0], hpc[1].parse::().unwrap())]; 17 | 18 | (0..num_servers).into_iter().for_each(|i| { 19 | let hpc: Vec<&str> = args[i + 3].split(":").collect(); 20 | let host = hpc[0]; 21 | let port = hpc[1]; 22 | let port = port.parse::().unwrap(); 23 | 24 | hosts.extend([(host, port)].iter()); 25 | }); 26 | 27 | let mut clients: Vec = (0..num_clients) 28 | .into_iter() 29 | .map(|i| { 30 | let (host, port) = hosts[i % hosts.len()]; 31 | DDataCraqClient::connect_host_port(host, port).unwrap() 32 | }) 33 | .collect(); 34 | 35 | if clients[0].write(gen_random_str(num_bytes)).is_err() { 36 | println!("bench_write: Couldn't write new revision."); 37 | } 38 | 39 | // Check if any object is written... 40 | if clients[0].read(CraqConsistencyModel::Strong, 0).is_err() { 41 | println!("bench_read: Could not read object."); 42 | } 43 | 44 | clients 45 | } 46 | 47 | pub fn gen_random_str(slen: usize) -> String { 48 | thread_rng().sample_iter(&Alphanumeric).take(slen).collect() 49 | } 50 | 51 | pub fn stub_read(clients: &mut Vec) { 52 | clients.iter_mut().for_each(|client| { 53 | let _ = client.read(CraqConsistencyModel::Eventual, 0); 54 | }); 55 | } 56 | 57 | fn client_benchmarks(c: &mut Criterion) { 58 | { 59 | // 1 clients 60 | let mut clients = entry_bench_read( 61 | "127.0.0.1:30001".to_string(), 62 | vec!["1", "1000", "100", "127.0.0.1:30002", "127.0.0.1:30003"], 63 | ); 64 | c.bench_function("benchmark_read_1_client", |b| { 65 | b.iter(|| stub_read(&mut clients)) 66 | }); 67 | } 68 | 69 | { 70 | // 2 clients 71 | let mut clients = entry_bench_read( 72 | "127.0.0.1:30001".to_string(), 73 | vec!["2", "1000", "100", "127.0.0.1:30002", "127.0.0.1:30003"], 74 | ); 75 | c.bench_function("benchmark_read_2_clients", |b| { 76 | b.iter(|| stub_read(&mut clients)) 77 | }); 78 | } 79 | 80 | { 81 | // 3 clients 82 | let mut clients = entry_bench_read( 83 | "127.0.0.1:30001".to_string(), 84 | vec!["3", "1000", "100", "127.0.0.1:30002", "127.0.0.1:30003"], 85 | ); 86 | c.bench_function("benchmark_read_3_clients", |b| { 87 | b.iter(|| stub_read(&mut clients)) 88 | }); 89 | } 90 | 91 | { 92 | // 4 clients 93 | let mut clients = entry_bench_read( 94 | "127.0.0.1:30001".to_string(), 95 | vec!["4", "1000", "100", "127.0.0.1:30002", "127.0.0.1:30003"], 96 | ); 97 | c.bench_function("benchmark_read_4_clients", |b| { 98 | b.iter(|| stub_read(&mut clients)) 99 | }); 100 | } 101 | 102 | { 103 | // 5 clients 104 | let mut clients = entry_bench_read( 105 | "127.0.0.1:30001".to_string(), 106 | vec!["5", "1000", "100", "127.0.0.1:30002", "127.0.0.1:30003"], 107 | ); 108 | c.bench_function("benchmark_read_5_clients", |b| { 109 | b.iter(|| stub_read(&mut clients)) 110 | }); 111 | } 112 | 113 | { 114 | // 10 clients 115 | let mut clients = entry_bench_read( 116 | "127.0.0.1:30001".to_string(), 117 | vec!["10", "1000", "100", "127.0.0.1:30002", "127.0.0.1:30003"], 118 | ); 119 | c.bench_function("benchmark_read_10_clients", |b| { 120 | b.iter(|| stub_read(&mut clients)) 121 | }); 122 | } 123 | 124 | { 125 | // 20 clients 126 | let mut clients = entry_bench_read( 127 | "127.0.0.1:30001".to_string(), 128 | vec!["20", "1000", "100", "127.0.0.1:30002", "127.0.0.1:30003"], 129 | ); 130 | c.bench_function("benchmark_read_20_clients", |b| { 131 | b.iter(|| stub_read(&mut clients)) 132 | }); 133 | } 134 | 135 | { 136 | // 30 clients 137 | let mut clients = entry_bench_read( 138 | "127.0.0.1:30001".to_string(), 139 | vec!["30", "1000", "100", "127.0.0.1:30002", "127.0.0.1:30003"], 140 | ); 141 | c.bench_function("benchmark_read_30_clients", |b| { 142 | b.iter(|| stub_read(&mut clients)) 143 | }); 144 | } 145 | 146 | { 147 | // 40 clients 148 | let mut clients = entry_bench_read( 149 | "127.0.0.1:30001".to_string(), 150 | vec!["40", "1000", "100", "127.0.0.1:30002", "127.0.0.1:30003"], 151 | ); 152 | c.bench_function("benchmark_read_40_clients", |b| { 153 | b.iter(|| stub_read(&mut clients)) 154 | }); 155 | } 156 | 157 | { 158 | // 50 clients 159 | let mut clients = entry_bench_read( 160 | "127.0.0.1:30001".to_string(), 161 | vec!["50", "1000", "100", "127.0.0.1:30002", "127.0.0.1:30003"], 162 | ); 163 | c.bench_function("benchmark_read_50_clients", |b| { 164 | b.iter(|| stub_read(&mut clients)) 165 | }); 166 | } 167 | 168 | { 169 | // 100 clients 170 | let mut clients = entry_bench_read( 171 | "127.0.0.1:30001".to_string(), 172 | vec!["100", "1000", "100", "127.0.0.1:30002", "127.0.0.1:30003"], 173 | ); 174 | c.bench_function("benchmark_read_100_clients", |b| { 175 | b.iter(|| stub_read(&mut clients)) 176 | }); 177 | } 178 | 179 | // { 180 | // // 500 clients 181 | // let mut clients = entry_bench_read("127.0.0.1:30001".to_string(), vec!["500", "1000", "100", "127.0.0.1:30002", "127.0.0.1:30003"]); 182 | // c.bench_function("benchmark_read_500_clients", |b| b.iter(|| stub_read(&mut clients))); 183 | // } 184 | 185 | // { 186 | // // 1000 clients 187 | // let mut clients = entry_bench_read("127.0.0.1:30001".to_string(), vec!["1000", "1000", "100", "127.0.0.1:30002", "127.0.0.1:30003"]); 188 | // c.bench_function("benchmark_read_1000_clients", |b| b.iter(|| stub_read(&mut clients))); 189 | // } 190 | } 191 | 192 | criterion_group!(benches, client_benchmarks); 193 | criterion_main!(benches); 194 | -------------------------------------------------------------------------------- /artillery-ddata/examples/craq_node.rs: -------------------------------------------------------------------------------- 1 | extern crate pretty_env_logger; 2 | 3 | #[macro_use] 4 | extern crate log; 5 | 6 | use artillery_ddata::craq::prelude::*; 7 | use clap::*; 8 | 9 | fn main() { 10 | pretty_env_logger::init(); 11 | 12 | let matches = App::new("Artillery CRAQ") 13 | .author("Mahmut Bulut, vertexclique [ta] gmail [tod] com") 14 | .version(crate_version!()) 15 | .about("Artillery Distributed Data Protocol Tester") 16 | .subcommand( 17 | SubCommand::with_name("server") 18 | .about("Runs a CRAQ server") 19 | .arg( 20 | Arg::with_name("cr_mode") 21 | .required(true) 22 | .help("CR mode that server would use: 0 for CRAQ, 1 for CR") 23 | .index(1), 24 | ) 25 | .arg( 26 | Arg::with_name("node_index") 27 | .required(true) 28 | .help("Node index this server would use") 29 | .index(2), 30 | ) 31 | .arg( 32 | Arg::with_name("chain_servers") 33 | .required(true) 34 | .multiple(true), 35 | ), 36 | ) 37 | .subcommand( 38 | SubCommand::with_name("client") 39 | .about("Runs a CRAQ client") 40 | .arg( 41 | Arg::with_name("server_ip_port") 42 | .required(true) 43 | .help("Server ip and port to connect") 44 | .index(1), 45 | ) 46 | .arg( 47 | Arg::with_name("test_method") 48 | .required(true) 49 | .help("Test method of client to test against the server") 50 | .index(2), 51 | ) 52 | .arg( 53 | Arg::with_name("extra_args") 54 | .required(true) 55 | .multiple(true) 56 | .min_values(3), 57 | ), 58 | ) 59 | .after_help("Enables Artillery CRAQ protocol to be tested in the server/client fashion") 60 | .get_matches(); 61 | 62 | match matches.subcommand() { 63 | ("server", Some(server_matches)) => { 64 | let cr_mode = match server_matches.value_of("cr_mode") { 65 | Some("0") => CRMode::Craq, 66 | Some("1") => CRMode::Cr, 67 | _ => panic!("CR mode not as expected"), 68 | }; 69 | 70 | if let Some(node_index) = server_matches.value_of("node_index") { 71 | let node_index = node_index.parse::().unwrap(); 72 | let varargs: Vec<&str> = 73 | server_matches.values_of("chain_servers").unwrap().collect(); 74 | 75 | let nodes: Vec = varargs.iter().flat_map(ChainNode::new).collect(); 76 | 77 | assert_eq!(nodes.len(), varargs.len(), "Node address parsing failed"); 78 | 79 | let chain = CraqChain::new(&nodes, node_index).unwrap(); 80 | CraqNode::start(cr_mode, chain, CraqConfig::default()) 81 | .expect("couldn't start CRAQ node"); 82 | } 83 | } 84 | ("client", Some(client_matches)) => { 85 | let _sip = client_matches 86 | .value_of("server_ip_port") 87 | .unwrap() 88 | .to_string(); 89 | 90 | match client_matches.value_of("test_method") { 91 | Some("bench_read") => todo!(), 92 | _ => unreachable!(), 93 | } 94 | } 95 | _ => { 96 | error!("Couldn't find any known subcommands"); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /artillery-ddata/src/craq/chain.rs: -------------------------------------------------------------------------------- 1 | use super::chain_node::ChainNode; 2 | use super::errors::*; 3 | use std::fmt; 4 | use std::fmt::Display; 5 | 6 | /// 7 | /// Representation of a closed-loop CRAQ chain 8 | #[derive(Default, Debug)] 9 | pub struct CraqChain { 10 | /// List of nodes in this chain, in order. 11 | nodes: Vec, 12 | /// Index of this node. 13 | node_idx: usize, 14 | } 15 | 16 | impl CraqChain { 17 | /// 18 | /// Create a new chain. 19 | pub fn new(nodes: &[ChainNode], node_idx: usize) -> Result { 20 | ensure!( 21 | node_idx < nodes.len(), 22 | "Node index can't be greater than chain length." 23 | ); 24 | 25 | Ok(Self { 26 | nodes: nodes.to_vec(), 27 | node_idx, 28 | }) 29 | } 30 | 31 | /// 32 | /// Returns whether this node is the head of its chain. 33 | pub fn is_head(&self) -> bool { 34 | self.node_idx == 0 35 | } 36 | 37 | /// 38 | /// Returns the successor node if exists 39 | pub fn is_tail(&self) -> bool { 40 | self.node_idx == self.nodes.len().saturating_sub(1) 41 | } 42 | 43 | /// 44 | /// Returns the successor node if exists 45 | pub fn get_successor(&self) -> Option<&ChainNode> { 46 | if self.is_tail() { 47 | None 48 | } else { 49 | self.nodes.get(self.node_idx.saturating_add(1)) 50 | } 51 | } 52 | 53 | /// 54 | /// Returns the tail node. 55 | pub fn get_tail(&self) -> Option<&ChainNode> { 56 | self.nodes.last() 57 | } 58 | 59 | /// 60 | /// Returns the chain node associated with the current node index. 61 | pub fn get_node(&self) -> Option<&ChainNode> { 62 | self.nodes.get(self.node_idx) 63 | } 64 | 65 | /// 66 | /// Returns the current node index. 67 | pub fn get_index(&self) -> usize { 68 | self.node_idx 69 | } 70 | 71 | /// 72 | /// Returns the size of this chain. 73 | pub fn chain_size(&self) -> usize { 74 | self.nodes.len() 75 | } 76 | } 77 | 78 | /// 79 | /// Human-readable display impl for the Chain 80 | impl Display for CraqChain { 81 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 82 | write!( 83 | f, 84 | "CR: Index [{}] in chain: {:#?}", 85 | self.node_idx, self.nodes 86 | ) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /artillery-ddata/src/craq/chain_node.rs: -------------------------------------------------------------------------------- 1 | use super::errors::*; 2 | use std::net::{SocketAddr, ToSocketAddrs}; 3 | 4 | /// 5 | /// Chain node representation 6 | #[derive(Debug, Clone)] 7 | pub struct ChainNode { 8 | host: SocketAddr, 9 | } 10 | 11 | impl ChainNode { 12 | pub fn new(addr: A) -> Result 13 | where 14 | A: ToSocketAddrs, 15 | { 16 | let host: SocketAddr = addr 17 | .to_socket_addrs()? 18 | .next() 19 | .ok_or_else(|| CraqError::SocketAddrError("No node address given or parsed.".into()))?; 20 | Ok(Self { host }) 21 | } 22 | 23 | pub fn get_addr(&self) -> &SocketAddr { 24 | &self.host 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /artillery-ddata/src/craq/client.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use super::errors::*; 4 | use super::{ 5 | node::CraqClient, 6 | proto::{CraqConsistencyModel, CraqObject, TCraqServiceSyncClient}, 7 | }; 8 | use std::net::{SocketAddr, ToSocketAddrs}; 9 | use thrift::protocol::{TBinaryInputProtocol, TBinaryOutputProtocol}; 10 | use thrift::transport::{ 11 | TFramedReadTransport, TFramedWriteTransport, TIoChannel, TTcpChannel as BiTcp, 12 | }; 13 | 14 | pub struct ReadObject { 15 | /// 16 | /// Object's value. 17 | value: Vec, 18 | /// 19 | /// Whether the read was dirty (true) or clean (false). 20 | dirty: bool, 21 | } 22 | 23 | impl ReadObject { 24 | /// 25 | /// Creates a new wrapper Read Object 26 | pub fn new(value: Vec, dirty: bool) -> Self { 27 | Self { value, dirty } 28 | } 29 | } 30 | 31 | impl fmt::Debug for ReadObject { 32 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 33 | f.debug_struct("ReadObject") 34 | .field("value", &self.value) 35 | .field("dirty", &self.dirty) 36 | .finish() 37 | } 38 | } 39 | 40 | impl fmt::Display for ReadObject { 41 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 42 | f.debug_struct("ReadObject") 43 | .field("value", &self.value) 44 | .field("dirty", &self.dirty) 45 | .finish() 46 | } 47 | } 48 | 49 | // Will be fixed as we implement stuff 50 | #[allow(dead_code)] 51 | pub struct DDataCraqClient { 52 | host: SocketAddr, 53 | cc: CraqClient, 54 | } 55 | 56 | impl DDataCraqClient { 57 | pub fn connect_host_port(host: T, port: u16) -> Result 58 | where 59 | T: AsRef, 60 | { 61 | Self::connect(format!("{}:{}", host.as_ref(), port)) 62 | } 63 | 64 | pub fn connect(addr: A) -> Result 65 | where 66 | A: ToSocketAddrs, 67 | { 68 | let host: SocketAddr = addr 69 | .to_socket_addrs()? 70 | .next() 71 | .ok_or_else(|| CraqError::SocketAddrError("No node address given or parsed.".into()))?; 72 | 73 | debug!("Client is initiating connection to: {}", host); 74 | 75 | let mut c = BiTcp::new(); 76 | c.open(&host.to_string())?; 77 | let (i_chan, o_chan) = c.split()?; 78 | let (i_tran, o_tran) = ( 79 | TFramedReadTransport::new(i_chan), 80 | TFramedWriteTransport::new(o_chan), 81 | ); 82 | let (i_prot, o_prot) = ( 83 | TBinaryInputProtocol::new(i_tran, true), 84 | TBinaryOutputProtocol::new(o_tran, true), 85 | ); 86 | 87 | debug!("Created client: {}", host); 88 | let cc = CraqClient::new(i_prot, o_prot); 89 | Ok(Self { host, cc }) 90 | } 91 | 92 | /// 93 | /// Writes an object to the cluster, returning the new object version or -1 upon failure. 94 | pub fn write(&mut self, value: String) -> Result { 95 | let mut obj = CraqObject::default(); 96 | obj.value = Some(value.into_bytes()); 97 | Ok(self.cc.write(obj)?) 98 | } 99 | 100 | /// 101 | /// Reads an object with given bound version. 102 | pub fn read(&mut self, model: CraqConsistencyModel, version_bound: i64) -> Result { 103 | let obj = self.cc.read(model, version_bound)?; 104 | 105 | match (obj.value, obj.dirty) { 106 | (Some(v), Some(d)) => Ok(ReadObject::new(v, d)), 107 | _ => bail!(CraqError::ReadError, "Read request failed"), 108 | } 109 | } 110 | 111 | /// 112 | /// Performs a test-and-set operation, returning the new object version or -1 upon failure. 113 | pub fn test_and_set(&mut self, value: String, expected_version: i64) -> Result { 114 | let mut obj = CraqObject::default(); 115 | obj.value = Some(value.into_bytes()); 116 | Ok(self.cc.test_and_set(obj, expected_version)?) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /artillery-ddata/src/craq/craq_config.rs: -------------------------------------------------------------------------------- 1 | use super::node::CRMode; 2 | 3 | #[derive(Debug, Clone)] 4 | pub struct CraqConfig { 5 | pub fallback_replication_port: u16, 6 | pub operation_mode: CRMode, 7 | pub connection_sleep_time: u64, 8 | pub connection_pool_size: usize, 9 | pub protocol_worker_size: usize, 10 | } 11 | 12 | impl Default for CraqConfig { 13 | fn default() -> Self { 14 | CraqConfig { 15 | fallback_replication_port: 22991_u16, 16 | operation_mode: CRMode::Craq, 17 | connection_sleep_time: 1000_u64, 18 | connection_pool_size: 50_usize, 19 | protocol_worker_size: 100_usize, 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /artillery-ddata/src/craq/errors.rs: -------------------------------------------------------------------------------- 1 | use failure::Fail; 2 | use std::io; 3 | 4 | use std::result; 5 | 6 | /// Result type for operations that could result in an `CraqError` 7 | pub type Result = result::Result; 8 | 9 | #[derive(Fail, Debug)] 10 | pub enum CraqError { 11 | #[fail(display = "Artillery :: CRAQ :: I/O error occurred: {}", _0)] 12 | IOError(io::Error), 13 | #[fail(display = "Artillery :: CRAQ :: Socket addr: {}", _0)] 14 | SocketAddrError(String), 15 | #[fail(display = "Artillery :: CRAQ :: Assertion failed: {}", _0)] 16 | AssertionError(String, failure::Backtrace), 17 | #[fail(display = "Artillery :: CRAQ :: Protocol error: {}", _0)] 18 | ProtocolError(thrift::Error), 19 | #[fail(display = "Artillery :: CRAQ :: Read error: {}", _0)] 20 | ReadError(String), 21 | } 22 | 23 | impl From for CraqError { 24 | fn from(e: io::Error) -> Self { 25 | CraqError::IOError(e) 26 | } 27 | } 28 | 29 | impl From for CraqError { 30 | fn from(e: thrift::Error) -> Self { 31 | CraqError::ProtocolError(e) 32 | } 33 | } 34 | 35 | #[macro_export] 36 | macro_rules! bail { 37 | ($kind:expr, $e:expr) => { 38 | return Err($kind($e.to_owned())); 39 | }; 40 | ($kind:expr, $fmt:expr, $($arg:tt)+) => { 41 | return Err($kind(format!($fmt, $($arg)+).to_owned())); 42 | }; 43 | } 44 | 45 | macro_rules! ensure { 46 | ($cond:expr, $e:expr) => { 47 | if !($cond) { 48 | return Err(CraqError::AssertionError($e.to_string(), failure::Backtrace::new())); 49 | } 50 | }; 51 | ($cond:expr, $fmt:expr, $($arg:tt)+) => { 52 | if !($cond) { 53 | return Err(CraqError::AssertionError(format!($fmt, $($arg)+).to_string(), failure::Backtrace::new())); 54 | } 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /artillery-ddata/src/craq/erwlock.rs: -------------------------------------------------------------------------------- 1 | use core::sync::atomic::spin_loop_hint; 2 | use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; 3 | 4 | /// 5 | /// 6 | /// RwLock that blocks until yielding 7 | pub struct ERwLock(RwLock); 8 | 9 | impl ERwLock { 10 | pub fn new(t: T) -> ERwLock { 11 | ERwLock(RwLock::new(t)) 12 | } 13 | } 14 | 15 | impl ERwLock { 16 | #[inline] 17 | pub fn read(&self) -> RwLockReadGuard { 18 | loop { 19 | match self.0.try_read() { 20 | Ok(guard) => break guard, 21 | _ => spin_loop_hint(), 22 | } 23 | } 24 | } 25 | 26 | #[inline] 27 | pub fn write(&self) -> RwLockWriteGuard { 28 | loop { 29 | match self.0.try_write() { 30 | Ok(guard) => break guard, 31 | _ => spin_loop_hint(), 32 | } 33 | } 34 | } 35 | 36 | #[allow(dead_code)] 37 | #[inline] 38 | pub fn inner(&self) -> &RwLock { 39 | &self.0 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /artillery-ddata/src/craq/mod.rs: -------------------------------------------------------------------------------- 1 | /// Error API for CRAQ distributed store 2 | #[macro_use] 3 | pub mod errors; 4 | 5 | mod chain; 6 | mod chain_node; 7 | mod erwlock; 8 | 9 | #[allow(clippy::all)] 10 | #[allow(deprecated)] 11 | #[allow(unknown_lints)] 12 | mod proto; 13 | mod server; 14 | 15 | pub mod client; 16 | pub mod craq_config; 17 | pub mod node; 18 | 19 | /// Prelude for CRAQ distributed store 20 | pub mod prelude { 21 | pub use super::chain::*; 22 | pub use super::chain_node::*; 23 | pub use super::client::*; 24 | pub use super::craq_config::*; 25 | pub use super::errors::*; 26 | pub use super::node::*; 27 | pub use super::proto::*; 28 | } 29 | -------------------------------------------------------------------------------- /artillery-ddata/src/craq/node.rs: -------------------------------------------------------------------------------- 1 | use super::errors::*; 2 | 3 | use super::chain::CraqChain; 4 | use super::{craq_config::CraqConfig, erwlock::ERwLock, proto::*, server::CraqProtoServer}; 5 | 6 | use std::{ 7 | net::{IpAddr, Ipv4Addr, SocketAddr, ToSocketAddrs}, 8 | sync::Arc, 9 | }; 10 | use thrift::protocol::{ 11 | TBinaryInputProtocol, TBinaryInputProtocolFactory, TBinaryOutputProtocol, 12 | TBinaryOutputProtocolFactory, 13 | }; 14 | use thrift::transport::{ 15 | ReadHalf, TFramedReadTransport, TFramedReadTransportFactory, TFramedWriteTransport, 16 | TFramedWriteTransportFactory, TIoChannel, TTcpChannel as BiTcp, WriteHalf, 17 | }; 18 | 19 | use thrift::server::TServer; 20 | 21 | use crossbeam_channel::{unbounded, Receiver, Sender}; 22 | 23 | /// 24 | /// CR mode that will be used. 25 | #[derive(Debug, Clone, PartialEq)] 26 | pub enum CRMode { 27 | /// Standard CR mode. 28 | Cr, 29 | /// CRAQ mode. 30 | Craq, 31 | } 32 | 33 | impl Default for CRMode { 34 | fn default() -> Self { 35 | CRMode::Craq 36 | } 37 | } 38 | 39 | type CraqClientInputProtocol = TBinaryInputProtocol>>; 40 | type CraqClientOutputProtocol = TBinaryOutputProtocol>>; 41 | pub(crate) type CraqClient = 42 | CraqServiceSyncClient; 43 | 44 | /// 45 | /// Representation of a physical CRAQ node. 46 | #[derive(Default)] 47 | pub struct CraqNode { 48 | /// Run mode which is either CR or CRAQ mode. 49 | pub cr_mode: CRMode, 50 | /// Whole chain. 51 | pub chain: Arc, 52 | /// Tail connection pool receiver. 53 | pub tail_pool_rx: Option>>, 54 | /// Successor connection pool receiver. 55 | pub successor_pool_rx: Option>>, 56 | /// Tail connection pool sender. 57 | pub tail_pool_tx: Option>>, 58 | /// Successor connection pool sender. 59 | pub successor_pool_tx: Option>>, 60 | /// Stored node configuration to be reused across iterations 61 | pub config: CraqConfig, 62 | } 63 | 64 | impl CraqNode { 65 | /// 66 | /// Initialize a CRAQ node with given chain 67 | fn new_node(cr_mode: CRMode, chain: CraqChain, config: CraqConfig) -> Result { 68 | Ok(Self { 69 | cr_mode, 70 | chain: Arc::new(chain), 71 | config, 72 | ..Default::default() 73 | }) 74 | } 75 | 76 | /// 77 | /// Initial connection to the underlying protocol server. 78 | fn connect_to_first(&self, server_addr: A) -> CraqClient 79 | where 80 | A: ToSocketAddrs, 81 | { 82 | self.connect_to_server(&server_addr).unwrap_or_else(|_e| { 83 | std::thread::sleep(std::time::Duration::from_millis( 84 | self.config.connection_sleep_time, 85 | )); 86 | self.connect_to_first(server_addr) 87 | }) 88 | } 89 | 90 | /// 91 | /// Creates a connection pool to the given underlying protocol server. 92 | fn create_conn_pool( 93 | &self, 94 | server_addr: A, 95 | ) -> Result<(Sender, Receiver)> 96 | where 97 | A: ToSocketAddrs, 98 | { 99 | let (tx, rx) = unbounded(); 100 | let client = self.connect_to_first(&server_addr); 101 | // TODO: tryize 102 | let _ = tx.try_send(client); 103 | 104 | let _ = (0..self.config.connection_pool_size) 105 | .flat_map(|_| -> Result<_> { Ok(tx.try_send(self.connect_to_server(&server_addr)?)) }); 106 | // while let Ok(_) = tx.try_send(self.connect_to_server(&server_addr)?) {} 107 | 108 | Ok((tx, rx)) 109 | } 110 | 111 | /// 112 | /// Connects to the other nodes in the given chain. 113 | fn connect(noderef: Arc>) -> Result<()> { 114 | let mut nodew = noderef.write(); 115 | 116 | debug!("Trying to connect"); 117 | if nodew.chain.is_tail() { 118 | return Ok(()); 119 | } 120 | 121 | debug!("Checking tail connection..."); 122 | if let Some(tail) = nodew.chain.clone().get_tail() { 123 | let tail = tail.clone(); 124 | let (t_tx, t_rx) = nodew.create_conn_pool(tail.get_addr())?; 125 | nodew.tail_pool_rx = Some(Arc::new(t_rx)); 126 | nodew.tail_pool_tx = Some(Arc::new(t_tx)); 127 | info!( 128 | "[CR Node {}] Connected to tail at {}", 129 | nodew.chain.get_index(), 130 | tail.get_addr() 131 | ); 132 | } else { 133 | // NOTE: shouldn't happen 134 | error!("Shouldn't have happened - tail follows"); 135 | unreachable!() 136 | } 137 | 138 | debug!("Checking node before the tail..."); 139 | // Is this the node before the tail? 140 | if nodew.chain.get_index() == nodew.chain.chain_size().saturating_sub(2) { 141 | nodew.successor_pool_tx = nodew.tail_pool_tx.clone(); 142 | nodew.successor_pool_rx = nodew.tail_pool_rx.clone(); 143 | info!("[CR Node {}] Node before the tail", nodew.chain.get_index()); 144 | return Ok(()); 145 | } 146 | 147 | debug!("Checking successor..."); 148 | if let Some(successor) = nodew.chain.get_successor() { 149 | let successor = successor.clone(); 150 | info!( 151 | "[CR Node {}] Connecting to successor at {}", 152 | nodew.chain.get_index(), 153 | successor.get_addr() 154 | ); 155 | let (s_tx, s_rx) = nodew.create_conn_pool(successor.get_addr())?; 156 | nodew.successor_pool_rx = Some(Arc::new(s_rx)); 157 | nodew.successor_pool_tx = Some(Arc::new(s_tx)); 158 | info!( 159 | "[CR Node {}] Connected to successor at {}", 160 | nodew.chain.get_index(), 161 | successor.get_addr() 162 | ); 163 | } else { 164 | // NOTE: shouldn't happen 165 | error!("Shouldn't have happened - successor interval"); 166 | unreachable!() 167 | } 168 | 169 | debug!("All aligned..."); 170 | 171 | Ok(()) 172 | } 173 | 174 | /// 175 | /// Entrypoint / Start procedure of this node 176 | pub fn start(cr_mode: CRMode, chain: CraqChain, config: CraqConfig) -> Result<()> { 177 | let port = chain 178 | .get_node() 179 | .map_or(config.fallback_replication_port, |n| n.get_addr().port()); 180 | 181 | let node = Arc::new(ERwLock::new(CraqNode::new_node(cr_mode, chain, config)?)); 182 | 183 | let connector_node = node.clone(); 184 | let handle = std::thread::spawn(move || { 185 | Self::connect(connector_node).expect("Successor connections has failed"); 186 | }); 187 | 188 | let _ = handle.join(); 189 | 190 | info!("Starting protocol server at port: {}", port); 191 | Self::run_protocol_server(node, port) 192 | } 193 | 194 | /// 195 | /// Connect to given server address using CRAQ client. 196 | fn connect_to_server(&self, addr: A) -> Result 197 | where 198 | A: ToSocketAddrs, 199 | { 200 | let host: SocketAddr = addr 201 | .to_socket_addrs()? 202 | .next() 203 | .ok_or_else(|| CraqError::SocketAddrError("No node address given or parsed.".into()))?; 204 | 205 | debug!("Issuing connection to: {}", host); 206 | 207 | let mut c = BiTcp::new(); 208 | c.open(&host.to_string())?; 209 | let (i_chan, o_chan) = c.split()?; 210 | let (i_tran, o_tran) = ( 211 | TFramedReadTransport::new(i_chan), 212 | TFramedWriteTransport::new(o_chan), 213 | ); 214 | let (i_prot, o_prot) = ( 215 | TBinaryInputProtocol::new(i_tran, true), 216 | TBinaryOutputProtocol::new(o_tran, true), 217 | ); 218 | 219 | debug!("Creating client: {}", host); 220 | Ok(CraqClient::new(i_prot, o_prot)) 221 | } 222 | 223 | /// 224 | /// Start local protocol server 225 | fn run_protocol_server(node: Arc>, port: u16) -> Result<()> { 226 | let node = node.read(); 227 | debug!("Protocol medium getting set up"); 228 | 229 | let server_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port); 230 | 231 | let (i_tran_fact, i_prot_fact) = ( 232 | TFramedReadTransportFactory::new(), 233 | TBinaryInputProtocolFactory::new(), 234 | ); 235 | let (o_tran_fact, o_prot_fact) = ( 236 | TFramedWriteTransportFactory::new(), 237 | TBinaryOutputProtocolFactory::new(), 238 | ); 239 | 240 | let processor = CraqServiceSyncProcessor::new(CraqProtoServer::new( 241 | node.tail_pool_rx.clone(), 242 | node.tail_pool_tx.clone(), 243 | node.successor_pool_rx.clone(), 244 | node.successor_pool_tx.clone(), 245 | node.chain.clone(), 246 | node.cr_mode.clone(), 247 | )); 248 | 249 | debug!("Server started"); 250 | let mut server = TServer::new( 251 | i_tran_fact, 252 | i_prot_fact, 253 | o_tran_fact, 254 | o_prot_fact, 255 | processor, 256 | node.config.protocol_worker_size, 257 | ); 258 | 259 | debug!("Started listening"); 260 | Ok(server.listen(&server_addr.to_string())?) 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /artillery-ddata/src/craq/protocol/proto.thrift: -------------------------------------------------------------------------------- 1 | /* 2 | * CRAQ replication protocol definition 3 | */ 4 | namespace cpp artillery_craq 5 | namespace java artillery.craq.thrift 6 | 7 | /** Version numbers. */ 8 | typedef i64 Version 9 | 10 | /** Consistency models. */ 11 | enum CraqConsistencyModel { STRONG, EVENTUAL, EVENTUAL_MAX_BOUNDED, DEBUG } 12 | 13 | /** Object envelope. */ 14 | struct CraqObject { 15 | 1: optional binary value; 16 | 2: optional bool dirty; 17 | } 18 | 19 | /** Artillery CRAQ Invalid State Error */ 20 | exception InvalidState { 21 | 1: string reason 22 | } 23 | 24 | /** Artillery CRAQ service. */ 25 | service CraqService { 26 | // ------------------------------------------------------------------------- 27 | // Client-facing methods 28 | // ------------------------------------------------------------------------- 29 | /** Reads a value with the desired consistency model. */ 30 | CraqObject read(1:CraqConsistencyModel model, 2:Version versionBound), 31 | 32 | /** Writes a new value. */ 33 | Version write(1:CraqObject obj), 34 | 35 | /** Performs a test-and-set operation. **/ 36 | Version testAndSet(1:CraqObject obj, 2:Version expectedVersion), 37 | 38 | // ------------------------------------------------------------------------- 39 | // Internal methods 40 | // ------------------------------------------------------------------------- 41 | /** Writes a new value with the given version. */ 42 | void writeVersioned(1:CraqObject obj, 2:Version version), 43 | 44 | /** Returns the latest committed version. */ 45 | Version versionQuery() 46 | } 47 | -------------------------------------------------------------------------------- /artillery-ddata/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate log; 3 | 4 | #[macro_use] 5 | pub mod craq; 6 | -------------------------------------------------------------------------------- /artillery-hierman/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /artillery-hierman/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "artillery-hierman" 3 | version = "0.1.0" 4 | authors = ["Mahmut Bulut "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /artillery-hierman/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | #[test] 4 | fn it_works() { 5 | assert_eq!(2 + 2, 4); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /checks.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -em 4 | 5 | cargo fmt 6 | cargo fix --allow-dirty --allow-staged 7 | cargo clippy 8 | cargo fmt 9 | -------------------------------------------------------------------------------- /ddata-tests/shutdown.sh: -------------------------------------------------------------------------------- 1 | for i in `seq 0 $CHAIN_LEN` 2 | do 3 | a=`printenv PID$i` 4 | kill $a 5 | echo "kill" $a 6 | export PID$i= 7 | done 8 | 9 | export CHAIN_LEN= 10 | -------------------------------------------------------------------------------- /ddata-tests/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eo pipefail 4 | 5 | len=$(($#-1)) 6 | export CHAIN_LEN=$len 7 | for i in `seq 0 $(($#-1))` 8 | do 9 | echo $i 10 | target/debug/examples/craq_node server 0 $i $* & 11 | export PID$i=$! 12 | echo ${PID}$i 13 | done 14 | -------------------------------------------------------------------------------- /deployment-tests/artillery-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: artillery-ap-deployment 5 | labels: 6 | app: artillery-ap 7 | spec: 8 | replicas: 3 9 | selector: 10 | matchLabels: 11 | app: artillery-ap 12 | template: 13 | metadata: 14 | labels: 15 | app: artillery-ap 16 | spec: 17 | containers: 18 | - name: artillery-ap 19 | image: artillery-ap:0.1.0 20 | ports: 21 | - containerPort: 27845 22 | -------------------------------------------------------------------------------- /deployment-tests/cluster-mdns-ap-test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | 3 | help() 4 | { 5 | echo "" 6 | echo "Usage: $0 -s CLUSTER_SIZE" 7 | echo -e "\t-size Launches a zeroconf AP Artillery cluster" 8 | exit 1 9 | } 10 | 11 | while getopts "s:" opt 12 | do 13 | case "$opt" in 14 | s ) CLUSTER_SIZE="$OPTARG" ;; 15 | ? ) help ;; 16 | esac 17 | done 18 | 19 | if [ -z "$CLUSTER_SIZE" ] 20 | then 21 | echo "Parameter expected"; 22 | help 23 | fi 24 | 25 | mkdir -p deployment-tests/node_state 26 | cd deployment-tests/node_state 27 | 28 | for i in {1..$CLUSTER_SIZE} 29 | do 30 | echo "Starting Node: $i" 31 | NODE_DATA_DIR="node$i" 32 | mkdir -p $NODE_DATA_DIR 33 | RUST_BACKTRACE=full RUST_LOG=debug cargo run --example cball_mdns_sd_infection $NODE_DATA_DIR & 34 | sleep 1 35 | done 36 | -------------------------------------------------------------------------------- /deployment-tests/kind-with-registry.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -o errexit 3 | 4 | # desired cluster name; default is "kind" 5 | KIND_CLUSTER_NAME="${KIND_CLUSTER_NAME:-kind}" 6 | 7 | # create registry container unless it already exists 8 | reg_name='kind-registry' 9 | reg_port='5000' 10 | running="$(docker inspect -f '{{.State.Running}}' "${reg_name}" 2>/dev/null || true)" 11 | if [ "${running}" != 'true' ]; then 12 | docker run \ 13 | -d --restart=always -p "${reg_port}:5000" --name "${reg_name}" \ 14 | registry:2 15 | fi 16 | reg_ip="$(docker inspect -f '{{.NetworkSettings.IPAddress}}' "${reg_name}")" 17 | 18 | # create a cluster with the local registry enabled in containerd 19 | cat <> docs/CNAME 13 | 14 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bastion-rs/artillery/75bb64bd5c9e49b9f328b5b7d7e4236441559476/docs/.nojekyll -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Artillery 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | artillery.bastion-rs.com -------------------------------------------------------------------------------- /docs/assets/js/10.e2c4e6dd.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[10],{194:function(n,w,o){}}]); -------------------------------------------------------------------------------- /docs/assets/js/4.b92d7a52.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[4],{196:function(t,e,n){},202:function(t,e,n){var i=n(1),s=n(203);i({target:"Array",stat:!0,forced:!n(135)((function(t){Array.from(t)}))},{from:s})},203:function(t,e,n){"use strict";var i=n(56),s=n(17),a=n(134),o=n(132),c=n(12),r=n(57),d=n(133);t.exports=function(t){var e,n,l,h,u,f,v=s(t),p="function"==typeof this?this:Array,C=arguments.length,g=C>1?arguments[1]:void 0,k=void 0!==g,_=d(v),m=0;if(k&&(g=i(g,C>2?arguments[2]:void 0,2)),null==_||p==Array&&o(_))for(n=new p(e=c(v.length));e>m;m++)f=k?g(v[m],m):v[m],r(n,m,f);else for(u=(h=_.call(v)).next,n=new p;!(l=u.call(h)).done;m++)f=k?a(h,g,[l.value,m],!0):l.value,r(n,m,f);return n.length=m,n}},204:function(t,e,n){"use strict";var i=n(196);n.n(i).a},217:function(t,e,n){"use strict";n.r(e);n(22),n(202),n(136),n(20),n(58),n(38),n(28);var i=n(23),s={data:function(){return{blocks:[]}},computed:{isEnchanceMode:function(){return!!this.$page.frontmatter.enhance},isBlockLayout:function(){return this.isEnchanceMode||!!this.blocks.length},pageClasses:function(){return{page__container:!0,"page--block-layout":this.isBlockLayout}},lastUpdated:function(){if(this.$page.lastUpdated)return new Date(this.$page.lastUpdated).toLocaleString(this.$lang)},lastUpdatedText:function(){return"string"==typeof this.$site.themeConfig.lastUpdated?this.$site.themeConfig.lastUpdated:"Last Updated"},editLink:function(){if(!1!==this.$page.frontmatter.editLink){var t=this.$site.themeConfig,e=t.repo,n=t.editLinks,s=t.docsDir,a=void 0===s?"":s,o=t.docsBranch,c=void 0===o?"master":o,r=t.docsRepo,d=void 0===r?e:r,l=this.$page.path;if("/"===l.substr(-1)?l+="README.md":l+=".md",d&&n)return(Object(i.a)(d)?d:"https://github.com/".concat(d)).replace(/\/$/,"")+"/edit/".concat(c)+(a?"/"+a.replace(/\/$/,""):"")+l}},editLinkText:function(){return this.$site.themeConfig.editLinkText||"Edit this page"}},watch:{$route:function(t,e){t.path!==e.path&&(this.blocks.length=0,this.isEnchanceMode&&this.$nextTick(this.resolveLayout))}},methods:{resolveLayout:function(){var t=this.$el.children[0],e="";Array.from(t.children).forEach((function(t){!function(t){var e=t.tagName.toLowerCase();return"h1"===e||"h2"===e}(t)?e+=t.outerHTML:(e&&(e+='\n \n
\n
\n \n \n '),e+='\n
\n
\n '.concat(t.outerHTML,'\n
\n
\n
\n '))})),e+='\n
\n
\n
\n
\n
\n ',t.innerHTML=e},addBlock:function(t){this.blocks.push(t)}},mounted:function(){this.isEnchanceMode&&this.$nextTick(this.resolveLayout)},created:function(){this.$on("addBlock",this.addBlock)}},a=(n(204),n(4)),o=Object(a.a)(s,(function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{class:t.pageClasses},[n("Content",{attrs:{custom:""}}),t._v(" "),n("div",{staticClass:"content__footer-container"},[n("div",{staticClass:"content__footer"},[t.editLink?n("div",{staticClass:"edit-link"},[n("a",{attrs:{href:t.editLink,target:"_blank",rel:"noopener noreferrer"}},[t._v(t._s(t.editLinkText))]),t._v(" "),n("svg",{attrs:{viewBox:"0 0 33 32",version:"1.1",xmlns:"http://www.w3.org/2000/svg",height:"16",width:"16"}},[n("g",{attrs:{id:"Page-1",stroke:"none","stroke-width":"1",fill:"none","fill-rule":"evenodd"}},[n("g",{attrs:{id:"github",fill:"#000"}},[n("path",{attrs:{d:"M16.3,0 C7.3,0 -3.55271368e-15,7.3 -3.55271368e-15,16.3 C-3.55271368e-15,23.5 4.7,29.6 11.1,31.8 C11.9,31.9 12.2,31.4 12.2,31 L12.2,28.2 C7.7,29.2 6.7,26 6.7,26 C6,24.2 5,23.7 5,23.7 C3.5,22.7 5.1,22.7 5.1,22.7 C6.7,22.8 7.6,24.4 7.6,24.4 C9.1,26.9 11.4,26.2 12.3,25.8 C12.4,24.7 12.9,24 13.3,23.6 C9.7,23.2 5.9,21.8 5.9,15.5 C5.9,13.7 6.5,12.3 7.6,11.1 C7.4,10.7 6.9,9 7.8,6.8 C7.8,6.8 9.2,6.4 12.3,8.5 C13.6,8.1 15,8 16.4,8 C17.8,8 19.2,8.2 20.5,8.5 C23.6,6.4 25,6.8 25,6.8 C25.9,9 25.3,10.7 25.2,11.1 C26.2,12.2 26.9,13.7 26.9,15.5 C26.9,21.8 23.1,23.1 19.5,23.5 C20.1,24 20.6,25 20.6,26.5 L20.6,31 C20.6,31.4 20.9,31.9 21.7,31.8 C28.2,29.6 32.8,23.5 32.8,16.3 C32.6,7.3 25.3,0 16.3,0 L16.3,0 Z",id:"Shape"}})])])])]):t._e(),t._v(" "),t.lastUpdated?n("time",{staticClass:"last-updated"},[n("span",{staticClass:"prefix"},[t._v(t._s(t.lastUpdatedText)+":")]),t._v(" "),n("span",{staticClass:"time"},[t._v(t._s(t.lastUpdated))])]):t._e()])])],1)}),[],!1,null,null,null);e.default=o.exports}}]); -------------------------------------------------------------------------------- /docs/assets/js/5.b60eac8b.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[5],{208:function(t,e,s){},281:function(t,e,s){"use strict";var o=s(208);s.n(o).a},284:function(t,e,s){"use strict";s.r(e);var o=["There's nothing here.","How did we get here?","That's a Four-Oh-Four.","Looks like we've got some broken links.","Please try your request again or contact support.","The page you are looking for doesn't exist or misterionsly dissapear."],n={methods:{getMsg:function(){return o[Math.floor(Math.random()*o.length)]}}},i=(s(281),s(4)),r=Object(i.a)(n,(function(){var t=this.$createElement,e=this._self._c||t;return e("div",{staticClass:"container notfound-page"},[e("div",{staticClass:"content"},[e("h1",[this._v("Not Found")]),this._v(" "),e("p",[this._v(this._s(this.getMsg()))]),this._v(" "),e("router-link",{staticClass:"backto",attrs:{to:"/"}},[this._v("Take me home.")])],1)])}),[],!1,null,"6d9b2618",null);e.default=r.exports}}]); -------------------------------------------------------------------------------- /docs/assets/js/6.d6285a75.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[6],{285:function(t,r,e){"use strict";e.r(r);var s=e(4),i=Object(s.a)({},(function(){var t=this.$createElement,r=this._self._c||t;return r("ContentSlotsDistributor",{attrs:{"slot-key":this.$parent.slotKey}},[r("div",{attrs:{align:"center"}},[r("img",{attrs:{src:"https://raw.githubusercontent.com/bastion-rs/artillery/master/img/artillery_cropped.png",width:"512",height:"512"}}),r("br")]),this._v(" "),r("hr"),this._v(" "),r("h1",{attrs:{align:"center"}},[this._v("Artillery: Cluster management & Distributed data protocol")])])}),[],!1,null,null,null);r.default=i.exports}}]); -------------------------------------------------------------------------------- /docs/assets/js/7.9042a811.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[7],{286:function(t,s,a){"use strict";a.r(s);var n=a(4),e=Object(n.a)({},(function(){var t=this,s=t.$createElement,a=t._self._c||s;return a("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[a("Block",[a("h1",{attrs:{id:"primitives"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#primitives"}},[t._v("#")]),t._v(" Primitives")])]),t._v(" "),a("Block",[a("p",[t._v("Artillery Core consists of various primitives. We will start with Service Discovery primitives and pave out way to Cluster primitives.")])]),t._v(" "),a("Block",[a("h2",{attrs:{id:"service-discovery-primitives"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#service-discovery-primitives"}},[t._v("#")]),t._v(" Service Discovery Primitives")]),t._v(" "),a("p",[t._v("For distributed operation we need to have a service discovery to find out who is operating/serving which services and service capabilities.")]),t._v(" "),a("p",[t._v("Our design consists of various service discovery techniques.")])]),t._v(" "),a("Block",[a("h2",{attrs:{id:"udp-anycast"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#udp-anycast"}},[t._v("#")]),t._v(" UDP Anycast")]),t._v(" "),a("p",[t._v("We have UDP anycast which allows the devices in the same network to nag each other continuously with a specific set of service requests to form a cluster initiation.")]),t._v(" "),a("p",[a("strong",[t._v("NOTE:")]),t._v(" Convergance of the UDP anycast might take longer time than the other zeroconf approaches.")]),t._v(" "),a("Example",[a("div",{staticClass:"language-rust extra-class"},[a("pre",{pre:!0,attrs:{class:"language-rust"}},[a("code",[a("span",{pre:!0,attrs:{class:"token attribute attr-name"}},[t._v("#[derive(Serialize, Deserialize, Debug, Clone, Ord, PartialOrd, PartialEq)]")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("struct")]),t._v(" ExampleSDReply "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n ip"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" String"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n port"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" u16"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("let")]),t._v(" epidemic_sd_config "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" ExampleSDReply "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n ip"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v('"127.0.0.1"')]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("into")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n port"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("1337")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// Cluster Formation Port of this instance")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("let")]),t._v(" reply "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" ServiceDiscoveryReply "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n serialized_data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" serde_json"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("::")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("to_string")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("&")]),t._v("epidemic_sd_config"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("unwrap")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// Initialize receiver channels")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("let")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("tx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" discoveries"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("channel")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// Register seeker endpoint")]),t._v("\nsd"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("register_seeker")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("tx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("unwrap")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// Sometimes you seek for nodes,")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// sometimes you need to be a listener to respond them.")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("if")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("let")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("Some")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("_"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" seeker "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n sd"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("seek_peers")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("unwrap")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("else")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n sd"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("set_listen_for_peers")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("true")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("unwrap")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("for")]),t._v(" discovery "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("in")]),t._v(" discoveries"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("iter")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("let")]),t._v(" discovery"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" ExampleSDReply "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("\n serde_json"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("::")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("from_str")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("&")]),t._v("discovery"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("serialized_data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("unwrap")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("if")]),t._v(" discovery"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("port "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("!=")]),t._v(" epidemic_sd_config"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("port "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("debug!")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v('"Seed node address came"')]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("let")]),t._v(" seed_node "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("format!")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v('"{}:{}"')]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" discovery"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("ip"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" discovery"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("port"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// We have received a discovery request.")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])])])])],1)],1)}),[],!1,null,null,null);s.default=e.exports}}]); -------------------------------------------------------------------------------- /docs/assets/js/8.f2fbc9e9.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[8],{288:function(e,a,t){"use strict";t.r(a);var s=t(4),l=Object(s.a)({},(function(){var e=this,a=e.$createElement,t=e._self._c||a;return t("ContentSlotsDistributor",{attrs:{"slot-key":e.$parent.slotKey}},[t("Block",[t("h1",{attrs:{id:"local-examples"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#local-examples"}},[e._v("#")]),e._v(" Local Examples")])]),e._v(" "),t("Block",[t("h2",{attrs:{id:"cluster-examples"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#cluster-examples"}},[e._v("#")]),e._v(" Cluster Examples")]),e._v(" "),t("p",[e._v("Below you can find examples to learn Artillery.\nYou can also take a look at the "),t("a",{attrs:{href:"https://github.com/bastion-rs/artillery/tree/master/artillery-core/examples",target:"_blank",rel:"noopener noreferrer"}},[e._v("Core Examples"),t("OutboundLink")],1),e._v(".")])]),e._v(" "),t("Block",[t("h2",{attrs:{id:"launching-a-local-ap-cluster"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#launching-a-local-ap-cluster"}},[e._v("#")]),e._v(" Launching a local AP Cluster")]),e._v(" "),t("p",[e._v("To spawn a local AP cluster at any size you can use the command below in the root directory of the project.")]),e._v(" "),t("Example",[t("div",{staticClass:"language-bash extra-class"},[t("pre",{pre:!0,attrs:{class:"language-bash"}},[t("code",[e._v("$ deployment-tests/cluster-mdns-ap-test.sh -s "),t("span",{pre:!0,attrs:{class:"token number"}},[e._v("50")]),e._v("\n")])])]),t("div",{staticClass:"language-bash extra-class"},[t("pre",{pre:!0,attrs:{class:"language-bash"}},[t("code",[e._v("$ "),t("span",{pre:!0,attrs:{class:"token function"}},[e._v("killall")]),e._v(" cball_mdns_sd_infection\n")])])])]),e._v(" "),t("p",[e._v("Argument "),t("code",[e._v("-s")]),e._v(" defines the amount of nodes in the cluster.\nTo shut down the cluster either use "),t("code",[e._v("killall")]),e._v(" or kill processes\none by one to see that cluster is self-healing.")])],1)],1)}),[],!1,null,null,null);a.default=l.exports}}]); -------------------------------------------------------------------------------- /docs/assets/js/9.7cb4df56.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[9],{287:function(e,t,a){"use strict";a.r(t);var r=a(4),i=Object(r.a)({},(function(){var e=this,t=e.$createElement,a=e._self._c||t;return a("ContentSlotsDistributor",{attrs:{"slot-key":e.$parent.slotKey}},[a("Block",[a("h1",{attrs:{id:"getting-started"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#getting-started"}},[e._v("#")]),e._v(" Getting Started")])]),e._v(" "),a("Block",[a("h2",{attrs:{id:"basics"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#basics"}},[e._v("#")]),e._v(" Basics")]),e._v(" "),a("p",[e._v("To use Artillery, you need to evaluate your requirements for distributed operation very carefully.\nEvery layer in artillery is usable modularly. Artillery uses \"Take it or leave it\" approach.\nIf you don't need it you don't include.")]),e._v(" "),a("p",[e._v("Artillery consists of various layers. Layers can have various consistency degree and capability model.\nArtillery layers are build on top each other. Most basic layer is "),a("code",[e._v("Core")]),e._v(".\nCore layer contains various prepared cluster configurations.\nCurrently it is supporting:")]),e._v(" "),a("ul",[a("li",[a("strong",[e._v("AP(Availability, Partition Tolerance")]),e._v(" Cluster mode")]),e._v(" "),a("li",[a("strong",[e._v("CP(Consistency, Partition Tolerance)")]),e._v(" Cluster mode (soon)")])]),e._v(" "),a("p",[e._v("In addition to cluster modes, it contains primitives to build your own cluster structures for your own designated environment.")]),e._v(" "),a("Example",[a("ul",[a("li",[a("code",[e._v("artillery-core")]),e._v(" "),a("ul",[a("li",[a("code",[e._v("cluster")]),e._v(": Prepared self-healing cluster structures")]),e._v(" "),a("li",[a("code",[e._v("epidemic")]),e._v(": Infection style clustering")]),e._v(" "),a("li",[a("code",[e._v("service_discovery")]),e._v(": Service discovery types\n"),a("ul",[a("li",[a("code",[e._v("mdns")]),e._v(": MDNS based service discovery")]),e._v(" "),a("li",[a("code",[e._v("udp_anycast")]),e._v(": UDP Anycast based service discovery\n(aka "),a("a",{attrs:{href:"https://bastion.rs",target:"_blank",rel:"noopener noreferrer"}},[e._v("Bastion"),a("OutboundLink")],1),e._v("'s core carrier protocol)")])])])])])])])],1),e._v(" "),a("Block",[a("h2",{attrs:{id:"distributed-data"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#distributed-data"}},[e._v("#")]),e._v(" Distributed Data")]),e._v(" "),a("p",[e._v("You might want to pass by the distributed configuration part and directly looking forward to have a distributed\ndata primitives. Like replicating your local map to some other instance's local map etc.")]),e._v(" "),a("p",[e._v("This is where "),a("code",[e._v("Ddata")]),e._v(" package kicks in. "),a("code",[e._v("Ddata")]),e._v(" supplies the most basic distributed data dissemination at the highest abstraction level.")]),e._v(" "),a("Example",[a("ul",[a("li",[a("code",[e._v("artillery-ddata")]),e._v(": Used for distributed data replication")])])])],1),e._v(" "),a("Block",[a("h2",{attrs:{id:"hierarchy-management"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#hierarchy-management"}},[e._v("#")]),e._v(" Hierarchy Management")]),e._v(" "),a("p",[e._v("This layer is specifically build for Bastion and it's distributed communication.\nIt contains a Hierarchy Management protocol. This protocol manages remote processes, links as well as their state.")]),e._v(" "),a("Example",[a("ul",[a("li",[a("code",[e._v("artillery-hierman")]),e._v(": Supervision hierarchy management layer")])])])],1)],1)}),[],!1,null,null,null);t.default=i.exports}}]); -------------------------------------------------------------------------------- /docs/examples/cluster-examples.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Local Examples | Artillery 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /docs/getting-started/getting-started.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Getting Started | Artillery 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Artillery 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /img/artillery_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bastion-rs/artillery/75bb64bd5c9e49b9f328b5b7d7e4236441559476/img/artillery_512.png -------------------------------------------------------------------------------- /img/artillery_cropped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bastion-rs/artillery/75bb64bd5c9e49b9f328b5b7d7e4236441559476/img/artillery_cropped.png -------------------------------------------------------------------------------- /site/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: 'Artillery', 3 | dest: '../docs', 4 | description: 'Cluster management & Distributed data protocol', 5 | theme: 'api', 6 | themeConfig: { 7 | editLinks: true, 8 | sidebarGroupOrder: [ 9 | 'getting-started', 10 | 'building-blocks', 11 | 'examples', 12 | ], 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /site/README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | ----------------- 6 | 7 |

Artillery: Cluster management & Distributed data protocol

8 | 9 | -------------------------------------------------------------------------------- /site/building-blocks/primitives.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Primitives' 3 | --- 4 | 5 | 6 | # Primitives 7 | 8 | 9 | 10 | 11 | 12 | Artillery Core consists of various primitives. We will start with Service Discovery primitives and pave out way to Cluster primitives. 13 | 14 | 15 | 16 | 17 | 18 | 19 | ## Service Discovery Primitives 20 | 21 | For distributed operation we need to have a service discovery to find out who is operating/serving which services and service capabilities. 22 | 23 | Our design consists of various service discovery techniques. 24 | 25 | 26 | 27 | 28 | 29 | 30 | ## UDP Anycast 31 | 32 | We have UDP anycast which allows the devices in the same network to nag each other continuously with a specific set of service requests to form a cluster initiation. 33 | 34 | **NOTE:** Convergance of the UDP anycast might take longer time than the other zeroconf approaches. 35 | 36 | 37 | 38 | ```rust 39 | #[derive(Serialize, Deserialize, Debug, Clone, Ord, PartialOrd, PartialEq)] 40 | struct ExampleSDReply { 41 | ip: String, 42 | port: u16, 43 | } 44 | 45 | let epidemic_sd_config = ExampleSDReply { 46 | ip: "127.0.0.1".into(), 47 | port: 1337, // Cluster Formation Port of this instance 48 | }; 49 | 50 | let reply = ServiceDiscoveryReply { 51 | serialized_data: serde_json::to_string(&epidemic_sd_config).unwrap(), 52 | }; 53 | 54 | // Initialize receiver channels 55 | let (tx, discoveries) = channel(); 56 | 57 | // Register seeker endpoint 58 | sd.register_seeker(tx).unwrap(); 59 | 60 | // Sometimes you seek for nodes, 61 | // sometimes you need to be a listener to respond them. 62 | if let Some(_) = seeker { 63 | sd.seek_peers().unwrap(); 64 | } else { 65 | sd.set_listen_for_peers(true).unwrap(); 66 | } 67 | 68 | for discovery in discoveries.iter() { 69 | let discovery: ExampleSDReply = 70 | serde_json::from_str(&discovery.serialized_data).unwrap(); 71 | if discovery.port != epidemic_sd_config.port { 72 | debug!("Seed node address came"); 73 | let seed_node = format!("{}:{}", discovery.ip, discovery.port); 74 | // We have received a discovery request. 75 | } 76 | } 77 | ``` 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /site/examples/cluster-examples.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Local Examples' 3 | --- 4 | 5 | 6 | # Local Examples 7 | 8 | 9 | 10 | 11 | ## Cluster Examples 12 | Below you can find examples to learn Artillery. 13 | You can also take a look at the [Core Examples](https://github.com/bastion-rs/artillery/tree/master/artillery-core/examples). 14 | 15 | 16 | 17 | 18 | 19 | ## Launching a local AP Cluster 20 | To spawn a local AP cluster at any size you can use the command below in the root directory of the project. 21 | 22 | 23 | 24 | ```bash 25 | $ deployment-tests/cluster-mdns-ap-test.sh -s 50 26 | ``` 27 | 28 | ```bash 29 | $ killall cball_mdns_sd_infection 30 | ``` 31 | 32 | 33 | 34 | Argument `-s` defines the amount of nodes in the cluster. 35 | To shut down the cluster either use `killall` or kill processes 36 | one by one to see that cluster is self-healing. 37 | 38 | 39 | -------------------------------------------------------------------------------- /site/getting-started/getting-started.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Getting Started' 3 | --- 4 | 5 | 6 | # Getting Started 7 | 8 | 9 | 10 | 11 | 12 | ## Basics 13 | 14 | To use Artillery, you need to evaluate your requirements for distributed operation very carefully. 15 | Every layer in artillery is usable modularly. Artillery uses "Take it or leave it" approach. 16 | If you don't need it you don't include. 17 | 18 | Artillery consists of various layers. Layers can have various consistency degree and capability model. 19 | Artillery layers are build on top each other. Most basic layer is `Core`. 20 | Core layer contains various prepared cluster configurations. 21 | Currently it is supporting: 22 | * **AP(Availability, Partition Tolerance** Cluster mode 23 | * **CP(Consistency, Partition Tolerance)** Cluster mode (soon) 24 | 25 | In addition to cluster modes, it contains primitives to build your own cluster structures for your own designated environment. 26 | 27 | 28 | 29 | * `artillery-core` 30 | * `cluster`: Prepared self-healing cluster structures 31 | * `epidemic`: Infection style clustering 32 | * `service_discovery`: Service discovery types 33 | * `mdns`: MDNS based service discovery 34 | * `udp_anycast`: UDP Anycast based service discovery 35 | (aka [Bastion](https://bastion.rs)'s core carrier protocol) 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | ## Distributed Data 44 | 45 | You might want to pass by the distributed configuration part and directly looking forward to have a distributed 46 | data primitives. Like replicating your local map to some other instance's local map etc. 47 | 48 | This is where `Ddata` package kicks in. `Ddata` supplies the most basic distributed data dissemination at the highest abstraction level. 49 | 50 | 51 | 52 | * `artillery-ddata`: Used for distributed data replication 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | ## Hierarchy Management 62 | 63 | This layer is specifically build for Bastion and it's distributed communication. 64 | It contains a Hierarchy Management protocol. This protocol manages remote processes, links as well as their state. 65 | 66 | 67 | 68 | * `artillery-hierman`: Supervision hierarchy management layer 69 | 70 | 71 | 72 | --------------------------------------------------------------------------------