├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── include └── lasp_pg.hrl ├── priv └── .gitkeep ├── rebar.config ├── rebar.lock ├── rebar3 ├── relx.config ├── src ├── lasp_pg.app.src ├── lasp_pg.erl ├── lasp_pg_app.erl ├── lasp_pg_monitor.erl └── lasp_pg_sup.erl ├── test ├── .gitkeep └── lasp_pg_SUITE.erl └── tools.mk /.gitignore: -------------------------------------------------------------------------------- 1 | *.beam 2 | .eunit 3 | deps/* 4 | ebin/* 5 | dev 6 | src/*.swp 7 | tags 8 | .qc/ 9 | riak_test/ebin/*.beam 10 | .eqc-info 11 | current_counterexample.eqc 12 | dialyzer_warnings 13 | .rebar/ 14 | distdir 15 | package 16 | deps.test/ 17 | rt/ 18 | .combo_dialyzer_plt 19 | build 20 | .vagrant 21 | _build/ 22 | TEST-* 23 | data/ 24 | *.deb 25 | log/ 26 | _checkouts/ 27 | priv/logs/*.csv 28 | priv/plots/*.pdf 29 | fprof.trace 30 | .rebar3/ 31 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: erlang 2 | otp_release: 3 | - 19.2 4 | install: 5 | - ./rebar3 update 6 | - make 7 | before_script: 8 | - epmd -daemon 9 | script: 10 | - make test 11 | - make xref 12 | - make dialyzer 13 | - make lint 14 | notifications: 15 | email: christopher.meiklejohn@gmail.com 16 | slack: lasp-lang:hiPRNnbUa3zdGrrXZfGRAF7D 17 | irc: "irc.freenode.org#lasp-lang" 18 | sudo: false 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Christopher Meiklejohn 2 | All rights reserved. 3 | 4 | 5 | Apache License 6 | Version 2.0, January 2004 7 | http://www.apache.org/licenses/ 8 | 9 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 10 | 11 | 1. Definitions. 12 | 13 | "License" shall mean the terms and conditions for use, reproduction, 14 | and distribution as defined by Sections 1 through 9 of this document. 15 | 16 | "Licensor" shall mean the copyright owner or entity authorized by 17 | the copyright owner that is granting the License. 18 | 19 | "Legal Entity" shall mean the union of the acting entity and all 20 | other entities that control, are controlled by, or are under common 21 | control with that entity. For the purposes of this definition, 22 | "control" means (i) the power, direct or indirect, to cause the 23 | direction or management of such entity, whether by contract or 24 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 25 | outstanding shares, or (iii) beneficial ownership of such entity. 26 | 27 | "You" (or "Your") shall mean an individual or Legal Entity 28 | exercising permissions granted by this License. 29 | 30 | "Source" form shall mean the preferred form for making modifications, 31 | including but not limited to software source code, documentation 32 | source, and configuration files. 33 | 34 | "Object" form shall mean any form resulting from mechanical 35 | transformation or translation of a Source form, including but 36 | not limited to compiled object code, generated documentation, 37 | and conversions to other media types. 38 | 39 | "Work" shall mean the work of authorship, whether in Source or 40 | Object form, made available under the License, as indicated by a 41 | copyright notice that is included in or attached to the work 42 | (an example is provided in the Appendix below). 43 | 44 | "Derivative Works" shall mean any work, whether in Source or Object 45 | form, that is based on (or derived from) the Work and for which the 46 | editorial revisions, annotations, elaborations, or other modifications 47 | represent, as a whole, an original work of authorship. For the purposes 48 | of this License, Derivative Works shall not include works that remain 49 | separable from, or merely link (or bind by name) to the interfaces of, 50 | the Work and Derivative Works thereof. 51 | 52 | "Contribution" shall mean any work of authorship, including 53 | the original version of the Work and any modifications or additions 54 | to that Work or Derivative Works thereof, that is intentionally 55 | submitted to Licensor for inclusion in the Work by the copyright owner 56 | or by an individual or Legal Entity authorized to submit on behalf of 57 | the copyright owner. For the purposes of this definition, "submitted" 58 | means any form of electronic, verbal, or written communication sent 59 | to the Licensor or its representatives, including but not limited to 60 | communication on electronic mailing lists, source code control systems, 61 | and issue tracking systems that are managed by, or on behalf of, the 62 | Licensor for the purpose of discussing and improving the Work, but 63 | excluding communication that is conspicuously marked or otherwise 64 | designated in writing by the copyright owner as "Not a Contribution." 65 | 66 | "Contributor" shall mean Licensor and any individual or Legal Entity 67 | on behalf of whom a Contribution has been received by Licensor and 68 | subsequently incorporated within the Work. 69 | 70 | 2. Grant of Copyright License. Subject to the terms and conditions of 71 | this License, each Contributor hereby grants to You a perpetual, 72 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 73 | copyright license to reproduce, prepare Derivative Works of, 74 | publicly display, publicly perform, sublicense, and distribute the 75 | Work and such Derivative Works in Source or Object form. 76 | 77 | 3. Grant of Patent License. Subject to the terms and conditions of 78 | this License, each Contributor hereby grants to You a perpetual, 79 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 80 | (except as stated in this section) patent license to make, have made, 81 | use, offer to sell, sell, import, and otherwise transfer the Work, 82 | where such license applies only to those patent claims licensable 83 | by such Contributor that are necessarily infringed by their 84 | Contribution(s) alone or by combination of their Contribution(s) 85 | with the Work to which such Contribution(s) was submitted. If You 86 | institute patent litigation against any entity (including a 87 | cross-claim or counterclaim in a lawsuit) alleging that the Work 88 | or a Contribution incorporated within the Work constitutes direct 89 | or contributory patent infringement, then any patent licenses 90 | granted to You under this License for that Work shall terminate 91 | as of the date such litigation is filed. 92 | 93 | 4. Redistribution. You may reproduce and distribute copies of the 94 | Work or Derivative Works thereof in any medium, with or without 95 | modifications, and in Source or Object form, provided that You 96 | meet the following conditions: 97 | 98 | (a) You must give any other recipients of the Work or 99 | Derivative Works a copy of this License; and 100 | 101 | (b) You must cause any modified files to carry prominent notices 102 | stating that You changed the files; and 103 | 104 | (c) You must retain, in the Source form of any Derivative Works 105 | that You distribute, all copyright, patent, trademark, and 106 | attribution notices from the Source form of the Work, 107 | excluding those notices that do not pertain to any part of 108 | the Derivative Works; and 109 | 110 | (d) If the Work includes a "NOTICE" text file as part of its 111 | distribution, then any Derivative Works that You distribute must 112 | include a readable copy of the attribution notices contained 113 | within such NOTICE file, excluding those notices that do not 114 | pertain to any part of the Derivative Works, in at least one 115 | of the following places: within a NOTICE text file distributed 116 | as part of the Derivative Works; within the Source form or 117 | documentation, if provided along with the Derivative Works; or, 118 | within a display generated by the Derivative Works, if and 119 | wherever such third-party notices normally appear. The contents 120 | of the NOTICE file are for informational purposes only and 121 | do not modify the License. You may add Your own attribution 122 | notices within Derivative Works that You distribute, alongside 123 | or as an addendum to the NOTICE text from the Work, provided 124 | that such additional attribution notices cannot be construed 125 | as modifying the License. 126 | 127 | You may add Your own copyright statement to Your modifications and 128 | may provide additional or different license terms and conditions 129 | for use, reproduction, or distribution of Your modifications, or 130 | for any such Derivative Works as a whole, provided Your use, 131 | reproduction, and distribution of the Work otherwise complies with 132 | the conditions stated in this License. 133 | 134 | 5. Submission of Contributions. Unless You explicitly state otherwise, 135 | any Contribution intentionally submitted for inclusion in the Work 136 | by You to the Licensor shall be under the terms and conditions of 137 | this License, without any additional terms or conditions. 138 | Notwithstanding the above, nothing herein shall supersede or modify 139 | the terms of any separate license agreement you may have executed 140 | with Licensor regarding such Contributions. 141 | 142 | 6. Trademarks. This License does not grant permission to use the trade 143 | names, trademarks, service marks, or product names of the Licensor, 144 | except as required for reasonable and customary use in describing the 145 | origin of the Work and reproducing the content of the NOTICE file. 146 | 147 | 7. Disclaimer of Warranty. Unless required by applicable law or 148 | agreed to in writing, Licensor provides the Work (and each 149 | Contributor provides its Contributions) on an "AS IS" BASIS, 150 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 151 | implied, including, without limitation, any warranties or conditions 152 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 153 | PARTICULAR PURPOSE. You are solely responsible for determining the 154 | appropriateness of using or redistributing the Work and assume any 155 | risks associated with Your exercise of permissions under this License. 156 | 157 | 8. Limitation of Liability. In no event and under no legal theory, 158 | whether in tort (including negligence), contract, or otherwise, 159 | unless required by applicable law (such as deliberate and grossly 160 | negligent acts) or agreed to in writing, shall any Contributor be 161 | liable to You for damages, including any direct, indirect, special, 162 | incidental, or consequential damages of any character arising as a 163 | result of this License or out of the use or inability to use the 164 | Work (including but not limited to damages for loss of goodwill, 165 | work stoppage, computer failure or malfunction, or any and all 166 | other commercial damages or losses), even if such Contributor 167 | has been advised of the possibility of such damages. 168 | 169 | 9. Accepting Warranty or Additional Liability. While redistributing 170 | the Work or Derivative Works thereof, You may choose to offer, 171 | and charge a fee for, acceptance of support, warranty, indemnity, 172 | or other liability obligations and/or rights consistent with this 173 | License. However, in accepting such obligations, You may act only 174 | on Your own behalf and on Your sole responsibility, not on behalf 175 | of any other Contributor, and only if You agree to indemnify, 176 | defend, and hold each Contributor harmless for any liability 177 | incurred by, or claims asserted against, such Contributor by reason 178 | of your accepting any such warranty or additional liability. 179 | 180 | END OF TERMS AND CONDITIONS 181 | 182 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PACKAGE ?= lasp_pg 2 | VERSION ?= $(shell git describe --tags) 3 | BASE_DIR = $(shell pwd) 4 | ERLANG_BIN = $(shell dirname $(shell which erl)) 5 | REBAR = $(shell pwd)/rebar3 6 | MAKE = make 7 | 8 | .PHONY: rel deps test plots 9 | 10 | all: compile 11 | 12 | ## 13 | ## Compilation targets 14 | ## 15 | 16 | compile: 17 | $(REBAR) compile 18 | 19 | clean: packageclean 20 | $(REBAR) clean 21 | 22 | packageclean: 23 | rm -fr *.deb 24 | rm -fr *.tar.gz 25 | 26 | ## 27 | ## Test targets 28 | ## 29 | 30 | check: test xref dialyzer lint 31 | 32 | test: ct eunit 33 | 34 | lint: 35 | ${REBAR} as lint lint 36 | 37 | eunit: 38 | ${REBAR} as test eunit 39 | 40 | ct: 41 | ${REBAR} as test ct 42 | 43 | shell: 44 | ${REBAR} shell --apps lasp_pg 45 | 46 | logs: 47 | tail -F priv/lager/*/log/*.log 48 | 49 | ## 50 | ## Release targets 51 | ## 52 | 53 | rel: 54 | ${REBAR} release 55 | 56 | stage: 57 | ${REBAR} release -d 58 | 59 | DIALYZER_APPS = kernel stdlib erts sasl eunit syntax_tools compiler crypto 60 | 61 | include tools.mk 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Lasp PG 2 | ======================================================= 3 | 4 | [![Build Status](https://travis-ci.org/lasp-lang/lasp_pg.svg?branch=master)](https://travis-ci.org/lasp-lang/lasp_pg) 5 | 6 | Eventually consistent process registry, the spirtual successor to [Riak PG](https://github.com/cmeiklejohn/riak_pg). 7 | 8 | ## Usage 9 | 10 | What are groups? 11 | 12 | ```erlang 13 | -type group() :: term(). 14 | ``` 15 | 16 | Join a process to a group (no need to pre-declare) 17 | 18 | ```erlang 19 | lasp_pg:join(group(), pid()) -> ok. 20 | ``` 21 | 22 | What about removing? 23 | 24 | ```erlang 25 | lasp_pg:leave(group(), pid()) -> ok. 26 | ``` 27 | 28 | You can also return the members. 29 | 30 | ```erlang 31 | lasp_pg:members(group()) -> {ok, sets:set(pid())}. 32 | ``` 33 | 34 | ## Clustering 35 | 36 | Use the ```lasp_peer_service``` API to join and leave nodes: 37 | 38 | ```erlang 39 | lasp_peer_service:join({Name, IP, Port}). 40 | ``` 41 | 42 | ## Partisan 43 | 44 | Our [Partisan](http://github.com/lasp-lang/partisan) library drives what 45 | distribution facility you use: you can use the custom HyParView inspired 46 | backend, which scales to 512+ node Erlang clusters, or fall back to 47 | client/server or full-mesh with distributed Erlang. 48 | 49 | This can be configured using the following command, before the Partisan 50 | application is started: 51 | 52 | ```erlang 53 | partisan_config:set(partisan_peer_service_manager, partisan_hyparview_peer_service_manager). 54 | ``` 55 | 56 | Available options are: ```partisan_client_server_peer_service_manager```, ```partisan_default_peer_service_manager``` or ```partisan_hyparview_peer_service_manager```. 57 | 58 | ## Replication 59 | 60 | Lasp PG uses the underlying Lasp KV store, which only has in-memory 61 | persistence currently, but a configurable API so that can be changed to 62 | use either RocksDB or LevelDB. Lasp KV is fully replicated across all 63 | nodes, but partial replication is coming soon. 64 | -------------------------------------------------------------------------------- /include/lasp_pg.hrl: -------------------------------------------------------------------------------- 1 | -define(SET, awset). 2 | -define(APP, lasp_pg). 3 | -define(PEER_IP, {127, 0, 0, 1}). 4 | -define(PEER_PORT, 9000). 5 | -define(PEER_SERVICE_SERVER, partisan_peer_service_server). 6 | -define(FANOUT, 5). 7 | -define(CACHE, partisan_connection_cache). 8 | -define(PARALLELISM, 1). 9 | -define(DEFAULT_CHANNEL, undefined). 10 | -define(CHANNELS, [?DEFAULT_CHANNEL]). 11 | -define(CONNECTION_JITTER, 1000). -------------------------------------------------------------------------------- /priv/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lasp-lang/lasp_pg/2879b983613e7546eaf183dbf71efeed6f11cde8/priv/.gitkeep -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {deps, [{lasp, "~>0.4"}]}. 2 | 3 | {dialyzer_base_plt_apps, [kernel, stdlib, erts, sasl, eunit, syntax_tools, compiler, crypto]}. 4 | {xref_checks, [undefined_function_calls]}. 5 | {erl_opts, [debug_info, 6 | warnings_as_errors, 7 | {platform_define, "^[0-9]+", namespaced_types}]}. 8 | {cover_enabled, true}. 9 | {eunit_opts, [verbose, {report,{eunit_surefire,[{dir,"."}]}}]}. 10 | {edoc_opts, [{preprocess, true}]}. 11 | 12 | {profiles, [ 13 | {lint, [ 14 | {plugins, [{rebar3_lint, {git, "https://github.com/project-fifo/rebar3_lint.git", {tag, "0.1.2"}}}]} 15 | ]}, 16 | {docs, [ 17 | {deps, [{edown, ".*", {git, "https://github.com/uwiger/edown.git", {branch, "master"}}}]} 18 | ]} 19 | ]}. 20 | 21 | {elvis, 22 | [#{dirs => ["src"], 23 | filter => "*.erl", 24 | rules => [ 25 | %% {elvis_style, line_length, 26 | %% #{ignore => [], 27 | %% limit => 80, 28 | %% skip_comments => false}}, 29 | {elvis_style, no_tabs}, 30 | {elvis_style, no_trailing_whitespace}, 31 | {elvis_style, macro_names, #{ignore => []}}, 32 | %% {elvis_style, macro_module_names}, 33 | {elvis_style, operator_spaces, #{rules => [{right, ","}, 34 | {right, "++"}, 35 | {left, "++"}]}}, 36 | %% {elvis_style, nesting_level, #{level => 3}}, 37 | {elvis_style, god_modules, 38 | #{limit => 25, 39 | ignore => []}}, 40 | {elvis_style, no_if_expression}, 41 | %% {elvis_style, invalid_dynamic_call, #{ignore => []}}, 42 | {elvis_style, used_ignored_variable}, 43 | {elvis_style, no_behavior_info}, 44 | { 45 | elvis_style, 46 | module_naming_convention, 47 | #{regex => "^[a-z]([a-z0-9]*_?)*(_SUITE)?$", 48 | ignore => []} 49 | }, 50 | { 51 | elvis_style, 52 | function_naming_convention, 53 | #{regex => "^([a-z][a-z0-9]*_?)*$"} 54 | }, 55 | {elvis_style, state_record_and_type}, 56 | {elvis_style, no_spec_with_records} 57 | %% {elvis_style, dont_repeat_yourself, #{min_complexity => 10}} 58 | %% {elvis_style, no_debug_call, #{ignore => []}} 59 | ] 60 | }, 61 | #{dirs => ["."], 62 | filter => "Makefile", 63 | rules => [] 64 | }, 65 | #{dirs => ["."], 66 | filter => "rebar.config", 67 | rules => [] 68 | } 69 | ] 70 | }. 71 | -------------------------------------------------------------------------------- /rebar.lock: -------------------------------------------------------------------------------- 1 | {"1.1.0", 2 | [{<<"acceptor_pool">>,{pkg,<<"acceptor_pool">>,<<"1.0.0-rc.0">>},2}, 3 | {<<"gen_flow">>,{pkg,<<"gen_flow">>,<<"0.0.4">>},1}, 4 | {<<"goldrush">>,{pkg,<<"goldrush">>,<<"0.1.9">>},2}, 5 | {<<"lager">>,{pkg,<<"lager">>,<<"3.5.2">>},1}, 6 | {<<"lasp">>,{pkg,<<"lasp">>,<<"0.4.0">>},0}, 7 | {<<"lasp_support">>,{pkg,<<"lasp_support">>,<<"0.0.3">>},1}, 8 | {<<"partisan">>,{pkg,<<"partisan">>,<<"1.1.0">>},1}, 9 | {<<"plumtree">>,{pkg,<<"plumtree">>,<<"0.4.0">>},1}, 10 | {<<"rand_compat">>,{pkg,<<"rand_compat">>,<<"0.0.3">>},1}, 11 | {<<"time_compat">>,{pkg,<<"time_compat">>,<<"0.0.1">>},1}, 12 | {<<"types">>,{pkg,<<"types">>,<<"0.1.6">>},1}]}. 13 | [ 14 | {pkg_hash,[ 15 | {<<"acceptor_pool">>, <<"679D741DF87FC13599B1AEF2DF8F78F1F880449A6BEFAB7C44FB6FAE0E92A2DE">>}, 16 | {<<"gen_flow">>, <<"E5F401967B2EE073EA4635AC8E98CC68139115473B036CC7220BB62AE4A78F2C">>}, 17 | {<<"goldrush">>, <<"F06E5D5F1277DA5C413E84D5A2924174182FB108DABB39D5EC548B27424CD106">>}, 18 | {<<"lager">>, <<"614A8C8F67BF99B69EB264EA22121AD25511C055AAEC09B086773D5108C6767F">>}, 19 | {<<"lasp">>, <<"8A0CF2B24C797E12BF7522DA827CD5531173F65B213BCCC4C300328E51173408">>}, 20 | {<<"lasp_support">>, <<"C1B7E1A472037AE82C71D2D16A10B7D644A621B66AE5AFE834CECF170F2E9169">>}, 21 | {<<"partisan">>, <<"E09515027D185295244F0F437208DD95DF8AFB3C94E98BD05448AB7E2BBE178F">>}, 22 | {<<"plumtree">>, <<"14E9E28C81FA03F1BDC544EA27BD1969E66ED106725A5042DAA40C5F7B246DBF">>}, 23 | {<<"rand_compat">>, <<"011646BC1F0B0C432FE101B816F25B9BBB74A085713CEE1DAFD2D62E9415EAD3">>}, 24 | {<<"time_compat">>, <<"23FE0AD1FDF3B5B88821B2D04B4B5E865BF587AE66056D671FE0F53514ED8139">>}, 25 | {<<"types">>, <<"03BB7140016C896D3441A77CB0B7D6ACAA583D6D6E9C4A3E1FD3C25123710290">>}]} 26 | ]. 27 | -------------------------------------------------------------------------------- /rebar3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lasp-lang/lasp_pg/2879b983613e7546eaf183dbf71efeed6f11cde8/rebar3 -------------------------------------------------------------------------------- /relx.config: -------------------------------------------------------------------------------- 1 | {release, {lasp_pg, "0.0.1"}, [lasp_pg]}. 2 | {extended_start_script, true}. 3 | {overlay, [{mkdir, "data"}]}. 4 | -------------------------------------------------------------------------------- /src/lasp_pg.app.src: -------------------------------------------------------------------------------- 1 | {application,lasp_pg, 2 | [{description,"Process groups for Lasp"}, 3 | {vsn,"0.1.0"}, 4 | {registered,[]}, 5 | {applications,[kernel,stdlib,lasp,partisan]}, 6 | {mod,{lasp_pg_app,[]}}, 7 | {modules,[]}, 8 | {maintainers,["Christopher S. Meiklejohn", "Tristan Sloughter"]}, 9 | {links,[{"Github","https://github.com/lasp-lang/lasp_pg"}]}, 10 | {licenses,["Apache 2"]} 11 | ]}. 12 | -------------------------------------------------------------------------------- /src/lasp_pg.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright (c) 2016 Christopher Meiklejohn. All Rights Reserved. 4 | %% 5 | %% This file is provided to you under the Apache License, 6 | %% Version 2.0 (the "License"); you may not use this file 7 | %% except in compliance with the License. You may obtain 8 | %% a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, 13 | %% software distributed under the License is distributed on an 14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | %% KIND, either express or implied. See the License for the 16 | %% specific language governing permissions and limitations 17 | %% under the License. 18 | %% 19 | %% ------------------------------------------------------------------- 20 | 21 | -module(lasp_pg). 22 | 23 | -export([start/0, 24 | stop/0]). 25 | 26 | -export([members/1, 27 | join/2, 28 | join/3, 29 | leave/2]). 30 | 31 | -include("lasp_pg.hrl"). 32 | 33 | %% @doc Start the application. 34 | start() -> 35 | application:ensure_all_started(lasp_pg). 36 | 37 | %% @doc Stop the application. 38 | stop() -> 39 | application:stop(lasp_pg). 40 | 41 | %% @doc Return members of the process group. 42 | members(Group) -> 43 | GroupName = term_to_binary(Group), 44 | lasp:query({GroupName, ?SET}). 45 | 46 | %% @doc Add a member to the process group. 47 | leave(Group, Pid) -> 48 | GroupName = term_to_binary(Group), 49 | {ok, {_, _, _, Value}} = lasp:update({GroupName, ?SET}, {rmv, Pid}, actor()), 50 | {ok, Value}. 51 | 52 | %% @doc Add a member to the process group. 53 | join(Group, Pid) -> 54 | GroupName = term_to_binary(Group), 55 | {ok, {_, _, _, Value}} = lasp:update({GroupName, ?SET}, {add, Pid}, actor()), 56 | {ok, Value}. 57 | 58 | %% @doc Add a member to the process group and maybe monitor it. 59 | -spec join(Group :: term(), Pid :: pid(), Monitor :: boolean()) -> {ok, term()}. 60 | join(Group, Pid, false) -> 61 | join(Group, Pid); 62 | join(Group, Pid, true) -> 63 | {ok, Value} = join(Group, Pid), 64 | lasp_pg_monitor:monitor_me(Group, Pid), 65 | {ok, Value}. 66 | 67 | %% @private 68 | actor() -> 69 | term_to_binary(node()). 70 | -------------------------------------------------------------------------------- /src/lasp_pg_app.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright (c) 2016 Christopher Meiklejohn. All Rights Reserved. 4 | %% 5 | %% This file is provided to you under the Apache License, 6 | %% Version 2.0 (the "License"); you may not use this file 7 | %% except in compliance with the License. You may obtain 8 | %% a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, 13 | %% software distributed under the License is distributed on an 14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | %% KIND, either express or implied. See the License for the 16 | %% specific language governing permissions and limitations 17 | %% under the License. 18 | %% 19 | %% ------------------------------------------------------------------- 20 | 21 | -module(lasp_pg_app). 22 | 23 | -behaviour(application). 24 | 25 | -include("lasp_pg.hrl"). 26 | 27 | -export([start/2, stop/1]). 28 | 29 | %% @doc Initialize the application. 30 | start(_StartType, _StartArgs) -> 31 | case lasp_pg_sup:start_link() of 32 | {ok, Pid} -> 33 | {ok, Pid}; 34 | Other -> 35 | {error, Other} 36 | end. 37 | 38 | %% @doc Stop the application. 39 | stop(_State) -> 40 | ok. 41 | -------------------------------------------------------------------------------- /src/lasp_pg_monitor.erl: -------------------------------------------------------------------------------- 1 | -module(lasp_pg_monitor). 2 | 3 | -behavior(gen_server). 4 | 5 | -export([start_link/0, 6 | monitor_me/2, 7 | rm_monitor/2]). 8 | 9 | -export([init/1, 10 | handle_call/3, 11 | handle_cast/2, 12 | handle_info/2, 13 | terminate/2, 14 | code_change/3]). 15 | 16 | -define(TAB, nuclues_pm_tab). 17 | 18 | -record(state, {}). 19 | 20 | start_link() -> 21 | case ets:info(?TAB, name) of 22 | undefined -> 23 | ets:new(?TAB, [bag, public, named_table, 24 | {write_concurrency, true}, 25 | {read_concurrency, true}]); 26 | _ -> 27 | ok 28 | end, 29 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 30 | 31 | monitor_me(Name, Pid) -> 32 | _ = ets:insert(?TAB, {Pid, Name}), 33 | gen_server:cast(?MODULE, {monitor_me, Pid}). 34 | 35 | rm_monitor(Name, Pid) -> 36 | ets:delete_object(?TAB, {Pid, Name}). 37 | 38 | init([]) -> 39 | {ok, #state{}}. 40 | 41 | handle_call(_Msg, _From, State) -> 42 | {noreply, State}. 43 | 44 | handle_cast({monitor_me, Pid}, State) -> 45 | _ = erlang:monitor(process, Pid), 46 | {noreply, State}. 47 | 48 | handle_info({'DOWN', _MRef, process, Pid, _}, S) -> 49 | _ = process_down(Pid), 50 | {noreply, S}; 51 | handle_info(_, State) -> 52 | {noreply, State}. 53 | 54 | code_change(_OldVsn, State, _Extra) -> 55 | {ok, State}. 56 | 57 | terminate(_Reason, _State) -> 58 | ok. 59 | 60 | %% Internal functions 61 | 62 | process_down(Pid) when is_pid(Pid) -> 63 | [begin 64 | unregister_name(Name, Pid), 65 | ets:delete(?TAB, Pid) 66 | end || {_, Name} <- ets:lookup(?TAB, Pid)]. 67 | 68 | 69 | -spec unregister_name(Name :: term(), Pid :: pid()) -> term(). 70 | unregister_name(Name, Pid) -> 71 | lasp_pg:leave(Name, Pid). 72 | -------------------------------------------------------------------------------- /src/lasp_pg_sup.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright (c) 2016 Christopher Meiklejohn. All Rights Reserved. 4 | %% 5 | %% This file is provided to you under the Apache License, 6 | %% Version 2.0 (the "License"); you may not use this file 7 | %% except in compliance with the License. You may obtain 8 | %% a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, 13 | %% software distributed under the License is distributed on an 14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | %% KIND, either express or implied. See the License for the 16 | %% specific language governing permissions and limitations 17 | %% under the License. 18 | %% 19 | %% ------------------------------------------------------------------- 20 | 21 | -module(lasp_pg_sup). 22 | 23 | -behaviour(supervisor). 24 | 25 | -include("lasp_pg.hrl"). 26 | 27 | -export([start_link/0]). 28 | 29 | -export([init/1]). 30 | 31 | -define(CHILD(I, Type, Timeout), 32 | {I, {I, start_link, []}, permanent, Timeout, Type, [I]}). 33 | -define(CHILD(I, Type), ?CHILD(I, Type, 5000)). 34 | 35 | start_link() -> 36 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 37 | 38 | init([]) -> 39 | Children = [?CHILD(lasp_pg_monitor, worker)], 40 | RestartStrategy = {one_for_one, 10, 10}, 41 | {ok, {RestartStrategy, Children}}. 42 | -------------------------------------------------------------------------------- /test/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lasp-lang/lasp_pg/2879b983613e7546eaf183dbf71efeed6f11cde8/test/.gitkeep -------------------------------------------------------------------------------- /test/lasp_pg_SUITE.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright (c) 2016 Christopher Meiklejohn. All Rights Reserved. 4 | %% 5 | %% This file is provided to you under the Apache License, 6 | %% Version 2.0 (the "License"); you may not use this file 7 | %% except in compliance with the License. You may obtain 8 | %% a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, 13 | %% software distributed under the License is distributed on an 14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | %% KIND, either express or implied. See the License for the 16 | %% specific language governing permissions and limitations 17 | %% under the License. 18 | %% 19 | %% ------------------------------------------------------------------- 20 | %% 21 | 22 | -module(lasp_pg_SUITE). 23 | -author("Christopher Meiklejohn "). 24 | 25 | -include("lasp_pg.hrl"). 26 | 27 | %% common_test callbacks 28 | -export([%% suite/0, 29 | init_per_suite/1, 30 | end_per_suite/1, 31 | init_per_testcase/2, 32 | end_per_testcase/2, 33 | all/0, 34 | groups/0, 35 | init_per_group/2]). 36 | 37 | %% tests 38 | -compile([export_all]). 39 | 40 | -include_lib("common_test/include/ct.hrl"). 41 | -include_lib("eunit/include/eunit.hrl"). 42 | -include_lib("kernel/include/inet.hrl"). 43 | 44 | -define(CLIENT_NUMBER, 3). 45 | 46 | %% =================================================================== 47 | %% common_test callbacks 48 | %% =================================================================== 49 | 50 | init_per_suite(_Config) -> 51 | _Config. 52 | 53 | end_per_suite(_Config) -> 54 | _Config. 55 | 56 | init_per_testcase(Case, Config) -> 57 | ct:pal("Beginning test case ~p", [Case]), 58 | 59 | [{hash, erlang:phash2({Case, Config})}|Config]. 60 | 61 | end_per_testcase(Case, _Config) -> 62 | ct:pal("Ending test case ~p", [Case]), 63 | 64 | _Config. 65 | 66 | init_per_group(_, Config) -> 67 | Config. 68 | 69 | end_per_group(_, _Config) -> 70 | ok. 71 | 72 | all() -> 73 | [default_manager_test]. 74 | 75 | groups() -> 76 | [default]. 77 | 78 | %% =================================================================== 79 | %% Tests. 80 | %% =================================================================== 81 | 82 | default_manager_test(Config) -> 83 | %% Use the default peer service manager. 84 | Manager = partisan_default_peer_service_manager, 85 | 86 | %% Specify servers. 87 | Servers = node_list(1, "server", Config), 88 | 89 | %% Specify clients. 90 | Clients = node_list(?CLIENT_NUMBER, "client", Config), 91 | 92 | %% Start nodes. 93 | Nodes = start(default_manager_test, Config, 94 | [{partisan_peer_service_manager, Manager}, 95 | {servers, Servers}, 96 | {clients, Clients}]), 97 | 98 | %% Pause for clustering. 99 | timer:sleep(1000), 100 | 101 | %% Verify membership. 102 | %% 103 | %% Every node should know about every other node in this topology. 104 | %% 105 | VerifyFun = fun({_, Node}) -> 106 | {ok, Members} = rpc:call(Node, Manager, members, []), 107 | SortedNodes = lists:usort([N || {_, N} <- Nodes]), 108 | SortedMembers = lists:usort(Members), 109 | case SortedMembers =:= SortedNodes of 110 | true -> 111 | true; 112 | false -> 113 | ct:pal("Membership incorrect; node ~p should have ~p but has ~p", 114 | [Node, SortedNodes, SortedMembers]), 115 | {false, {Node, SortedNodes, SortedMembers}} 116 | end 117 | end, 118 | 119 | %% Verify the membership is correct. 120 | lists:foreach(fun(Node) -> 121 | VerifyNodeFun = fun() -> VerifyFun(Node) end, 122 | 123 | case wait_until(VerifyNodeFun, 60 * 2, 100) of 124 | ok -> 125 | ok; 126 | {fail, {false, {Node, Expected, Contains}}} -> 127 | ct:fail("Membership incorrect; node ~p should have ~p but has ~p", 128 | [Node, Expected, Contains]) 129 | end 130 | end, Nodes), 131 | 132 | %% Verify forward message functionality. 133 | lists:foreach(fun({_Name, Node}) -> 134 | ok = check_forward_message(Node, Manager) 135 | end, Nodes), 136 | 137 | %% Verify parallelism. 138 | ConfigParallelism = proplists:get_value(parallelism, Config, ?PARALLELISM), 139 | ct:pal("Configured parallelism: ~p", [ConfigParallelism]), 140 | 141 | %% Verify channels. 142 | ConfigChannels = proplists:get_value(channels, Config, ?CHANNELS), 143 | ct:pal("Configured channels: ~p", [ConfigChannels]), 144 | 145 | ConnectionsFun = fun(Node) -> 146 | Connections = rpc:call(Node, 147 | partisan_default_peer_service_manager, 148 | connections, 149 | []), 150 | ct:pal("Connections: ~p~n", [Connections]), 151 | Connections 152 | end, 153 | 154 | VerifyConnectionsFun = fun(Node, Channel, Parallelism) -> 155 | %% Get list of connections. 156 | {ok, Connections} = ConnectionsFun(Node), 157 | 158 | %% Verify we have enough connections. 159 | dict:fold(fun(_N, Active, Acc) -> 160 | Filtered = lists:filter(fun({_, C, _}) -> 161 | case C of 162 | Channel -> 163 | true; 164 | _ -> 165 | false 166 | end 167 | end, Active), 168 | 169 | case length(Filtered) == Parallelism of 170 | true -> 171 | Acc andalso true; 172 | false -> 173 | Acc andalso false 174 | end 175 | end, true, Connections) 176 | end, 177 | 178 | lists:foreach(fun({_Name, Node}) -> 179 | %% Get enabled parallelism. 180 | Parallelism = rpc:call(Node, partisan_config, get, [parallelism, ?PARALLELISM]), 181 | ct:pal("Parallelism is: ~p", [Parallelism]), 182 | 183 | %% Get enabled channels. 184 | Channels = rpc:call(Node, partisan_config, get, [channels, ?CHANNELS]), 185 | ct:pal("Channels are: ~p", [Channels]), 186 | 187 | lists:foreach(fun(Channel) -> 188 | %% Generate fun. 189 | VerifyConnectionsNodeFun = fun() -> 190 | VerifyConnectionsFun(Node, Channel, Parallelism) 191 | end, 192 | 193 | %% Wait until connections established. 194 | case wait_until(VerifyConnectionsNodeFun, 60 * 2, 100) of 195 | ok -> 196 | ok; 197 | _ -> 198 | ct:fail("Not enough connections have been opened; need: ~p", [Parallelism]) 199 | end 200 | end, Channels) 201 | end, Nodes), 202 | 203 | %% Select two of the nodes. 204 | [{_, Node1}, {_, Node2}|_] = Nodes, 205 | 206 | %% Setup processs test. 207 | ct:pal("Spawning processes for test."), 208 | Group = group, 209 | 210 | %% Spawn process 1 and join to group. 211 | Pid1 = spawn(fun() -> 212 | receive 213 | exit -> 214 | ok 215 | end 216 | end), 217 | {ok, _} = rpc:call(Node1, lasp_pg, join, [Group, Pid1]), 218 | 219 | %% Verify join. 220 | {ok, EncodedMembers1} = rpc:call(Node1, lasp_pg, members, [Group]), 221 | Members1 = sets:to_list(EncodedMembers1), 222 | ct:pal("Members on node ~p after join is ~p", [Node1, Members1]), 223 | ?assertMatch([Pid1], Members1), 224 | 225 | %% Spawn process 2 and join to group. 226 | Pid2 = spawn(fun() -> 227 | receive 228 | exit -> 229 | ok 230 | end 231 | end), 232 | {ok, _} = rpc:call(Node2, lasp_pg, join, [Group, Pid2]), 233 | 234 | %% Wait anti-entropy interval. 235 | timer:sleep(5000), 236 | 237 | %% Verify join propagates back to node 1. 238 | wait_until(fun() -> 239 | {ok, EncodedMembers2} = rpc:call(Node1, lasp_pg, members, [Group]), 240 | Members2 = sets:to_list(EncodedMembers2), 241 | ct:pal("Members on node ~p after join is ~p", [Node1, Members2]), 242 | [Pid1, Pid2] =:= Members2 243 | end, 60 * 2, 500), 244 | %% Stop nodes. 245 | stop(Nodes), 246 | 247 | ok. 248 | 249 | %% =================================================================== 250 | %% Internal functions. 251 | %% =================================================================== 252 | 253 | %% @private 254 | start(_Case, Config, Options) -> 255 | %% Launch distribution for the test runner. 256 | ct:pal("Launching Erlang distribution..."), 257 | 258 | {ok, Hostname} = inet:gethostname(), 259 | os:cmd(os:find_executable("epmd") ++ " -daemon"), 260 | case net_kernel:start([list_to_atom("runner@" ++ Hostname), shortnames]) of 261 | {ok, _} -> 262 | ok; 263 | {error, {already_started, _}} -> 264 | ok 265 | end, 266 | 267 | %% Load sasl. 268 | application:load(sasl), 269 | ok = application:set_env(sasl, 270 | sasl_error_logger, 271 | false), 272 | application:start(sasl), 273 | 274 | %% Load lager. 275 | {ok, _} = application:ensure_all_started(lager), 276 | 277 | Servers = proplists:get_value(servers, Options, []), 278 | Clients = proplists:get_value(clients, Options, []), 279 | 280 | NodeNames = lists:flatten(Servers ++ Clients), 281 | 282 | %% Start all nodes. 283 | InitializerFun = fun(Name) -> 284 | ct:pal("Starting node: ~p", [Name]), 285 | 286 | NodeConfig = [{monitor_master, true}, 287 | {startup_functions, [{code, set_path, [codepath()]}]}], 288 | 289 | case ct_slave:start(Name, NodeConfig) of 290 | {ok, Node} -> 291 | {Name, Node}; 292 | Error -> 293 | ct:fail(Error) 294 | end 295 | end, 296 | Nodes = lists:map(InitializerFun, NodeNames), 297 | 298 | %% Load applications on all of the nodes. 299 | LoaderFun = fun({_Name, Node}) -> 300 | ct:pal("Loading applications on node: ~p", [Node]), 301 | 302 | PrivDir = code:priv_dir(?APP), 303 | NodeDir = filename:join([PrivDir, "lager", Node]), 304 | 305 | %% Manually force sasl loading, and disable the logger. 306 | ok = rpc:call(Node, application, load, [sasl]), 307 | ok = rpc:call(Node, application, set_env, 308 | [sasl, sasl_error_logger, false]), 309 | ok = rpc:call(Node, application, start, [sasl]), 310 | 311 | ok = rpc:call(Node, application, load, [partisan]), 312 | ok = rpc:call(Node, application, load, [lager]), 313 | ok = rpc:call(Node, application, load, [lasp_pg]), 314 | ok = rpc:call(Node, application, set_env, [sasl, 315 | sasl_error_logger, 316 | false]), 317 | ok = rpc:call(Node, application, set_env, [lager, 318 | log_root, 319 | NodeDir]) 320 | end, 321 | lists:map(LoaderFun, Nodes), 322 | 323 | %% Configure settings. 324 | ConfigureFun = fun({Name, Node}) -> 325 | %% Configure the peer service. 326 | PeerService = proplists:get_value(partisan_peer_service_manager, Options), 327 | ct:pal("Setting peer service manager on node ~p to ~p", [Node, PeerService]), 328 | ok = rpc:call(Node, partisan_config, set, 329 | [partisan_peer_service_manager, PeerService]), 330 | 331 | MaxActiveSize = proplists:get_value(max_active_size, Options, 5), 332 | ok = rpc:call(Node, partisan_config, set, 333 | [max_active_size, MaxActiveSize]), 334 | 335 | ok = rpc:call(Node, application, set_env, [partisan, peer_ip, ?PEER_IP]), 336 | 337 | Channels = case ?config(channels, Config) of 338 | undefined -> 339 | ?CHANNELS; 340 | C -> 341 | C 342 | end, 343 | ct:pal("Setting channels to: ~p", [Channels]), 344 | ok = rpc:call(Node, partisan_config, set, [channels, Channels]), 345 | 346 | ok = rpc:call(Node, partisan_config, set, [tls, ?config(tls, Config)]), 347 | Parallelism = case ?config(parallelism, Config) of 348 | undefined -> 349 | ?PARALLELISM; 350 | P -> 351 | P 352 | end, 353 | ct:pal("Setting parallelism to: ~p", [Parallelism]), 354 | ok = rpc:call(Node, partisan_config, set, [parallelism, Parallelism]), 355 | 356 | Servers = proplists:get_value(servers, Options, []), 357 | Clients = proplists:get_value(clients, Options, []), 358 | 359 | %% Configure servers. 360 | case lists:member(Name, Servers) of 361 | true -> 362 | ok = rpc:call(Node, partisan_config, set, [tag, server]), 363 | ok = rpc:call(Node, partisan_config, set, [tls_options, ?config(tls_server_opts, Config)]); 364 | false -> 365 | ok 366 | end, 367 | 368 | %% Configure clients. 369 | case lists:member(Name, Clients) of 370 | true -> 371 | ok = rpc:call(Node, partisan_config, set, [tag, client]), 372 | ok = rpc:call(Node, partisan_config, set, [tls_options, ?config(tls_client_opts, Config)]); 373 | false -> 374 | ok 375 | end 376 | end, 377 | lists:foreach(ConfigureFun, Nodes), 378 | 379 | ct:pal("Starting nodes."), 380 | 381 | StartFun = fun({_Name, Node}) -> 382 | %% Start Lasp PG. 383 | {ok, Apps} = rpc:call(Node, application, ensure_all_started, [lasp_pg]), 384 | ct:pal("Started dependent applications: ~p", [Apps]), 385 | 386 | %% Start a dummy registered process that saves in the environment 387 | %% whatever message it gets, it will only do this *x* amount of times 388 | %% *x* being the number of nodes present in the cluster 389 | Pid = rpc:call(Node, erlang, spawn, 390 | [fun() -> 391 | lists:foreach(fun(_) -> 392 | receive 393 | {store, N} -> 394 | %% save the number in the environment 395 | application:set_env(partisan, forward_message_test, N) 396 | end 397 | end, lists:seq(1, length(NodeNames))) 398 | end]), 399 | true = rpc:call(Node, erlang, register, [store_proc, Pid]), 400 | ct:pal("registered store_proc on pid ~p, node ~p", 401 | [Pid, Node]) 402 | end, 403 | lists:foreach(StartFun, Nodes), 404 | 405 | ct:pal("Clustering nodes."), 406 | lists:foreach(fun(Node) -> cluster(Node, Nodes, Options, Config) end, Nodes), 407 | 408 | ct:pal("Partisan fully initialized."), 409 | 410 | Nodes. 411 | 412 | %% @private 413 | omit(OmitNameList, Nodes0) -> 414 | FoldFun = fun({Name, _Node} = N, Nodes) -> 415 | case lists:member(Name, OmitNameList) of 416 | true -> 417 | Nodes; 418 | false -> 419 | Nodes ++ [N] 420 | end 421 | end, 422 | lists:foldl(FoldFun, [], Nodes0). 423 | 424 | %% @private 425 | codepath() -> 426 | lists:filter(fun filelib:is_dir/1, code:get_path()). 427 | 428 | %% @private 429 | %% 430 | %% We have to cluster each node with all other nodes to compute the 431 | %% correct overlay: for instance, sometimes you'll want to establish a 432 | %% client/server topology, which requires all nodes talk to every other 433 | %% node to correctly compute the overlay. 434 | %% 435 | cluster({Name, _Node} = Myself, Nodes, Options, Config) when is_list(Nodes) -> 436 | Manager = proplists:get_value(partisan_peer_service_manager, Options), 437 | 438 | Servers = proplists:get_value(servers, Options, []), 439 | Clients = proplists:get_value(clients, Options, []), 440 | 441 | AmIServer = lists:member(Name, Servers), 442 | AmIClient = lists:member(Name, Clients), 443 | 444 | OtherNodes = case Manager of 445 | partisan_default_peer_service_manager -> 446 | %% Omit just ourselves. 447 | omit([Name], Nodes); 448 | partisan_client_server_peer_service_manager -> 449 | case {AmIServer, AmIClient} of 450 | {true, false} -> 451 | %% If I'm a server, I connect to both 452 | %% clients and servers! 453 | omit([Name], Nodes); 454 | {false, true} -> 455 | %% I'm a client, pick servers. 456 | omit(Clients, Nodes); 457 | {_, _} -> 458 | omit([Name], Nodes) 459 | end; 460 | partisan_hyparview_peer_service_manager -> 461 | case {AmIServer, AmIClient} of 462 | {true, false} -> 463 | %% If I'm a server, I connect to both 464 | %% clients and servers! 465 | omit([Name], Nodes); 466 | {false, true} -> 467 | %% I'm a client, pick servers. 468 | omit(Clients, Nodes); 469 | {_, _} -> 470 | omit([Name], Nodes) 471 | end 472 | end, 473 | lists:map(fun(OtherNode) -> cluster(Myself, OtherNode, Config) end, OtherNodes). 474 | cluster({_, Node}, {_, OtherNode}, Config) -> 475 | PeerPort = rpc:call(OtherNode, 476 | partisan_config, 477 | get, 478 | [peer_port, ?PEER_PORT]), 479 | Parallelism = case ?config(parallelism, Config) of 480 | undefined -> 481 | 1; 482 | P -> 483 | P 484 | end, 485 | Channels = case ?config(channels, Config) of 486 | undefined -> 487 | []; 488 | C -> 489 | C 490 | end, 491 | JoinMethod = case ?config(sync_join, Config) of 492 | undefined -> 493 | join; 494 | true -> 495 | sync_join 496 | end, 497 | ct:pal("Joining node: ~p to ~p at port ~p", [Node, OtherNode, PeerPort]), 498 | ok = rpc:call(Node, 499 | partisan_peer_service, 500 | JoinMethod, 501 | [#{name => OtherNode, 502 | listen_addrs => [#{ip => {127, 0, 0, 1}, port => PeerPort}], 503 | channels => Channels, 504 | parallelism => Parallelism}]). 505 | 506 | %% @private 507 | stop(Nodes) -> 508 | StopFun = fun({Name, _Node}) -> 509 | case ct_slave:stop(Name) of 510 | {ok, _} -> 511 | ok; 512 | {error, stop_timeout, _} -> 513 | ok; 514 | Error -> 515 | ct:fail(Error) 516 | end 517 | end, 518 | lists:map(StopFun, Nodes), 519 | ok. 520 | 521 | %% @private 522 | connect(G, N1, N2) -> 523 | %% Add vertex for neighboring node. 524 | digraph:add_vertex(G, N1), 525 | % ct:pal("Adding vertex: ~p", [N1]), 526 | 527 | %% Add vertex for neighboring node. 528 | digraph:add_vertex(G, N2), 529 | % ct:pal("Adding vertex: ~p", [N2]), 530 | 531 | %% Add edge to that node. 532 | digraph:add_edge(G, N1, N2), 533 | % ct:pal("Adding edge from ~p to ~p", [N1, N2]), 534 | 535 | ok. 536 | 537 | %% @private 538 | node_list(0, _Name, _Config) -> 539 | []; 540 | node_list(N, Name, Config) -> 541 | [ list_to_atom(string:join([Name, 542 | integer_to_list(?config(hash, Config)), 543 | integer_to_list(X)], 544 | "_")) || 545 | X <- lists:seq(1, N) ]. 546 | 547 | %% @private 548 | make_certs(Config) -> 549 | DataDir = ?config(data_dir, Config), 550 | PrivDir = ?config(priv_dir, Config), 551 | ct:pal("Generating TLS certificates into ~s", [PrivDir]), 552 | MakeCertsFile = filename:join(DataDir, "make_certs.erl"), 553 | {ok, make_certs, ModBin} = compile:file(MakeCertsFile, 554 | [binary, debug_info, report_errors, report_warnings]), 555 | {module, make_certs} = code:load_binary(make_certs, MakeCertsFile, ModBin), 556 | 557 | make_certs:all(DataDir, PrivDir), 558 | 559 | [{tls_server_opts, 560 | [ 561 | {certfile, filename:join(PrivDir, "server/keycert.pem")}, 562 | {cacertfile, filename:join(PrivDir, "server/cacerts.pem")} 563 | ]}, 564 | {tls_client_opts, 565 | [ 566 | {certfile, filename:join(PrivDir, "client/keycert.pem")}, 567 | {cacertfile, filename:join(PrivDir, "client/cacerts.pem")} 568 | ]}]. 569 | 570 | %% @private 571 | check_forward_message(Node, Manager) -> 572 | {ok, Members} = rpc:call(Node, Manager, members, []), 573 | %% ask member node to forward a message to one other random member 574 | ct:pal("members of ~p: ~p", [Node, Members]), 575 | RandomMember = random(Members, Node), 576 | ct:pal("requesting node ~p to forward message to store_proc on node ~p", 577 | [Node, RandomMember]), 578 | Rand = rand_compat:uniform(), 579 | ok = rpc:call(Node, Manager, forward_message, 580 | [RandomMember, store_proc, {store, Rand}]), 581 | %% now fetch the value from the random destination node 582 | ok = wait_until(fun() -> 583 | %% it must match with what we asked the node to forward 584 | case rpc:call(RandomMember, application, get_env, [partisan, forward_message_test]) of 585 | {ok, R} -> 586 | R =:= Rand; 587 | _ -> 588 | false 589 | end 590 | end, 60 * 2, 500), 591 | 592 | ok. 593 | 594 | random(List0, Omit) -> 595 | List = List0 -- lists:flatten([Omit]), 596 | %% Catch exceptions where there may not be enough members. 597 | try 598 | Index = rand_compat:uniform(length(List)), 599 | lists:nth(Index, List) 600 | catch 601 | _:_ -> 602 | undefined 603 | end. 604 | 605 | wait_until(Fun, Retry, Delay) when Retry > 0 -> 606 | Res = Fun(), 607 | case Res of 608 | true -> 609 | ok; 610 | _ when Retry == 1 -> 611 | {fail, Res}; 612 | _ -> 613 | timer:sleep(Delay), 614 | wait_until(Fun, Retry-1, Delay) 615 | end. 616 | 617 | %% @private 618 | %% 619 | %% Kill a random node and then return a list of nodes that still have the 620 | %% killed node in their membership 621 | %% 622 | hyparview_check_stopped_member(_, [_Node]) -> {undefined, []}; 623 | hyparview_check_stopped_member(KilledNode, Nodes) -> 624 | %% Obtain the membership from all the nodes, 625 | %% the killed node shouldn't be there 626 | lists:filtermap(fun({_, Node}) -> 627 | {ok, Members} = rpc:call(Node, partisan_peer_service, members, []), 628 | case lists:member(KilledNode, Members) of 629 | true -> 630 | {true, Node}; 631 | false -> 632 | false 633 | end 634 | end, Nodes). 635 | 636 | %% @private 637 | hyparview_membership_check(Nodes) -> 638 | Manager = partisan_hyparview_peer_service_manager, 639 | %% Create new digraph. 640 | Graph = digraph:new(), 641 | 642 | %% Verify membership. 643 | %% 644 | %% Every node should know about every other node in this topology 645 | %% when the active setting is high. 646 | %% 647 | ConnectFun = 648 | fun({_, Node}) -> 649 | {ok, ActiveSet} = rpc:call(Node, Manager, active, []), 650 | Active = sets:to_list(ActiveSet), 651 | 652 | %% Add vertexes and edges. 653 | [connect(Graph, Node, N) || #{name := N} <- Active] 654 | end, 655 | %% Build a digraph representing the membership 656 | lists:foreach(ConnectFun, Nodes), 657 | 658 | %% Verify connectedness. 659 | %% Return a list of node tuples that were found not to be connected, 660 | %% empty otherwise 661 | ConnectedFails = 662 | lists:flatmap(fun({_Name, Node}=Myself) -> 663 | lists:filtermap(fun({_, N}) -> 664 | Path = digraph:get_short_path(Graph, Node, N), 665 | case Path of 666 | false -> 667 | %% print out the active view of each node 668 | lists:foreach(fun({_, N1}) -> 669 | {ok, ActiveSet} = rpc:call(N1, Manager, active, []), 670 | Active = sets:to_list(ActiveSet), 671 | ct:pal("node ~p active view: ~p", 672 | [N1, Active]) 673 | end, Nodes), 674 | {true, {Node, N}}; 675 | _ -> 676 | false 677 | end 678 | end, Nodes -- [Myself]) 679 | end, Nodes), 680 | 681 | %% Verify symmetry. 682 | SymmetryFails = 683 | lists:flatmap(fun({_, Node1}) -> 684 | %% Get first nodes active set. 685 | {ok, ActiveSet1} = rpc:call(Node1, Manager, active, []), 686 | Active1 = sets:to_list(ActiveSet1), 687 | 688 | lists:filtermap(fun(#{name := Node2}) -> 689 | %% Get second nodes active set. 690 | {ok, ActiveSet2} = rpc:call(Node2, Manager, active, []), 691 | Active2 = sets:to_list(ActiveSet2), 692 | 693 | case lists:member(Node1, [N || #{name := N} <- Active2]) of 694 | true -> 695 | false; 696 | false -> 697 | {true, {Node1, Node2}} 698 | end 699 | end, Active1) 700 | end, Nodes), 701 | 702 | {ConnectedFails, SymmetryFails}. 703 | -------------------------------------------------------------------------------- /tools.mk: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------- 2 | # 3 | # Copyright (c) 2014 Basho Technologies, Inc. 4 | # 5 | # This file is provided to you under the Apache License, 6 | # Version 2.0 (the "License"); you may not use this file 7 | # except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, 13 | # software distributed under the License is distributed on an 14 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | # KIND, either express or implied. See the License for the 16 | # specific language governing permissions and limitations 17 | # under the License. 18 | # 19 | # ------------------------------------------------------------------- 20 | 21 | # ------------------------------------------------------------------- 22 | # NOTE: This file is is from https://github.com/basho/tools.mk. 23 | # It should not be edited in a project. It should simply be updated 24 | # wholesale when a new version of tools.mk is released. 25 | # ------------------------------------------------------------------- 26 | 27 | REBAR = $(shell pwd)/rebar3 28 | REVISION ?= $(shell git rev-parse --short HEAD) 29 | PROJECT ?= $(shell basename `find src -name "*.app.src"` .app.src) 30 | DEP_DIR ?= "deps" 31 | EBIN_DIR ?= "ebin" 32 | 33 | .PHONY: compile-no-deps test docs xref dialyzer-run dialyzer-quick dialyzer \ 34 | cleanplt upload-docs 35 | 36 | compile-no-deps: 37 | ${REBAR} compile skip_deps=true 38 | 39 | docs: 40 | ${REBAR} doc skip_deps=true 41 | 42 | xref: compile 43 | ${REBAR} xref skip_deps=true 44 | 45 | dialyzer: compile 46 | ${REBAR} dialyzer 47 | --------------------------------------------------------------------------------