├── .gitignore ├── .travis.yml ├── Emakefile ├── LICENSE.md ├── Makefile ├── README.md ├── bin └── erlware_release_start_helper ├── dialyzer.ignore-warning ├── etc └── log4erl.conf ├── include └── resource_discovery.hrl ├── overview.edoc ├── rebar.config ├── sinan.cfg ├── src ├── rd_core.erl ├── rd_heartbeat.erl ├── rd_store.erl ├── rd_sup.erl ├── rd_util.erl ├── resource_discovery.app.src ├── resource_discovery.erl └── sys.config ├── start.sh └── test ├── rd_store_tests.erl └── resource_discovery_tests.erl /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .eunit 3 | logs 4 | /ebin/* 5 | *~ 6 | .#* 7 | *.beam 8 | /deps/* 9 | /doc/* 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: erlang 2 | otp_release: 3 | - 17.1 4 | - 17.0 5 | - R16B03-1 6 | - R16B03 7 | - R16B02 8 | - R16B01 9 | - R16B 10 | script: "make && make test" 11 | branches: 12 | only: 13 | - master 14 | -------------------------------------------------------------------------------- /Emakefile: -------------------------------------------------------------------------------- 1 | %% need to keep in place to make flymake work 2 | {['src/*'], [debug_info, {outdir, "ebin"}, {i,"include"}]}. 3 | {['test/*'], [debug_info, {outdir, "ebin"}, {i,"include"}, {i, "test"}]}. 4 | 5 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REBAR=rebar 2 | 3 | all: get-deps compile 4 | 5 | get-deps: 6 | $(REBAR) get-deps 7 | 8 | compile: 9 | $(REBAR) compile 10 | 11 | clean: 12 | $(REBAR) clean 13 | rm -rf ebin/*.* 14 | rm -rf test/*.beam 15 | rm -rf test/*.config 16 | 17 | test: all 18 | mkdir -p .eunit 19 | cp -r etc .eunit/. 20 | ERL_FLAGS="-sname rebar" $(REBAR) skip_deps=true eunit 21 | 22 | doc: 23 | $(REBAR) doc skip_deps=true 24 | 25 | APPS = kernel stdlib sasl erts ssl tools os_mon runtime_tools crypto inets \ 26 | xmerl snmp public_key mnesia eunit syntax_tools compiler 27 | COMBO_PLT = $(HOME)/.resource_discovery_dialyzer_plt 28 | 29 | check_plt: compile 30 | dialyzer --check_plt --plt $(COMBO_PLT) --apps $(APPS) \ 31 | 32 | build_plt: compile 33 | dialyzer --build_plt --output_plt $(COMBO_PLT) --apps $(APPS) \ 34 | 35 | dialyzer: compile 36 | @echo 37 | @echo Use "'make check_plt'" to check PLT prior to using this target. 38 | @echo Use "'make build_plt'" to build PLT prior to using this target. 39 | @echo 40 | @sleep 1 41 | dialyzer -Wno_return --plt $(COMBO_PLT) ebin | fgrep -v -f ./dialyzer.ignore-warning 42 | 43 | cleanplt: 44 | @echo 45 | @echo "Are you sure? It takes about 1/2 hour to re-build." 46 | @echo Deleting $(COMBO_PLT) in 5 seconds. 47 | @echo 48 | sleep 5 49 | rm $(COMBO_PLT) 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/erlware/resource_discovery.svg?branch=master)](https://travis-ci.org/erlware/resource_discovery) 2 | 3 | The Resource Discovery Application 4 | ================================== 5 | 6 | ### Authored by: Martin J. Logan @martinjlogan martinjlogan@erlware.org 7 | ### Many improvements by: Roman Shestakov 8 | 9 | Manual build instructions 10 | ========================= 11 | 12 | 1. clone / build rebar from here: https://github.com/basho/rebar 13 | 2. clone project: git clone git@github.com:RomanShestakov/Resource_discovery.git 14 | 3. build the project: cd resource_discovery;make 15 | 16 | 17 | Overview 18 | ======== 19 | 20 | The Resource Discovery application allows you to set up smart Erlang clusters. Producers and consumers within an Erlang cluster can find eachother with no pre-configuration needed. For example lets say we have three services in a cluster of nodes: 21 | 22 | * Video Display Agent 23 | * Video Producer 24 | * System Logger 25 | 26 | The Display Agent must of course be able to find a Video Producer and both the Video Producer and the Display Agent must know about the system logger. Before understanding how all that is expressed in Resource Discovery we need to get some definitions out of the way. 27 | Definitions 28 | 29 | Resource Discovery has 3 major types that you need to be concerned with. They are listed here. 30 | 31 | resource_tuple() = {resource_type(), resource()}. The type of a resource followed by the actual resource. Local resource tuples are communicated to other resource discovery instances. 32 | 33 | resource_type() = atom(). The name of a resource, how it is identified. For example a type of service that you have on the network may be identified by it's node name in which case you might have a resource type of 'my_service' of which there may be many node names representing resources such as {my_service, myservicenode@myhost}. 34 | 35 | resource() = term(). Either a concrete resource or a reference to one like a pid(). 36 | 37 | Expressing Relationships 38 | ======================== 39 | 40 | Using these definitions we can describe the relationships that we have between our different services; the Video Display Agent, the Video Producer, and the System Logger. It is helpful to break things down into three categories. These are as follows: 41 | 42 | * Local Resources Tuples 43 | * Target Resource Types 44 | * Cached Resource Tuples 45 | 46 | Local Resources Tuples contain those resources that a running Erlang node has to share with the cluster. A Video Display Agent node could make a call like what is below to express what it has to share with the network. 47 | 48 | ``` 49 | resource_discovery:add_local_resource_tuples({video_producer, node()}). 50 | ``` 51 | 52 | A Video Producer node needs to know about the System Logger instance and about all instances of Video Display Agent nodes. To express this it could make a call like what is below to express this. 53 | 54 | ``` 55 | resource_discovery:add_target_resource_types([video_display, system_logger]). 56 | ``` 57 | 58 | This indicates that the Video Producer service wants to find out about all services in the cluster of resource types video_display and system_logger. 59 | 60 | Now we are set up for our system to obtain resource tuples from other nodes in the cluster and cache them locally. Cached Resource Tuples are those that have been discovered and are ready to be used. When each of the nodes comes up onto the network they will make one of the following calls: 61 | 62 | ``` 63 | resource_discovery:trade_resources(). 64 | ``` 65 | 66 | or 67 | 68 | ``` 69 | resource_discovery:sync_resources(). 70 | ``` 71 | 72 | This will communicate with all other nodes in the current network. When this function returns all Resource Tuples on the network that match the calling nodes Target Resource Types will be cached locally. Furthermore the local nodes Local Resource Tuples will be cached on all remote nodes that have specified their resource types as target resource types. trade_resources/0 accomplishes this asyncronously where as sync_resources is a blocking call which typically accomplishes a full sync before returning. After either of these calls have been made you can go about using the resources you have discovered. 73 | 74 | To use a resource from the resource cache Resource Discovery provides a number of useful functions. The most common of these is get_resource/1. This will loop through resources in round robin fashion supplying a different resource each time it is called. There are also the rpc functions that act upon node based resources; these are rpc_call and rpc_multicall. There are other functions available for more control over the specific resources of a particular type you get. There are also functions provided for removing resources that have been found to be malfunctioning or simply not around anymore. This is up to the user of resource discovery to do at the application level. 75 | Getting Notifications about New Resources 76 | 77 | Sometimes when resources become available in the cluster it is desirable for an application to be notified of their presence. If you want immediate notification of new Cached Resources you can subscribe to notifications by using: 78 | 79 | ``` 80 | resource_discovery:add_callback_modules([video_send, log_send]). 81 | ``` 82 | 83 | The above call will ensure that the exported function "resource_up/1" will be called upon the caching of a new resource within the modules "video_send" and "log_send". Those functions will be called each time this happens regardless of the resource and so the resource up function should use pattern matching like that pictured below to only concern itself with the resources it needs to act upon. 84 | 85 | ``` 86 | resource_up({system_logger, Instance}) -> 87 | ... do stuff ... 88 | resource_up(_DontCare) -> 89 | ok. 90 | ``` 91 | 92 | Getting into an Erlang Cloud 93 | ============================ 94 | 95 | Getting into an Erlang node cluster is required as a minimal bootstrap for Resource Discovery. To accomplish initial contact by pinging remote nodes already in the desired cloud the following entries should be in your Erlang config file. 96 | 97 | ``` 98 | {resource_discovery, 99 | [ 100 | {contact_nodes, [node1@mynet.com, node2@mynet.com]} 101 | ] 102 | } 103 | ``` 104 | 105 | These entries instruct resource discovery to ping node1 and node2 in order to join an Erlang cloud. At least one of the pings must succeed in order for startup to succeed. Optionally all this can be overridden at the command line with the -contact_node flag: 106 | 107 | ``` 108 | erl ... -contact_node node3@mynet.com 109 | ``` 110 | 111 | The above flag set at startup would instruct Resource Discovery to ping node3 instead of those configured in the config file. In the future a plugin scheme will be added so that other methods of bootstrapping into a cloud may be added. An easy way to get up and running for a test is to fire up the first node pointing to itself and then additional nodes pointing to the first as in the following example: (remember that "Macintosh" should be replaced with your local sname suffix) 112 | 113 | ``` 114 | erl -sname a -contact_node a@Macintosh 115 | erl -sname b -contact_node a@Macintosh 116 | erl -sname c -contact_node a@Macintosh 117 | ``` 118 | Overcoming network and resource failures with heartbeating 119 | =========================================================== 120 | 121 | At times resources fail and are perhaps taken out of the store of Cached Resource Tuples by an application when it discovers the resource is no longer around. At other times networks may fail and clusters become separated. One way of overcomming all of this is to setup heartbeats from Resource Discovery. Resource discovery can be configured to heartbeat at a specified interval. This means that it will both re-ping the configured contact nodes and will run a trade_resources/0 at the specified interval. To enable this place configuration similar to what is below in you Erlang config file. 122 | 123 | ``` 124 | {resource_discovery, 125 | [ 126 | {heartbeat_frequency, 60000} 127 | ] 128 | } 129 | ``` 130 | 131 | An entry like that above tells Resource Discovery to ping on average every 60000 milliseconds (+- a random factor). An entry of 0 disables heartbeating all together and is the default if not configured. 132 | 133 | Enjoy! Send questions of comments to erlware-questions@erlware.org or erlware-dev@erlware.org. 134 | -------------------------------------------------------------------------------- /bin/erlware_release_start_helper: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ $# -lt 3 ];then 4 | echo "usage $0 [extra-args]" 5 | exit 1 6 | fi 7 | 8 | REL_NAME=$1; shift 9 | REL_VSN=$1; shift 10 | ERTS_VSN=$1; shift 11 | CONFIG_FILE_NAME=$1 12 | 13 | ERTS_DIR=$ROOTDIR/erts-$ERTS_VSN 14 | export BINDIR=$ERTS_DIR/bin 15 | export EMU=beam 16 | export PROGNAME=erl 17 | export LD_LIBRARY_PATH=$ERTS_DIR/lib 18 | 19 | export REL_DIR=$ROOTDIR/releases/$REL_NAME-$REL_VSN 20 | 21 | if [ "$CONFIG_FILE_NAME" = "no_config" ];then 22 | $BINDIR/erlexec -boot $REL_DIR/$REL_NAME $@ 23 | else 24 | shift 25 | $BINDIR/erlexec -config $REL_DIR/$CONFIG_FILE_NAME -boot $REL_DIR/$REL_NAME $@ 26 | fi 27 | -------------------------------------------------------------------------------- /dialyzer.ignore-warning: -------------------------------------------------------------------------------- 1 | rd_store_tests.erl:31: The variable __V can never match since previous clauses completely covered the type 'true' 2 | rd_store_tests.erl:35: The variable __V can never match since previous clauses completely covered the type 'true' 3 | rd_store_tests.erl:39: The variable __V can never match since previous clauses completely covered the type 'ok' 4 | rd_store_tests.erl:42: The variable __V can never match since previous clauses completely covered the type 'true' 5 | rd_store_tests.erl:45: The variable __V can never match since previous clauses completely covered the type 'true' 6 | rd_store_tests.erl:46: The variable __V can never match since previous clauses completely covered the type 'ok' 7 | resource_discovery_tests.erl:32: The variable __V can never match since previous clauses completely covered the type 'ok' 8 | resource_discovery_tests.erl:33: The variable __V can never match since previous clauses completely covered the type 'ok' 9 | resource_discovery_tests.erl:34: The variable __V can never match since previous clauses completely covered the type 'ok' 10 | resource_discovery_tests.erl:40: The variable __V can never match since previous clauses completely covered the type 'ok' 11 | resource_discovery_tests.erl:49: The variable __V can never match since previous clauses completely covered the type 'ok' 12 | resource_discovery_tests.erl:50: The variable __V can never match since previous clauses completely covered the type 'ok' 13 | resource_discovery_tests.erl:51: The variable __V can never match since previous clauses completely covered the type 'ok' 14 | resource_discovery_tests.erl:52: The variable __V can never match since previous clauses completely covered the type 'ok' 15 | resource_discovery_tests.erl:55: The variable __V can never match since previous clauses completely covered the type 'ok' 16 | resource_discovery_tests.erl:56: The variable __V can never match since previous clauses completely covered the type 'ok' 17 | 18 | -------------------------------------------------------------------------------- /etc/log4erl.conf: -------------------------------------------------------------------------------- 1 | logger { 2 | file_appender file{ 3 | dir = "log", 4 | level = info, 5 | file = "app_slogs", 6 | type = size, 7 | max = 100000, 8 | suffix = txt, 9 | rotation = 5, 10 | format = '[%L] %I %l%n' 11 | } 12 | 13 | %% Consloe appender with level set to warn 14 | console_appender cmd{ 15 | level = info, 16 | format = '[%L] %I %l%n' 17 | } 18 | } 19 | 20 | %% %% To send logs to email 21 | %% logger email{ 22 | %% smtp_appender email{ 23 | %% level=all, 24 | %% ip = "192.168.1.6", 25 | %% %port = 25, 26 | %% no_auth = true, 27 | %% %username = user, 28 | %% %password = pass, 29 | %% from = "admin@my_server", 30 | %% to = "notification@my_server", 31 | %% title = "System info", 32 | %% msg = "[%T %j] %L:%n%l%n" 33 | %% } 34 | %%} 35 | -------------------------------------------------------------------------------- /include/resource_discovery.hrl: -------------------------------------------------------------------------------- 1 | %%% Type Definitions 2 | -type resource_type() :: atom(). 3 | -type resource() :: term(). 4 | -type resource_tuple() :: {resource_type(), resource()}. 5 | 6 | 7 | -------------------------------------------------------------------------------- /overview.edoc: -------------------------------------------------------------------------------- 1 | @title The Resource Discovery Application 2 | @doc 3 | 4 |

5 | The Resource Discovery application allows you to set up smart Erlang clusters. Producers and consumers within an Erlang cluster can find eachother with no pre-configuration needed. For example lets say we have three services in a cluster of nodes: 6 |

7 | 8 |
    9 |
  • Video Display Agent
  • 10 |
  • Video Producer
  • 11 |
  • System Logger
  • 12 |
13 | 14 |

15 | The Display Agent must of course be able to find a Video Producer and both the Video Producer and the Display Agent must know about the system logger. Before understanding how all that is expressed in Resource Discovery we need to get some definitions out of the way. 16 |

17 | 18 |

Definitions

19 | 20 |

21 | Resource Discovery has 3 major types that you need to be concerned with. They are listed here. 22 |

23 |

24 | resource_tuple() = {resource_type(), resource()}. The type 25 | of a resource followed by the actual resource. Local 26 | resource tuples are communicated to other resource discovery 27 | instances. 28 |

29 |

30 | resource_type() = atom(). The name of a resource, how it is identified. For example 31 | a type of service that you have on the network may be identified by it's node name 32 | in which case you might have a resource type of 'my_service' of which there may be 33 | many node names representing resources such as {my_service, myservicenode@myhost}. 34 |

35 |

36 | resource() = term(). Either a concrete resource or a reference to one like a pid(). 37 |

38 | 39 |

Expressing Relationships

40 | 41 |

42 | Using these definitions we can describe the relationships that we have between our different services; the Video Display Agent, the Video Producer, and the System Logger. It is helpful to break things down into three categories. These are as follows: 43 |

44 | 45 |
    46 |
  • Local Resources Tuples
  • 47 |
  • Target Resource Types
  • 48 |
  • Cached Resource Tuples
  • 49 |
50 | 51 |

52 | Local Resources Tuples contain those resources that a running Erlang node has to share with the cluster. A Video Display Agent node could make a call like what is below to express what it has to share with the network. 53 |

54 | 55 | ``` 56 | resource_discovery:add_local_resource_tuples({video_producer, node()}). 57 | ''' 58 | 59 |

60 | A Video Producer node needs to know about the System Logger instance and about all instances of Video Display Agent nodes. To express this it could make a call like what is below to express this. 61 |

62 | 63 | ``` 64 | resource_discovery:add_target_resource_types([video_display, system_logger]). 65 | ''' 66 | 67 |

68 | This indicates that the Video Producer service wants to find out about all services in the cluster of resource types video_display and system_logger. 69 |

70 | 71 |

72 | Now we are set up for our system to obtain resource tuples from other nodes in the cluster and cache them locally. Cached Resource Tuples are those that have been discovered and are ready to be used. When each of the nodes comes up onto the network they will make one of the following calls: 73 |

74 | 75 | ``` 76 | resource_discovery:trade_resources(). 77 | ''' 78 | or 79 | ``` 80 | resource_discovery:sync_resources(). 81 | ''' 82 | 83 |

84 | This will communicate with all other nodes in the current network. When this function returns all Resource Tuples on the network that match the calling nodes Target Resource Types will be cached locally. Furthermore the local nodes Local Resource Tuples will be cached on all remote nodes that have specified their resource types as target resource types. trade_resources/0 accomplishes this asyncronously where as sync_resources is a blocking call which typically accomplishes a full sync before returning. After either of these calls have been made you can go about using the resources you have discovered. 85 |

86 | 87 |

88 | To use a resource from the resource cache Resource Discovery provides a number of useful functions. The most common of these is ``get_resource/1''. This will loop through resources in round robin fashion supplying a different resource each time it is called. There are also the rpc functions that act upon node based resources; these are rpc_call and rpc_multicall. There are other functions available for more control over the specific resources of a particular type you get. There are also functions provided for removing resources that have been found to be malfunctioning or simply not around anymore. This is up to the user of resource discovery to do at the application level. 89 |

90 | 91 |

Getting Notifications about New Resources

92 | 93 |

94 | Sometimes when resources become available in the cluster it is desirable for an application to be notified of their presence. If you want immediate notification of new Cached Resources you can subscribe to notifications by using: 95 |

96 | 97 | ``` 98 | resource_discovery:add_callback_modules([video_send, log_send]). 99 | ''' 100 | 101 |

102 | The above call will ensure that the exported function "resource_up/1" will be called upon the caching of a new resource within the modules "video_send" and "log_send". Those functions will be called each time this happens regardless of the resource and so the resource up function should use pattern matching like that pictured below to only concern itself with the resources it needs to act upon. 103 |

104 | 105 | ``` 106 | resource_up({system_logger, Instance}) -> 107 | ... do stuff ... 108 | resource_up(_DontCare) -> 109 | ok. 110 | ''' 111 | 112 |

Getting into an Erlang Cloud

113 | 114 |

115 | Getting into an Erlang node cluster is required as a minimal bootstrap for Resource Discovery. To accomplish initial contact by pinging remote nodes already in the desired cloud the following entries should be in your Erlang config file. 116 |

117 | 118 | ``` 119 | {resource_discovery, 120 | [ 121 | {contact_nodes, [node1@mynet.com, node2@mynet.com]} 122 | ] 123 | } 124 | ''' 125 | 126 |

127 | These entries instruct resource discovery to ping node1 and node2 in order to join an Erlang cloud. At least one of the pings must succeed in order for startup to succeed. Optionally all this can be overridden at the command line with the -contact_node flag: 128 |

129 | 130 | ``` 131 | erl ... -contact_node node3@mynet.com 132 | ''' 133 | 134 |

135 | The above flag set at startup would instruct Resource Discovery to ping node3 instead of those configured in the config file. In the future a plugin scheme will be added so that other methods of bootstrapping into a cloud may be added. An easy way to get up and running for a test is to fire up the first node pointing to itself and then additional nodes pointing to the first as in the following example: (remember that "Macintosh" should be replaced with your local sname suffix) 136 |

137 | 138 | ``` 139 | erl -sname a -contact_node a@Macintosh 140 | erl -sname b -contact_node a@Macintosh 141 | erl -sname c -contact_node a@Macintosh 142 | ''' 143 | 144 |

Overcomming network and resource failures with heartbeating

145 | 146 |

147 | At times resources fail and are perhaps taken out of the store of Cached Resource Tuples by an application when it discovers the resource is no longer around. At other times networks may fail and clusters become separated. One way of overcomming all of this is to setup heartbeats from Resource Discovery. Resource discovery can be configured to heartbeat at a specified interval. This means that it will both re-ping the configured contact nodes and will run a trade_resources/0 at the specified interval. To enable this place configuration similar to what is below in you Erlang config file. 148 |

149 | 150 | ``` 151 | {resource_discovery, 152 | [ 153 | {heartbeat_frequency, 60000} 154 | ] 155 | } 156 | ''' 157 | 158 |

159 | An entry like that above tells Resource Discovery to ping about every 60000 milliseconds plus or minus a randomization factor. An entry of 0 disables heartbeating all together and is the default if not configured. 160 |

161 | 162 |

163 | Enjoy! Send questions of comments to erlware-questions@erlware.org or erlware-dev@erlware.org. 164 |

165 | 166 | @author Martin Logan 167 | @copyright 2010 Erlware 168 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {lib_dirs,["deps"]}. 2 | {src_dirs, ["src", "test"]}. 3 | {excl_archive_filters, [".*"]}. 4 | {cover_enabled, true}. 5 | {erl_opts, [debug_info, fail_on_warning]}. 6 | 7 | {post_hooks, [{compile, "cp ./src/sys.config ./ebin/sys.config"}]}. 8 | 9 | -------------------------------------------------------------------------------- /sinan.cfg: -------------------------------------------------------------------------------- 1 | project : { 2 | name : resource_discovery 3 | vsn : "0.1.1.0" 4 | }, 5 | 6 | -------------------------------------------------------------------------------- /src/rd_core.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Martin Logan 3 | %%% @copyright 2008 Erlware 4 | %%% @doc 5 | %%% Cache and distribute resources. 6 | %%% @end 7 | %%%------------------------------------------------------------------- 8 | -module(rd_core). 9 | 10 | -behaviour(gen_server). 11 | 12 | %% API 13 | -export([ 14 | start_link/0, 15 | sync_resources/2, 16 | trade_resources/0, 17 | filter_resource_tuples_by_types/2, 18 | make_callbacks/1 19 | ]). 20 | 21 | % Fetch 22 | -export([ 23 | round_robin_get/1, 24 | all_of_type_get/1, 25 | get_resource_types/0, 26 | get_num_resource_types/0, 27 | get_num_resource/1, 28 | get_local_resource_tuples/0, 29 | get_target_resource_types/0, 30 | get_deleted_resource_tuples/0 31 | ]). 32 | 33 | % Store 34 | -export([ 35 | store_local_resource_tuples/1, 36 | store_callback_modules/1, 37 | store_target_resource_types/1, 38 | store_resource_tuples/1 39 | ]). 40 | 41 | % Delete 42 | -export([ 43 | delete_local_resource_tuple/1, 44 | delete_target_resource_type/1, 45 | delete_callback_module/1, 46 | delete_resource_tuple/1 47 | ]). 48 | 49 | %% gen_server callbacks 50 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 51 | terminate/2, code_change/3]). 52 | 53 | -include("../include/resource_discovery.hrl"). 54 | 55 | -define(SERVER, ?MODULE). 56 | 57 | -record(state, {}). 58 | 59 | %%%=================================================================== 60 | %%% API 61 | %%%=================================================================== 62 | 63 | %%-------------------------------------------------------------------- 64 | %% @doc 65 | %% Starts the server 66 | %% 67 | %% @spec start_link() -> {ok, Pid} | ignore | {error, Error} 68 | %% @end 69 | %%-------------------------------------------------------------------- 70 | start_link() -> 71 | gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). 72 | 73 | %%-------------------------------------------------------------------- 74 | %% @doc call each subscribed callback function for each new 75 | %% resource supplied. 76 | %% @end 77 | %%-------------------------------------------------------------------- 78 | -spec make_callbacks([resource_tuple()]) -> ok. 79 | make_callbacks(NewResources) -> 80 | lists:foreach( 81 | fun(Module) -> 82 | lists:foreach(fun(Resource) -> 83 | spawn(fun() -> Module:resource_up(Resource) end) end, 84 | NewResources) 85 | end, 86 | rd_store:get_callback_modules()). 87 | 88 | %%-------------------------------------------------------------------- 89 | %% @doc return a list of resources that have a resource_type() found in the target 90 | %% types list. 91 | %% @end 92 | %%-------------------------------------------------------------------- 93 | -spec filter_resource_tuples_by_types([resource_type()], [resource_tuple()]) -> [resource_tuple()]. 94 | filter_resource_tuples_by_types(TargetTypes, Resources) -> 95 | Fun = 96 | fun({Type, _Instance} = Resource, Acc) -> 97 | case lists:member(Type, TargetTypes) of 98 | true -> [Resource|Acc]; 99 | false -> Acc 100 | end 101 | end, 102 | lists:foldl(Fun, [], Resources). 103 | 104 | 105 | %%----------------------------------------------------------------------- 106 | %% @doc Store the callback modules for the local system. 107 | %% @end 108 | %%----------------------------------------------------------------------- 109 | -spec store_callback_modules([atom()]) -> no_return(). 110 | store_callback_modules([H|_] = Modules) when is_atom(H) -> 111 | gen_server:call(?SERVER, {store_callback_modules, Modules}). 112 | 113 | %%----------------------------------------------------------------------- 114 | %% @doc Store the target types for the local system. Store an "I Want" 115 | %% type. These are the types we wish to find among the node cluster. 116 | %% @end 117 | %%----------------------------------------------------------------------- 118 | -spec store_target_resource_types([atom()]) -> no_return(). 119 | store_target_resource_types([H|_] = TargetTypes) when is_atom(H) -> 120 | gen_server:call(?SERVER, {store_target_resource_types, TargetTypes}). 121 | 122 | %%----------------------------------------------------------------------- 123 | %% @doc Store the "I haves" or local_resources for resource discovery. 124 | %% @end 125 | %%----------------------------------------------------------------------- 126 | -spec store_local_resource_tuples([resource_tuple()]) -> ok. 127 | store_local_resource_tuples([{_,_}|_] = LocalResourceTuples) -> 128 | gen_server:call(?SERVER, {store_local_resource_tuples, LocalResourceTuples}). 129 | 130 | 131 | -spec store_resource_tuples([resource_tuple()]) -> ok. 132 | store_resource_tuples(Resource) -> 133 | gen_server:call(?SERVER, {store_resource_tuples, Resource}). 134 | 135 | %%----------------------------------------------------------------------- 136 | %% @doc Remove a callback module. 137 | %% @end 138 | %%----------------------------------------------------------------------- 139 | -spec delete_callback_module(atom()) -> true. 140 | delete_callback_module(CallBackModule) -> 141 | gen_server:call(?SERVER, {delete_callback_module, CallBackModule}). 142 | 143 | %%----------------------------------------------------------------------- 144 | %% @doc Remove a target type. 145 | %% @end 146 | %%----------------------------------------------------------------------- 147 | -spec delete_target_resource_type(atom()) -> true. 148 | delete_target_resource_type(TargetType) -> 149 | gen_server:call(?SERVER, {delete_target_resource_type, TargetType}). 150 | 151 | %%----------------------------------------------------------------------- 152 | %% @doc Remove a local resource. 153 | %% @end 154 | %%----------------------------------------------------------------------- 155 | -spec delete_local_resource_tuple(resource_tuple()) -> ok | {error, local_resource_not_found, resource_tuple()}. 156 | delete_local_resource_tuple(LocalResourceTuple) -> 157 | gen_server:call(?SERVER, {delete_local_resource_tuple, LocalResourceTuple}). 158 | 159 | %%----------------------------------------------------------------------- 160 | %% @doc Remove a resource. 161 | %% @end 162 | %%----------------------------------------------------------------------- 163 | -spec delete_resource_tuple(resource_tuple()) -> ok. 164 | delete_resource_tuple({_,_} = ResourceTuple) -> 165 | gen_server:call(?SERVER, {delete_resource_tuple, ResourceTuple}). 166 | 167 | %%-------------------------------------------------------------------- 168 | %% @doc inform an rd_core server of local resources and target types. 169 | %% This will prompt the remote servers to asyncronously send 170 | %% back remote resource information. 171 | %% @end 172 | %%-------------------------------------------------------------------- 173 | -spec trade_resources() -> ok. 174 | trade_resources() -> 175 | gen_server:cast(?SERVER, trade_resources). 176 | 177 | %%----------------------------------------------------------------------- 178 | %% @doc 179 | %% Gets resource of a particular type outputs and places it in last position. 180 | %% @end 181 | %%----------------------------------------------------------------------- 182 | -spec round_robin_get(resource_type()) -> {ok, resource()} | {error, not_found}. 183 | round_robin_get(Type) -> 184 | gen_server:call(?SERVER, {round_robin_get, Type}). 185 | 186 | %%-------------------------------------------------------------------- 187 | %% @doc 188 | %% Gets all cached resources of a given type 189 | %% @end 190 | %%-------------------------------------------------------------------- 191 | -spec all_of_type_get(resource_type()) -> [resource()]. 192 | all_of_type_get(Type) -> 193 | gen_server:call(?SERVER, {all_of_type_get, Type}). 194 | 195 | %%----------------------------------------------------------------------- 196 | %% @doc Gets resource of a particular type outputs and places it in last position. 197 | %% @end 198 | %%----------------------------------------------------------------------- 199 | %%-spec sync_resources(node(), {LocalResourceTuples, TargetTypes, DeletedTuples}) -> ok. 200 | sync_resources(Node, {LocalResourceTuples, TargetTypes, DeletedTuples}) -> 201 | error_logger:info_msg("synch resources for node: ~p", [Node]), 202 | {ok, FilteredRemotes} = gen_server:call({?SERVER, Node}, {sync_resources, {LocalResourceTuples, TargetTypes, DeletedTuples}}), 203 | rd_store:store_resource_tuples(FilteredRemotes), 204 | make_callbacks(FilteredRemotes), 205 | ok. 206 | 207 | %%-------------------------------------------------------------------- 208 | %% @doc 209 | %% Get cached resource types 210 | %% @end 211 | %%-------------------------------------------------------------------- 212 | -spec get_resource_types() -> [resource_type()]. 213 | get_resource_types() -> 214 | gen_server:call(?SERVER, get_resource_types). 215 | 216 | %%------------------------------------------------------------------------------ 217 | %% @doc Gets the number of resource types locally cached. 218 | %% @end 219 | %%------------------------------------------------------------------------------ 220 | -spec get_num_resource_types() -> integer(). 221 | get_num_resource_types() -> 222 | gen_server:call(?SERVER, get_num_resource_types). 223 | 224 | %%-------------------------------------------------------------------- 225 | %% @doc 226 | %% get number of cached resources of the given type 227 | %% @end 228 | %%-------------------------------------------------------------------- 229 | -spec get_num_resource(resource_type()) -> integer(). 230 | get_num_resource(Type) -> 231 | gen_server:call(?SERVER, {get_num_resource, Type}). 232 | 233 | -spec get_local_resource_tuples() -> [resource_tuple()]. 234 | get_local_resource_tuples() -> 235 | gen_server:call(?SERVER, get_local_resource_tuples). 236 | 237 | -spec get_target_resource_types() -> [atom()]. 238 | get_target_resource_types() -> 239 | gen_server:call(?SERVER, get_target_resource_types). 240 | 241 | %%-------------------------------------------------------------------- 242 | %% @doc 243 | %% get resources which we deleted 244 | %% @end 245 | %%-------------------------------------------------------------------- 246 | -spec get_deleted_resource_tuples() -> [resource_tuple()]. 247 | get_deleted_resource_tuples() -> 248 | gen_server:call(?SERVER, get_deleted_resource_tuples). 249 | 250 | 251 | %%%=================================================================== 252 | %%% gen_server callbacks 253 | %%%=================================================================== 254 | 255 | init([]) -> 256 | {ok, #state{}}. 257 | 258 | handle_call({sync_resources, {Remotes, RemoteTargetTypes, RemoteDeletedTuples}}, _From, State) -> 259 | error_logger:info_msg("sync_resources, got remotes: ~p deleted: ~p", [Remotes, RemoteDeletedTuples]), 260 | LocalResourceTuples = rd_store:get_local_resource_tuples(), 261 | TargetTypes = rd_store:get_target_resource_types(), 262 | FilteredRemotes = filter_resource_tuples_by_types(TargetTypes, Remotes), 263 | FilteredLocals = filter_resource_tuples_by_types(RemoteTargetTypes, LocalResourceTuples), 264 | error_logger:info_msg("sync_resources, storing filted remotes: ~p", [FilteredRemotes]), 265 | rd_store:store_resource_tuples(FilteredRemotes), 266 | [rd_store:delete_resource_tuple(DR) || DR <- RemoteDeletedTuples], 267 | make_callbacks(FilteredRemotes), 268 | {reply, {ok, FilteredLocals}, State}; 269 | handle_call({round_robin_get, Type}, _From, State) -> 270 | Reply = rd_store:round_robin_get(Type), 271 | {reply, Reply, State}; 272 | handle_call({all_of_type_get, Type}, _From, State) -> 273 | Reply = rd_store:get_resources(Type), 274 | {reply, Reply, State}; 275 | handle_call({store_callback_modules, Modules}, _From, State) -> 276 | rd_store:store_callback_modules(Modules), 277 | {reply, ok, State}; 278 | handle_call({store_target_resource_types, TargetTypes}, _From, State) -> 279 | Reply = rd_store:store_target_resource_types(TargetTypes), 280 | {reply, Reply, State}; 281 | handle_call({store_local_resource_tuples, LocalResourceTuples}, _From, State) -> 282 | Reply = rd_store:store_local_resource_tuples(LocalResourceTuples), 283 | {reply, Reply, State}; 284 | handle_call({store_resource_tuples, Resource}, _From, State) -> 285 | Reply = rd_store:store_resource_tuples(Resource), 286 | {reply, Reply, State}; 287 | handle_call({delete_callback_module, Module}, _From, State) -> 288 | rd_store:delete_callback_module(Module), 289 | {reply, ok, State}; 290 | handle_call({delete_target_resource_type, TargetType}, _From, State) -> 291 | Reply = rd_store:delete_target_resource_type(TargetType), 292 | {reply, Reply, State}; 293 | handle_call({delete_local_resource_tuple, LocalResourceTuple}, _From, State) -> 294 | Reply = rd_store:delete_local_resource_tuple(LocalResourceTuple), 295 | {reply, Reply, State}; 296 | handle_call({delete_resource_tuple, ResourceTuple}, _From, State) -> 297 | Reply = rd_store:delete_resource_tuple(ResourceTuple), 298 | {reply, Reply, State}; 299 | handle_call(get_resource_types, _From, State) -> 300 | Reply = rd_store:get_resource_types(), 301 | {reply, Reply, State}; 302 | handle_call(get_local_resource_tuples, _From, State) -> 303 | Reply = rd_store:get_local_resource_tuples(), 304 | {reply, Reply, State}; 305 | handle_call(get_target_resource_types, _From, State) -> 306 | Reply = rd_store:get_target_resource_types(), 307 | {reply, Reply, State}; 308 | handle_call(get_deleted_resource_tuples, _From, State) -> 309 | Reply = rd_store:get_deleted_resource_tuples(), 310 | {reply, Reply, State}; 311 | handle_call(get_num_resource_types, _From, State) -> 312 | Reply = rd_store:get_num_resource_types(), 313 | {reply, Reply, State}; 314 | handle_call({get_num_resource, Type}, _From, State) -> 315 | Reply = rd_store:get_num_resource(Type), 316 | {reply, Reply, State}. 317 | 318 | handle_cast(trade_resources, State) -> 319 | ResourceTuples = rd_store:get_local_resource_tuples(), 320 | DeletedTuples = rd_store:get_deleted_resource_tuples(), 321 | lists:foreach( 322 | fun(Node) -> 323 | gen_server:cast({?SERVER, Node}, 324 | {trade_resources, {node(), {ResourceTuples, DeletedTuples}}}) 325 | end, 326 | nodes(known)), 327 | rd_store:delete_deleted_resource_tuple(), 328 | {noreply, State}; 329 | handle_cast({trade_resources, {ReplyTo, {Remotes, RemoteDeletedTuples}}}, State) -> 330 | error_logger:info_msg("trade_resources, got remotes ~p: deleted: ~p", [Remotes, RemoteDeletedTuples]), 331 | Locals = rd_store:get_local_resource_tuples(), 332 | LocalsDeleted = rd_store:get_deleted_resource_tuples(), 333 | TargetTypes = rd_store:get_target_resource_types(), 334 | FilteredRemotes = filter_resource_tuples_by_types(TargetTypes, Remotes), 335 | error_logger:info_msg("got remotes and filtered ~p", [FilteredRemotes]), 336 | rd_store:store_resource_tuples(FilteredRemotes), 337 | error_logger:info_msg("trade_resources, deleting ~p", [RemoteDeletedTuples]), 338 | [rd_store:delete_resource_tuple(DR) || DR <- RemoteDeletedTuples], 339 | make_callbacks(FilteredRemotes), 340 | reply(ReplyTo, {Locals, LocalsDeleted}), 341 | {noreply, State}. 342 | 343 | handle_info(_Info, State) -> 344 | {noreply, State}. 345 | 346 | terminate(_Reason, _State) -> 347 | ok. 348 | 349 | code_change(_OldVsn, State, _Extra) -> 350 | {ok, State}. 351 | 352 | %%%=================================================================== 353 | %%% Internal functions 354 | %%%=================================================================== 355 | 356 | 357 | reply(noreply, {_LocalResources, _LocalsDeleted}) -> ok; 358 | reply(_ReplyTo, {[], []}) -> ok; 359 | reply(ReplyTo, {LocalResources,LocalsDeleted}) -> 360 | gen_server:cast({?SERVER, ReplyTo}, {trade_resources, {noreply, {LocalResources, LocalsDeleted}}}). 361 | -------------------------------------------------------------------------------- /src/rd_heartbeat.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% File : rd_heartbeat.erl 3 | %%% Author : Martin J. Logan 4 | %%% @doc This does an inform nodes at a specified time interval. 5 | %%% @end 6 | %%%------------------------------------------------------------------- 7 | -module(rd_heartbeat). 8 | 9 | -behaviour(gen_server). 10 | 11 | %% External exports 12 | -export([ start_link/1, start_link/0]). 13 | 14 | %% gen_server callbacks 15 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). 16 | 17 | -define(SERVER, ?MODULE). 18 | 19 | -record(state, {frequency}). 20 | 21 | %%==================================================================== 22 | %% External functions 23 | %%==================================================================== 24 | 25 | %%-------------------------------------------------------------------- 26 | %% @doc Starts the server 27 | %%
 
 28 | %% Expects:
 29 | %%  Frequency - The frequency of heartbeats in milliseconds.
 30 | %% 
31 | %% @end 32 | %%-------------------------------------------------------------------- 33 | -spec start_link(non_neg_integer()) -> {ok, pid()}. 34 | start_link(Frequency) -> 35 | gen_server:start_link({local, ?SERVER}, ?MODULE, [Frequency], []). 36 | 37 | %% @equiv start_link(0) 38 | start_link() -> 39 | %% The default value is 0 which indicates no heartbeating. 40 | {ok,Frequency} = rd_util:get_env(heartbeat_frequency, 60000), 41 | start_link(Frequency). 42 | 43 | %%==================================================================== 44 | %% Server functions 45 | %%==================================================================== 46 | 47 | %%-------------------------------------------------------------------- 48 | %% Function: init/1 49 | %% Description: Initiates the server 50 | %% Returns: {ok, State} | 51 | %% {ok, State, Timeout} | 52 | %% ignore | 53 | %% {stop, Reason} 54 | %%-------------------------------------------------------------------- 55 | init([Frequency]) -> 56 | ok = resource_discovery:contact_nodes(), 57 | {ok, #state{frequency = Frequency}, Frequency}. 58 | 59 | %%-------------------------------------------------------------------- 60 | %% Function: handle_call/3 61 | %% Description: Handling call messages 62 | %% Returns: {reply, Reply, State} | 63 | %% {reply, Reply, State, Timeout} | 64 | %% {noreply, State} | 65 | %% {noreply, State, Timeout} | 66 | %% {stop, Reason, Reply, State} | (terminate/2 is called) 67 | %% {stop, Reason, State} (terminate/2 is called) 68 | %%-------------------------------------------------------------------- 69 | handle_call(_Request, _From, State) -> 70 | {reply, ok, State}. 71 | 72 | %%-------------------------------------------------------------------- 73 | %% Function: handle_cast/2 74 | %% Description: Handling cast messages 75 | %% Returns: {noreply, State} | 76 | %% {noreply, State, Timeout} | 77 | %% {stop, Reason, State} (terminate/2 is called) 78 | %%-------------------------------------------------------------------- 79 | handle_cast(_Msg, State) -> 80 | {noreply, State}. 81 | 82 | %%-------------------------------------------------------------------- 83 | %% Function: handle_info/2 84 | %% Description: Handling all non call/cast messages 85 | %% Returns: {noreply, State} | 86 | %% {noreply, State, Timeout} | 87 | %% {stop, Reason, State} (terminate/2 is called) 88 | %%-------------------------------------------------------------------- 89 | handle_info(timeout, State = #state{frequency = 0}) -> 90 | {stop, normal, State}; 91 | handle_info(timeout, State = #state{frequency = Frequency}) -> 92 | resource_discovery:contact_nodes(), 93 | resource_discovery:trade_resources(), 94 | %% Wait for approximately the frequency with a random factor. 95 | {noreply, State, random:uniform(Frequency div 2) + (Frequency div 2) + Frequency div 3}. 96 | 97 | %%-------------------------------------------------------------------- 98 | %% Function: terminate/2 99 | %% Description: Shutdown the server 100 | %% Returns: any (ignored by gen_server) 101 | %%-------------------------------------------------------------------- 102 | terminate(Reason, _State) -> 103 | error_logger:info_msg("stoppping resource discovery hearbeat ~p", [Reason]), 104 | ok. 105 | 106 | %%-------------------------------------------------------------------- 107 | %% Func: code_change/3 108 | %% Purpose: Convert process state when code is changed 109 | %% Returns: {ok, NewState} 110 | %%-------------------------------------------------------------------- 111 | code_change(_OldVsn, State, _Extra) -> 112 | {ok, State}. 113 | 114 | -------------------------------------------------------------------------------- /src/rd_store.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% File : rd_store.erl 3 | %%% Author : Martin J. Logan 4 | %%% @doc The storage management functions for resource discovery 5 | %%% @end 6 | %%%------------------------------------------------------------------- 7 | -module(rd_store). 8 | 9 | %%-------------------------------------------------------------------- 10 | %% Include files 11 | %%-------------------------------------------------------------------- 12 | -include("../include/resource_discovery.hrl"). 13 | 14 | %%-------------------------------------------------------------------- 15 | %% External exports 16 | %%-------------------------------------------------------------------- 17 | 18 | % Create 19 | -export([ 20 | new/0, 21 | delete/0 22 | ]). 23 | 24 | % Lookup 25 | -export([ 26 | round_robin_get/1, 27 | get_resources/1, 28 | get_callback_modules/0, 29 | get_local_resource_tuples/0, 30 | get_deleted_resource_tuples/0, 31 | get_target_resource_types/0, 32 | get_num_resource_types/0, 33 | get_num_resource/1, 34 | get_resource_types/0 35 | ]). 36 | 37 | % Delete 38 | -export([ 39 | delete_local_resource_tuple/1, 40 | delete_target_resource_type/1, 41 | delete_callback_module/1, 42 | delete_resource_tuple/1, 43 | delete_deleted_resource_tuple/0 44 | ]). 45 | 46 | % Store 47 | -export([ 48 | store_local_resource_tuples/1, 49 | store_callback_modules/1, 50 | store_target_resource_types/1, 51 | store_resource_tuples/1, 52 | store_resource_tuple/1 53 | ]). 54 | 55 | %%-------------------------------------------------------------------- 56 | %% Macros 57 | %%-------------------------------------------------------------------- 58 | -define(LKVStore, rd_local_kv_store). 59 | -define(RS, rd_resource_store). 60 | %% temp store for deleted local resources, so we could remove them from remote nodes. 61 | -define(DL, rd_deleted_kv_store). 62 | 63 | %%==================================================================== 64 | %% External functions 65 | %%==================================================================== 66 | 67 | %%%--------------------------- 68 | %%% Local Type, Target type Storage 69 | %%%--------------------------- 70 | 71 | %%----------------------------------------------------------------------- 72 | %% @doc LPS stands for "Local Parameter Store". 73 | %% Initialises persistent storage. 74 | %% @end 75 | %%----------------------------------------------------------------------- 76 | -spec new() -> ok. 77 | new() -> 78 | ets:new(?RS, [named_table, public]), 79 | ets:new(?LKVStore, [named_table, public]), 80 | ok. 81 | 82 | %%-------------------------------------------------------------------- 83 | %% @doc 84 | %% Deletes storage 85 | %% @end 86 | %%-------------------------------------------------------------------- 87 | -spec delete() -> ok. 88 | delete() -> 89 | ets:delete(?RS), 90 | ets:delete(?LKVStore), 91 | ok. 92 | 93 | %%----------------------------------------------------------------------- 94 | %% @doc Store the callback modules for the local system. 95 | %% @end 96 | %%----------------------------------------------------------------------- 97 | -spec store_callback_modules([atom()]) -> ok. 98 | store_callback_modules([H|_] = Modules) when is_atom(H) -> 99 | ets:insert(?LKVStore, {callback_modules, lists:usort(get_callback_modules() ++ Modules)}), 100 | ok. 101 | 102 | %%----------------------------------------------------------------------- 103 | %% @doc Output the callback modules. 104 | %% @end 105 | %%----------------------------------------------------------------------- 106 | -spec get_callback_modules() -> [atom()]. 107 | get_callback_modules() -> 108 | case ets:lookup(?LKVStore, callback_modules) of 109 | [{callback_modules, CallBackModules}] -> CallBackModules; 110 | [] -> [] 111 | end. 112 | 113 | %%----------------------------------------------------------------------- 114 | %% @doc Remove a callback module. 115 | %% @end 116 | %%----------------------------------------------------------------------- 117 | -spec delete_callback_module(atom()) -> ok. 118 | delete_callback_module(CallBackModule) -> 119 | NewCallBackModules = lists:delete(CallBackModule, get_callback_modules()), 120 | ets:insert(?LKVStore, {callback_modules, NewCallBackModules}), 121 | ok. 122 | 123 | %%----------------------------------------------------------------------- 124 | %% @doc Store the target types for the local system. Store an "I Want" 125 | %% type. These are the types we wish to find among the node cluster. 126 | %% @end 127 | %%----------------------------------------------------------------------- 128 | -spec store_target_resource_types([atom()]) -> ok. 129 | store_target_resource_types([H|_] = TargetTypes) when is_atom(H) -> 130 | ets:insert(?LKVStore, {target_types, lists:usort(get_target_resource_types() ++ TargetTypes)}), 131 | ok. 132 | 133 | %%----------------------------------------------------------------------- 134 | %% @doc Output the target types. These are the resource types we wish 135 | %% to find within our node cluster. 136 | %% @end 137 | %%----------------------------------------------------------------------- 138 | -spec get_target_resource_types() -> [atom()]. 139 | get_target_resource_types() -> 140 | case ets:lookup(?LKVStore, target_types) of 141 | [{target_types, TargetTypes}] -> lists:usort(TargetTypes); 142 | [] -> [] 143 | end. 144 | 145 | %%----------------------------------------------------------------------- 146 | %% @doc Remove a target type. 147 | %% @end 148 | %%----------------------------------------------------------------------- 149 | -spec delete_target_resource_type(atom()) -> ok. 150 | delete_target_resource_type(TargetType) -> 151 | ets:insert(?LKVStore, {target_types, lists:delete(TargetType, get_target_resource_types())}), 152 | ok. 153 | 154 | %%----------------------------------------------------------------------- 155 | %% @doc Store the "I haves" or local_resources for resource discovery. 156 | %% @end 157 | %%----------------------------------------------------------------------- 158 | -spec store_local_resource_tuples([resource_tuple()]) -> ok. 159 | store_local_resource_tuples([{_,_}|_] = LocalResourceTuples) -> 160 | ets:insert(?LKVStore, {local_resources, lists:usort(get_local_resource_tuples() ++ LocalResourceTuples)}), 161 | ok. 162 | 163 | %%----------------------------------------------------------------------- 164 | %% @doc Output the local resources. 165 | %% @end 166 | %%----------------------------------------------------------------------- 167 | -spec get_local_resource_tuples() -> [resource_tuple()]. 168 | get_local_resource_tuples() -> 169 | case ets:lookup(?LKVStore, local_resources) of 170 | [{local_resources, LocalResources}] -> LocalResources; 171 | [] -> [] 172 | end. 173 | 174 | 175 | %%-------------------------------------------------------------------- 176 | %% @doc 177 | %% get resources which we deleted 178 | %% @end 179 | %%-------------------------------------------------------------------- 180 | -spec get_deleted_resource_tuples() -> [resource_tuple()]. 181 | get_deleted_resource_tuples() -> 182 | case ets:lookup(?LKVStore, deleted_resources) of 183 | [{deleted_resources, DeletedResources}] -> DeletedResources; 184 | [] -> [] 185 | end. 186 | 187 | 188 | %%----------------------------------------------------------------------- 189 | %% @doc Remove a local resource. 190 | %% @end 191 | %%----------------------------------------------------------------------- 192 | -spec delete_local_resource_tuple(resource_tuple()) -> ok | {error, local_resource_not_found, resource_tuple()}. 193 | delete_local_resource_tuple(LocalResource) -> 194 | %% first get local resource tuples 195 | LocalResources = get_local_resource_tuples(), 196 | %% only add to deleted cache if resource actually exist 197 | case lists:member(LocalResource, LocalResources) of 198 | true -> 199 | %% store resource to be deleted in delete_cache table, so it could be removed from remote nodes resource cache 200 | %% after syching 201 | ets:insert(?LKVStore, {deleted_resources, lists:usort(get_deleted_resource_tuples() ++ [LocalResource])}), 202 | %% now remove resource 203 | ets:insert(?LKVStore, {local_resources, lists:delete(LocalResource, LocalResources)}), 204 | ok; 205 | false -> {error, local_resource_not_found, LocalResource} 206 | end. 207 | 208 | %%%--------------------------- 209 | %%% Network Resource Storage 210 | %%%--------------------------- 211 | 212 | %%----------------------------------------------------------------------- 213 | %% @doc Outputs a list of all resources for a particular type. 214 | %% @end 215 | %%----------------------------------------------------------------------- 216 | -spec get_resources(resource_type()) -> [resource()]. 217 | get_resources(Type) -> 218 | case ets:lookup(?RS, Type) of 219 | [{Type, Resources}] -> Resources; 220 | [] -> [] 221 | end. 222 | 223 | %%----------------------------------------------------------------------- 224 | %% @doc Adds a new resource. 225 | %% @end 226 | %%----------------------------------------------------------------------- 227 | -spec store_resource_tuple(resource_tuple()) -> ok. 228 | store_resource_tuple({Type, Resource}) when is_atom(Type) -> 229 | ets:insert(?RS, {Type, lists:usort(get_resources(Type) ++ [Resource])}), 230 | ok. 231 | 232 | -spec store_resource_tuples([resource_tuple()]) -> ok. 233 | store_resource_tuples([]) -> ok; 234 | store_resource_tuples([{_,_}|_] = ResourceTuples) -> 235 | lists:foreach(fun(ResourceTuple) -> 236 | store_resource_tuple(ResourceTuple) 237 | end, ResourceTuples). 238 | 239 | %%----------------------------------------------------------------------- 240 | %% @doc Remove a single resource from cache. 241 | %% @end 242 | %%----------------------------------------------------------------------- 243 | -spec delete_resource_tuple(resource_tuple()) -> ok. 244 | delete_resource_tuple({Type, Resource}) -> 245 | ets:insert(?RS, {Type, lists:delete(Resource, get_resources(Type))}), 246 | %% delete Type if it doesnt have any resources 247 | case get_resources(Type) of 248 | [] -> ets:match_delete(?RS, {Type, '_'}); 249 | _ -> do_nothing 250 | end, 251 | ok. 252 | 253 | 254 | %%-------------------------------------------------------------------- 255 | %% @doc 256 | %% clear deleted resource cache. after the network was synched 257 | %% @end 258 | %%-------------------------------------------------------------------- 259 | -spec delete_deleted_resource_tuple() -> ok. 260 | delete_deleted_resource_tuple() -> 261 | ets:insert(?LKVStore, {deleted_resources, []}), 262 | ok. 263 | 264 | 265 | %%----------------------------------------------------------------------- 266 | %% @doc Gets resource of a particular type outputs and places it in last position. 267 | %% @end 268 | %%----------------------------------------------------------------------- 269 | -spec round_robin_get(resource_type()) -> {ok, resource()} | {error, not_found}. 270 | round_robin_get(Type) -> 271 | case get_resources(Type) of 272 | [Resource | RL] -> 273 | ets:insert(?RS, {Type, RL ++ [Resource]}), 274 | {ok, Resource}; 275 | [] -> {error, not_found} 276 | end. 277 | 278 | %%----------------------------------------------------------------------- 279 | %% @doc Outputs the number of resource types. 280 | %% @end 281 | %%----------------------------------------------------------------------- 282 | -spec get_num_resource_types() -> integer(). 283 | get_num_resource_types() -> 284 | length(ets:match(?RS, {'$1', '_'})). 285 | 286 | %%----------------------------------------------------------------------- 287 | %% @doc Outputs the number of resource types. 288 | %% @end 289 | %%----------------------------------------------------------------------- 290 | -spec get_num_resource(resource_type()) -> integer(). 291 | get_num_resource(Type) -> 292 | case ets:lookup(?RS, Type) of 293 | [{Type, Resources}] -> length(Resources); 294 | [] -> 0 295 | end. 296 | 297 | %%----------------------------------------------------------------------- 298 | %% @doc Outputs the types of resources. 299 | %% Return cached resource types 300 | %% @end 301 | %%----------------------------------------------------------------------- 302 | -spec get_resource_types() -> [resource_type()]. 303 | get_resource_types() -> 304 | lists:usort([E || [E] <- ets:match(?RS, {'$1', '_'})]). 305 | -------------------------------------------------------------------------------- /src/rd_sup.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% File : rd_sup.erl 3 | %%% Author : Martin J. Logan 4 | %%% @doc The super. 5 | %%%------------------------------------------------------------------- 6 | -module(rd_sup). 7 | 8 | -behaviour(supervisor). 9 | %%-------------------------------------------------------------------- 10 | %% Include files 11 | %%-------------------------------------------------------------------- 12 | 13 | %%-------------------------------------------------------------------- 14 | %% External exports 15 | %%-------------------------------------------------------------------- 16 | -export([ 17 | start_link/0 18 | ]). 19 | 20 | %%-------------------------------------------------------------------- 21 | %% Internal exports 22 | %%-------------------------------------------------------------------- 23 | -export([ 24 | init/1 25 | ]). 26 | 27 | %%-------------------------------------------------------------------- 28 | %% Macros 29 | %%-------------------------------------------------------------------- 30 | -define(SERVER, ?MODULE). 31 | -define(HEART, heart). 32 | 33 | %%-------------------------------------------------------------------- 34 | %% Records 35 | %%-------------------------------------------------------------------- 36 | 37 | %%==================================================================== 38 | %% External functions 39 | %%==================================================================== 40 | %%-------------------------------------------------------------------- 41 | %% @doc Starts the supervisor. 42 | %% @spec start_link() -> {ok, Pid} 43 | %% @end 44 | %%-------------------------------------------------------------------- 45 | start_link() -> 46 | supervisor:start_link({local, ?SERVER}, ?MODULE, []). 47 | 48 | %%==================================================================== 49 | %% Server functions 50 | %%==================================================================== 51 | %%-------------------------------------------------------------------- 52 | %% @hidden 53 | %% Func: init/1 54 | %% Returns: {ok, {SupFlags, [ChildSpec]}} | 55 | %% ignore | 56 | %% {error, Reason} 57 | %%-------------------------------------------------------------------- 58 | init([]) -> 59 | RestartStrategy = one_for_one, 60 | MaxRestarts = 1000, 61 | MaxTimeBetRestarts = 3600, 62 | SupFlags = {RestartStrategy, MaxRestarts, MaxTimeBetRestarts}, 63 | 64 | ChildSpecs = 65 | [ 66 | {rd_core, 67 | {rd_core, start_link, []}, 68 | permanent, 69 | 1000, 70 | worker, 71 | [rd_core]}, 72 | {rd_heartbeat, 73 | {rd_heartbeat, start_link, []}, 74 | transient, 75 | brutal_kill, 76 | worker, 77 | [rd_heartbeat]} 78 | ], 79 | 80 | {ok, {SupFlags, ChildSpecs}}. 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/rd_util.erl: -------------------------------------------------------------------------------- 1 | %%% @author Martin Logan 2 | %%% @copyright (C) 2010, Martin Logan 3 | %%% @doc 4 | %%% 5 | %%% @end 6 | %%% Created : 28 Oct 2010 by Martin Logan 7 | -module(rd_util). 8 | 9 | %% API 10 | -export([ 11 | get_env/2, 12 | do_until/2, 13 | sync_ping/2, 14 | poll_until/3 15 | ]). 16 | 17 | %%%=================================================================== 18 | %%% API 19 | %%%=================================================================== 20 | 21 | %% @doc Applies a fun to all elements of a list until getting a non false 22 | %% return value from the passed in fun. 23 | -spec do_until(term(), list()) -> term() | false. 24 | do_until(_F, []) -> 25 | false; 26 | do_until(F, [Last]) -> 27 | F(Last); 28 | do_until(F, [H|T]) -> 29 | case F(H) of 30 | false -> do_until(F, T); 31 | Return -> Return 32 | end. 33 | 34 | %% @doc Pings a node and returns only after the net kernal distributes the nodes. 35 | -spec sync_ping(node(), timeout()) -> pang | pong. 36 | sync_ping(Node, Timeout) -> 37 | error_logger:info_msg("pinging node: ~p", [Node]), 38 | case net_adm:ping(Node) of 39 | pong -> 40 | Resp = 41 | poll_until(fun() -> 42 | length(get_remote_nodes(Node)) == length(nodes()) 43 | end, 44 | 10, Timeout div 10), 45 | case Resp of 46 | true -> pong; 47 | false -> pang 48 | end; 49 | pang -> 50 | pang 51 | end. 52 | 53 | %% @doc This is a higher order function that allows for Iterations 54 | %% number of executions of Fun until false is not returned 55 | %% from the Fun pausing for PauseMS after each execution. 56 | %%
 57 | %% Variables:
 58 | %%  Fun - A fun to execute per iteration.
 59 | %%  Iterations - The maximum number of iterations to try getting Reply out of Fun.  
 60 | %%  PauseMS - The number of miliseconds to wait inbetween each iteration.
 61 | %%  Return - What ever the fun returns.
 62 | %% 
63 | -spec poll_until(term(), timeout(), timeout()) -> term() | false. 64 | poll_until(Fun, 0, _PauseMS) -> 65 | Fun(); 66 | poll_until(Fun, Iterations, PauseMS) -> 67 | case Fun() of 68 | false -> 69 | timer:sleep(PauseMS), 70 | case Iterations of 71 | infinity -> poll_until(Fun, Iterations, PauseMS); 72 | Iterations -> poll_until(Fun, Iterations - 1, PauseMS) 73 | end; 74 | Reply -> 75 | Reply 76 | end. 77 | 78 | %% @doc Get application data but provide a default. 79 | -spec get_env(atom(), term()) -> term(). 80 | get_env(Key, Default) -> 81 | case application:get_env(resource_discovery, Key) of 82 | {ok, Value} -> {ok, Value}; 83 | undefined -> {ok, Default} 84 | end. 85 | 86 | 87 | 88 | %%%=================================================================== 89 | %%% Internal functions 90 | %%%=================================================================== 91 | 92 | 93 | get_remote_nodes(Node) -> 94 | try 95 | Nodes = rpc:call(Node, erlang, nodes, []), 96 | error_logger:info_msg("contact node has ~p", [Nodes]), 97 | Nodes 98 | catch 99 | _C:E -> 100 | error_logger:info_msg("failed to connect to contact node ~p", [Node]), 101 | throw(E) 102 | end. 103 | 104 | -------------------------------------------------------------------------------- /src/resource_discovery.app.src: -------------------------------------------------------------------------------- 1 | %%% -*- mode:erlang -*- 2 | {application, resource_discovery, 3 | [ 4 | % A quick description of the application. 5 | {description, "Resource discovery & management"}, 6 | 7 | % The version of the applicaton 8 | {vsn, "0.2.1.0"}, 9 | 10 | % All modules used by the application. 11 | {modules, 12 | [ 13 | resource_discovery, 14 | rd_core, 15 | rd_heartbeat, 16 | rd_store, 17 | rd_util, 18 | rd_sup 19 | ]}, 20 | 21 | % All of the registered names the application uses. 22 | {registered, []}, 23 | 24 | % Applications that are to be started prior to this one. 25 | {applications, 26 | [ 27 | kernel, 28 | stdlib 29 | ]}, 30 | 31 | % configuration parameters 32 | {env, [{heartbeat_frequency, 60000}]}, 33 | 34 | % The M F A to start this application. 35 | {mod, {resource_discovery, 36 | [ 37 | {heartbeat_frequency, 60000} 38 | ]}} 39 | ] 40 | }. 41 | 42 | -------------------------------------------------------------------------------- /src/resource_discovery.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% File : resource_discovery.erl 3 | %%% Author : Martin J. Logan 4 | %%% @doc 5 | %%% Resource Discovery has 3 major types. They are listed here. 6 | %%% @type resource_tuple() = {resource_type(), resource()}. The type 7 | %%% of a resource followed by the actual resource. Local 8 | %%% resource tuples are communicated to other resource discovery 9 | %%% instances. 10 | %%% @type resource_type() = atom(). The name of a resource, how it is identified. For example 11 | %%% a type of service that you have on the network may be identified by it's node name 12 | %%% in which case you might have a resource type of 'my_service' of which there may be 13 | %%% many node names representing resources such as {my_service, myservicenode@myhost}. 14 | %%% @type resource() = term(). Either a concrete resource or a reference to one like a pid(). 15 | %%% @end 16 | %%%------------------------------------------------------------------- 17 | -module(resource_discovery). 18 | 19 | %%-------------------------------------------------------------------- 20 | %% External exports 21 | %%-------------------------------------------------------------------- 22 | 23 | % Standard exports. 24 | -export([ 25 | start/0, 26 | start/2, 27 | stop/0 28 | ]). 29 | 30 | % Add 31 | -export([ 32 | add_local_resource_tuples/1, 33 | add_local_resource_tuple/1, 34 | add_target_resource_types/1, 35 | add_target_resource_type/1, 36 | add_callback_modules/1, 37 | add_callback_module/1 38 | ]). 39 | % Get 40 | -export([ 41 | get_resource/1, 42 | get_resources/1, 43 | get_num_resource/1, 44 | get_resource_types/0, 45 | get_num_resource_types/0 46 | ]). 47 | 48 | % Delete 49 | -export([ 50 | delete_local_resource_tuple/1, 51 | delete_target_resource_type/1, 52 | delete_resource_tuple/1, 53 | delete_callback_module/1, 54 | delete_callback_modules/1 55 | ]). 56 | 57 | % Other 58 | -export([ 59 | trade_resources/0, 60 | sync_resources/1, 61 | sync_resources/0, 62 | contact_nodes/0, 63 | rpc_multicall/5, 64 | rpc_multicall/4, 65 | rpc_call/5, 66 | rpc_call/4 67 | ]). 68 | 69 | -include("../include/resource_discovery.hrl"). 70 | 71 | %%-------------------------------------------------------------------- 72 | %% Macros 73 | %%-------------------------------------------------------------------- 74 | -define(RD, rd_core). 75 | 76 | %%==================================================================== 77 | %% External functions 78 | %%==================================================================== 79 | 80 | start() -> 81 | application:start(resource_discovery). 82 | 83 | %%-------------------------------------------------------------------- 84 | %% @doc Starts the resource discovery application. 85 | %% @spec start(Type, StartArgs) -> {ok, Pid} | {ok, Pid, State} | {error, Reason} 86 | %% @end 87 | %%-------------------------------------------------------------------- 88 | start(_Type, _StartArgs) -> 89 | % Create the storage for the local parameters; i.e. LocalTypes 90 | % and TargetTypes. 91 | random:seed(now()), 92 | rd_store:new(), 93 | rd_sup:start_link(). 94 | 95 | stop() -> application:stop(resource_discovery). 96 | 97 | %%-------------------------------------------------------------------- 98 | %% @doc inform an rd_core server of local resources and target types. 99 | %% This will prompt the remote servers to asyncronously send 100 | %% back remote resource information. 101 | %% @end 102 | %%-------------------------------------------------------------------- 103 | -spec trade_resources() -> ok. 104 | trade_resources() -> rd_core:trade_resources(). 105 | 106 | %%----------------------------------------------------------------------- 107 | %% @doc Syncronizes resources between the caller and the node supplied. 108 | %% Like trade resources but this call blocks. 109 | %% @end 110 | %%----------------------------------------------------------------------- 111 | -spec sync_resources(timeout()) -> ok. 112 | sync_resources(Timeout) -> 113 | sync_locals(), 114 | Self = self(), 115 | Nodes = nodes(known), 116 | error_logger:info_msg("synching resources to nodes: ~p", [Nodes]), 117 | LocalResourceTuples = rd_core:get_local_resource_tuples(), 118 | DeletedTuples = rd_core:get_deleted_resource_tuples(), 119 | TargetTypes = rd_core:get_target_resource_types(), 120 | 121 | Pids = [spawn(fun() -> 122 | Self ! {'\$sync_resources\$', self(), 123 | (catch rd_core:sync_resources(Node, {LocalResourceTuples, TargetTypes, DeletedTuples}))} 124 | end) 125 | || Node <- Nodes], 126 | %% resources are synched so remove deleted tuples cache 127 | rd_store:delete_deleted_resource_tuple(), 128 | get_responses(Pids, Timeout). 129 | 130 | -spec sync_resources() -> ok. 131 | sync_resources() -> sync_resources(10000). 132 | 133 | sync_locals() -> 134 | LocalResourceTuples = rd_core:get_local_resource_tuples(), 135 | TargetTypes = rd_core:get_target_resource_types(), 136 | FilteredLocals = rd_core:filter_resource_tuples_by_types(TargetTypes, LocalResourceTuples), 137 | rd_core:store_resource_tuples(FilteredLocals), 138 | rd_core:make_callbacks(FilteredLocals). 139 | 140 | get_responses([], _Timeout) -> ok; 141 | get_responses(Pids, Timeout) -> 142 | %% XXX TODO fix the timeout by subracting elapsed time. 143 | %% XXX TODO perhaps use the response. 144 | receive 145 | {'\$sync_resources\$', Pid, _Resp} -> 146 | NewPids = lists:delete(Pid, Pids), 147 | get_responses(NewPids, Timeout) 148 | after 149 | Timeout -> {error, timeout} 150 | end. 151 | 152 | %%------------------------------------------------------------------------------ 153 | %% @doc Adds to the list of target types. Target types are the types 154 | %% of resources that this instance of resource_discovery will cache following 155 | %% a notification of such a resource from a resource_discovery instance. 156 | %% This includes the local instance. 157 | %% @end 158 | %%------------------------------------------------------------------------------ 159 | -spec add_target_resource_types([resource_type()]) -> ok. 160 | add_target_resource_types([H|_] = TargetTypes) when is_atom(H) -> 161 | rd_core:store_target_resource_types(TargetTypes). 162 | 163 | -spec add_target_resource_type(resource_type()) -> ok. 164 | add_target_resource_type(TargetType) when is_atom(TargetType) -> 165 | add_target_resource_types([TargetType]). 166 | 167 | %%------------------------------------------------------------------------------ 168 | %% @doc Adds to the list of local resource tuples. 169 | %% @end 170 | %%------------------------------------------------------------------------------ 171 | -spec add_local_resource_tuples([resource_tuple()]) -> ok. 172 | add_local_resource_tuples([{T,_}|_] = LocalResourceTuples) when is_atom(T) -> 173 | rd_core:store_local_resource_tuples(LocalResourceTuples). 174 | 175 | -spec add_local_resource_tuple(resource_tuple()) -> ok. 176 | add_local_resource_tuple({T,_} = LocalResourceTuple) when is_atom(T) -> 177 | add_local_resource_tuples([LocalResourceTuple]). 178 | 179 | %%------------------------------------------------------------------------------ 180 | %% @doc Add a callback module or modules to the list of callbacks to be 181 | %% called upon new resources entering the system. 182 | %% @end 183 | %%------------------------------------------------------------------------------ 184 | -spec add_callback_modules([atom()]) -> ok. 185 | add_callback_modules([H|_] = Modules) when is_atom(H) -> 186 | rd_core:store_callback_modules(Modules). 187 | 188 | -spec add_callback_module(atom()) -> ok. 189 | add_callback_module(Module) when is_atom(Module) -> 190 | add_callback_modules([Module]). 191 | 192 | %%------------------------------------------------------------------------------ 193 | %% @doc Replies with the cached resource. Round robins though the resources 194 | %% cached. 195 | %% @end 196 | %%------------------------------------------------------------------------------ 197 | -spec get_resource(resource_type()) -> {ok, resource()} | {error, not_found}. 198 | get_resource(Type) when is_atom(Type) -> 199 | rd_core:round_robin_get(Type). 200 | 201 | %%------------------------------------------------------------------------------ 202 | %% @doc Returns ALL cached resources for a particular type. 203 | %% @end 204 | %%------------------------------------------------------------------------------ 205 | -spec get_resources(resource_type()) -> [resource()]. 206 | get_resources(Type) -> 207 | rd_core:all_of_type_get(Type). 208 | 209 | %%------------------------------------------------------------------------------ 210 | %% @doc Gets a list of the types that have resources that have been cached. 211 | %% @end 212 | %%------------------------------------------------------------------------------ 213 | -spec get_resource_types() -> [resource_type()]. 214 | get_resource_types() -> 215 | rd_core:get_resource_types(). 216 | 217 | %%------------------------------------------------------------------------------ 218 | %% @doc Removes a cached resource from the resource pool. Only returns after the 219 | %% resource has been deleted. 220 | %% @end 221 | %%------------------------------------------------------------------------------ 222 | -spec delete_resource_tuple(resource_tuple()) -> ok. 223 | delete_resource_tuple(ResourceTuple = {_,_}) -> 224 | rd_core:delete_resource_tuple(ResourceTuple). 225 | 226 | %%------------------------------------------------------------------------------ 227 | %% @doc Remove a target type and all associated resources. 228 | %% @end 229 | %%------------------------------------------------------------------------------ 230 | -spec delete_target_resource_type(resource_type()) -> true. 231 | delete_target_resource_type(Type) -> 232 | rd_core:delete_target_resource_type(Type). 233 | 234 | %%------------------------------------------------------------------------------ 235 | %% @doc Remove a local resource. The resource will no longer be available for 236 | %% other nodes to discover once this call returns. 237 | %% @end 238 | %%------------------------------------------------------------------------------ 239 | -spec delete_local_resource_tuple(resource_tuple()) -> ok | {error, local_resource_not_found, resource_tuple()}. 240 | delete_local_resource_tuple(LocalResourceTuple) -> 241 | rd_core:delete_local_resource_tuple(LocalResourceTuple). 242 | 243 | %%-------------------------------------------------------------------- 244 | %% @doc 245 | %% Delete callback modules 246 | %% @end 247 | %%-------------------------------------------------------------------- 248 | -spec delete_callback_modules([atom()]) -> ok. 249 | delete_callback_modules([H|_] = Modules) when is_atom(H) -> 250 | [rd_core:delete_callback_module(Module) || Module <- Modules], 251 | ok. 252 | 253 | %%-------------------------------------------------------------------- 254 | %% @doc 255 | %% Delete callback module 256 | %% @end 257 | %%-------------------------------------------------------------------- 258 | -spec delete_callback_module(atom()) -> ok. 259 | delete_callback_module(Module) when is_atom(Module) -> 260 | delete_callback_modules([Module]). 261 | 262 | %%------------------------------------------------------------------------------ 263 | %% @doc Gets the number of resource types locally cached. 264 | %% @end 265 | %%------------------------------------------------------------------------------ 266 | -spec get_num_resource_types() -> integer(). 267 | get_num_resource_types() -> 268 | rd_core:get_num_resource_types(). 269 | 270 | %%------------------------------------------------------------------------------ 271 | %% @doc Counts the cached instances of a particular resource type. 272 | %% @end 273 | %%------------------------------------------------------------------------------ 274 | -spec get_num_resource(resource_type()) -> integer(). 275 | get_num_resource(Type) -> 276 | rd_core:get_num_resource(Type). 277 | 278 | %%------------------------------------------------------------------------------ 279 | %% @doc Contacts resource discoveries initial contact node. 280 | %% 281 | %% The initial contact node is specified in configuration with: 282 | %% 283 | %% {contact_nodes, [NodeName]} 284 | %% 285 | %% The config can be overridden by specifying a contact node at the command line 286 | %% like so: 287 | %% 288 | %% -contact_node foo@bar.com 289 | %% 290 | %% 291 | %% @spec contact_nodes(Timeout) -> ok | {error, bad_contact_node} | {error, no_contact_node} 292 | %% where 293 | %% Timeout = Milliseconds::integer() 294 | %% @end 295 | %%------------------------------------------------------------------------------ 296 | contact_nodes(Timeout) -> 297 | {ok, ContactNodes} = 298 | case lists:keysearch(contact_node, 1, init:get_arguments()) of 299 | {value, {contact_node, [I_ContactNode]}} -> 300 | application:set_env(resource_discovery, contact_nodes, [I_ContactNode]), 301 | {ok, [list_to_atom(I_ContactNode)]}; 302 | _ -> rd_util:get_env(contact_nodes, [node()]) 303 | end, 304 | ping_contact_nodes(ContactNodes, Timeout). 305 | 306 | %% @spec contact_nodes() -> pong | pang | no_contact_node 307 | %% @equiv contact_nodes(10000) 308 | contact_nodes() -> 309 | contact_nodes(10000). 310 | 311 | ping_contact_nodes([], _Timeout) -> 312 | error_logger:info_msg("No contact node specified. Potentially running in a standalone node", []), 313 | {error, no_contact_node}; 314 | ping_contact_nodes(Nodes, Timeout) -> 315 | Reply = rd_util:do_until(fun(Node) -> 316 | case rd_util:sync_ping(Node, Timeout) of 317 | pong -> true; 318 | pang -> 319 | error_logger:info_msg("ping contact node at ~p failed", [Node]), 320 | false 321 | end 322 | end, 323 | Nodes), 324 | case Reply of 325 | false -> {error, bad_contact_node}; 326 | true -> ok 327 | end. 328 | 329 | %%------------------------------------------------------------------------------ 330 | %% @doc Execute an rpc on a cached resource. If the result of the rpc is {badrpc, reason} the 331 | %% resource is deleted and the next resource is tried, else the result is 332 | %% returned to the user. 333 | %%
334 | %% Varibles:
335 | %%  Type - The resource type to get from resource discovery.
336 | %% 
337 | %% @end 338 | %%------------------------------------------------------------------------------ 339 | -spec rpc_call(resource_type(), atom(), atom(), [term()], timeout()) -> term() | {error, not_found}. 340 | rpc_call(Type, Module, Function, Args, Timeout) -> 341 | case get_resource(Type) of 342 | {ok, Resource} -> 343 | error_logger:info_msg("got a resource ~p", [Resource]), 344 | case rpc:call(Resource, Module, Function, Args, Timeout) of 345 | {badrpc, Reason} -> 346 | error_logger:info_msg("got a badrpc ~p", [Reason]), 347 | delete_resource_tuple({Type, Resource}), 348 | rpc_call(Type, Module, Function, Args, Timeout); 349 | Reply -> 350 | error_logger:info_msg("result of rpc was ~p", [Reply]), 351 | Reply 352 | end; 353 | {error, not_found} -> {error, not_found} 354 | end. 355 | 356 | -spec rpc_call(resource_type(), atom(), atom(), [term()]) -> term() | {error, no_resources}. 357 | rpc_call(Type, Module, Function, Args) -> 358 | rpc_call(Type, Module, Function, Args, 60000). 359 | 360 | %%------------------------------------------------------------------------------ 361 | %% @doc Execute an rpc on a cached resource. Any bad nodes are deleted. 362 | %% resource is deleted and the next resource is tried, else the result is 363 | %% returned to the user. 364 | %% @end 365 | %%------------------------------------------------------------------------------ 366 | -spec rpc_multicall(resource_type(), atom(), atom(), [term()], timeout()) -> {term(), [node()]} | {error, no_resources}. 367 | rpc_multicall(Type, Module, Function, Args, Timeout) -> 368 | case get_resources(Type) of 369 | [] -> {error, no_resources}; 370 | Resources -> 371 | error_logger:info_msg("got resources ~p", [Resources]), 372 | {Resl, BadNodes} = rpc:multicall(Resources, Module, Function, Args, Timeout), 373 | [delete_resource_tuple({Type, BadNode}) || BadNode <- BadNodes], 374 | {Resl, BadNodes} 375 | end. 376 | 377 | -spec rpc_multicall(resource_type(), atom(), atom(), [term()]) -> {term(), [node()]} | {error, no_resources}. 378 | rpc_multicall(Type, Module, Function, Args) -> 379 | rpc_multicall(Type, Module, Function, Args, 60000). 380 | -------------------------------------------------------------------------------- /src/sys.config: -------------------------------------------------------------------------------- 1 | %%% -*- mode:erlang -*- 2 | %%% Warning - this config file *must* end with 3 | 4 | [ 5 | {resource_discovery, 6 | [ 7 | {contact_nodes, ['rd@127.0.0.1']}, 8 | {heartbeat_frequency, 60000}, 9 | {log4erl_config, "etc/log4erl.conf"} 10 | ]} 11 | ]. 12 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cd `dirname $0` 3 | exec erl -pa $PWD/ebin -pa $PWD/deps/*/ebin -name rd@127.0.0.1 -setcookie rd -s resource_discovery start -config ebin/sys $@ 4 | -------------------------------------------------------------------------------- /test/rd_store_tests.erl: -------------------------------------------------------------------------------- 1 | -module(rd_store_tests). 2 | -compile([export_all]). 3 | 4 | -include_lib("eunit/include/eunit.hrl"). 5 | -define(TEST_VALUES, [{a, a}, {a, b}, {b, a}]). 6 | 7 | rd_store_local_resource_test_() -> 8 | {setup, local, 9 | fun rd_store:new/0, 10 | fun(_Pid) -> rd_store:delete() end, 11 | fun(_P) -> 12 | {inorder, 13 | [ 14 | %% add local resources 15 | ?_assertEqual(ok, rd_store:store_local_resource_tuples(?TEST_VALUES)), 16 | %% check that we get the same resources from storage 17 | ?_assertEqual(?TEST_VALUES, rd_store:get_local_resource_tuples()), 18 | %% test for dublicate values 19 | ?_assertEqual(ok, rd_store:store_local_resource_tuples([{a,a},{a,b}])), 20 | %% shouln't have new resources after dublicate were attempted to add 21 | ?_assertEqual(?TEST_VALUES, rd_store:get_local_resource_tuples()), 22 | %% remove resource 23 | ?_assertEqual(ok, rd_store:delete_local_resource_tuple({a, a})), 24 | ?_assertMatch([{a, b}, {b, a}], rd_store:get_local_resource_tuples()) 25 | ]} 26 | end }. 27 | 28 | rd_store_target_resource_test_() -> 29 | {setup, local, 30 | fun rd_store:new/0, 31 | fun(_Pid) -> rd_store:delete() end, 32 | fun(_P) -> 33 | {inorder, 34 | [ 35 | ?_assertEqual(ok, rd_store:store_target_resource_types([c, d, e])), 36 | ?_assertMatch([c,d,e], rd_store:get_target_resource_types()), 37 | ?_assertMatch(ok, rd_store:delete_target_resource_type(c)), 38 | ?_assertMatch([d,e], rd_store:get_target_resource_types()) 39 | ]} 40 | end }. 41 | 42 | 43 | rd_store_resource_test_() -> 44 | {setup, local, 45 | fun rd_store:new/0, 46 | fun(_Pid) -> rd_store:delete() end, 47 | fun(_P) -> 48 | {inorder, 49 | [ 50 | ?_assertEqual(ok, rd_store:store_resource_tuple({c, c1})), 51 | ?_assertEqual(ok, rd_store:store_resource_tuples([{d, d1},{d, d2}, {e, e1}, {e, e2}, {b, b1}])), 52 | ?_assertMatch(2, rd_store:get_num_resource(d)), 53 | ?_assertMatch(4, rd_store:get_num_resource_types()), 54 | ?_assertMatch([b1], rd_store:get_resources(b)), 55 | ?_assertMatch([d1, d2], rd_store:get_resources(d)), 56 | ?_assertMatch([e1, e2], rd_store:get_resources(e)) 57 | ]} 58 | end }. 59 | 60 | rd_store_callback_test_() -> 61 | {setup, local, 62 | fun rd_store:new/0, 63 | fun(_Pid) -> rd_store:delete() end, 64 | fun(_P) -> 65 | {inorder, 66 | [ 67 | ?_assertEqual(ok, rd_store:store_callback_modules([a, b])), 68 | ?_assertEqual([a,b], rd_store:get_callback_modules()), 69 | ?_assertEqual(ok, rd_store:delete_callback_module(a)), 70 | ?_assertEqual([b], rd_store:get_callback_modules()) 71 | ]} 72 | end }. 73 | -------------------------------------------------------------------------------- /test/resource_discovery_tests.erl: -------------------------------------------------------------------------------- 1 | -module(resource_discovery_tests). 2 | -compile([export_all]). 3 | 4 | -export([resource_up/1, test_adder/2]). 5 | 6 | -include_lib("eunit/include/eunit.hrl"). 7 | 8 | -define(RESOURCE_TUPLES, [{a, a}, {a, b}, {a, b}, {b, a}]). 9 | 10 | %% T = [{a, a}, {a, b}, {a, b}, {b, a}]. 11 | %% resource_discovery:add_local_resource_tuples(T). 12 | %% resource_discovery:add_target_resource_types([b]). 13 | %% resource_discovery:get_resources(b). 14 | 15 | process_test_() -> 16 | {setup, 17 | fun start_process/0, 18 | fun stop_process/1, 19 | fun run/1}. 20 | 21 | start_process() -> 22 | resource_discovery:start(). 23 | stop_process(_P) -> 24 | resource_discovery:stop(), 25 | ok. 26 | 27 | run(_P) -> 28 | {inorder, 29 | [ 30 | %% add local and target resources 31 | ?_assertMatch(ok, resource_discovery:add_local_resource_tuples(?RESOURCE_TUPLES)), 32 | ?_assertMatch(ok, resource_discovery:add_target_resource_type(b)), 33 | ?_assertMatch(ok, resource_discovery:sync_resources()), 34 | %% check that target resource is avilable 35 | ?_assertMatch([a], resource_discovery:get_resources(b)), 36 | ?_assertMatch([b], resource_discovery:get_resource_types()), 37 | %% delete local resource, the resource should dissapear from remote caches after sych 38 | ?_assertMatch(ok, resource_discovery:delete_local_resource_tuple({b,a})), 39 | %% try to remove non existing resource 40 | ?_assertMatch({error, local_resource_not_found, {b,a1}}, resource_discovery:delete_local_resource_tuple({b,a1})), 41 | ?_assertMatch(ok, resource_discovery:sync_resources()), 42 | %% check that it is not avialable to network anymore 43 | ?_assertMatch([], resource_discovery:get_resources(b)), 44 | ?_assertMatch([], resource_discovery:get_resources(a)), 45 | ?_assertMatch({error, not_found}, resource_discovery:get_resource(b)), 46 | %% check that resource types with no resources is gone 47 | ?_assertMatch([], resource_discovery:get_resource_types()), 48 | %% add new target resources in "I want" list 49 | ?_assertMatch(ok, resource_discovery:add_target_resource_types([e,f,n])), 50 | ?_assertMatch(ok, resource_discovery:sync_resources()), 51 | ?_assertMatch(ok, resource_discovery:add_local_resource_tuples([{e,e1}, {e,e2}, {e,e3}, {f, f1}])), 52 | ?_assertMatch(ok, resource_discovery:add_local_resource_tuple({f, f1})), 53 | ?_assertMatch(ok, resource_discovery:trade_resources()), 54 | ?_assertMatch([e, f], resource_discovery:get_resource_types()), 55 | ?_assertEqual(2, resource_discovery:get_num_resource_types()), 56 | ?_assertEqual(3, resource_discovery:get_num_resource(e)), 57 | %% number of non-existing resource 58 | ?_assertEqual(0, resource_discovery:get_num_resource(k)), 59 | ?_assertMatch(ok, resource_discovery:add_local_resource_tuples([{b,a}])), 60 | ?_assertMatch(ok, resource_discovery:sync_resources()), 61 | ?_assertMatch([a], resource_discovery:get_resources(b)), 62 | ?_assertMatch({ok, a}, resource_discovery:get_resource(b)), 63 | ?_assertMatch([e1, e2, e3], resource_discovery:get_resources(e)), 64 | %% delete resource 65 | ?_assertMatch(ok,resource_discovery:delete_resource_tuple({b,a})), 66 | %% shouldn't exist in local cache 67 | ?_assertMatch({error, not_found}, resource_discovery:get_resource(b)), 68 | ?_assertMatch(ok, resource_discovery:trade_resources()), 69 | %% ... but deleted resource will reappear after syching 70 | ?_assertMatch({ok, a}, resource_discovery:get_resource(b)), 71 | %% need to delete target resource type first, then delete resource from cache 72 | ?_assertMatch(ok, resource_discovery:delete_target_resource_type(b)), 73 | ?_assertMatch(ok,resource_discovery:delete_resource_tuple({b,a})), 74 | ?_assertMatch([e, f], resource_discovery:get_resource_types()), 75 | ?_assertMatch(ok, resource_discovery:trade_resources()), 76 | %% now it should be gone 77 | ?_assertMatch({error, not_found}, resource_discovery:get_resource(b)) 78 | ]}. 79 | 80 | 81 | 82 | %% test call notifications 83 | %% we setup a loop to hold a state, callback will fire up resource_up/1 func, which will set a state of 84 | %% a loop process, so we can verify this state in unit test. 85 | 86 | -record(state, {value = false}). 87 | rd_notification_test_() -> 88 | {setup, 89 | fun start_notify_loop/0, 90 | fun(Pid) -> stop_notify_loop(Pid) end, 91 | fun run_notify/1}. 92 | 93 | start_notify_loop() -> 94 | resource_discovery:start(), 95 | Pid = spawn(fun() -> loop(#state{}) end), 96 | register(notify_loop_process, Pid), 97 | Pid. 98 | 99 | stop_notify_loop(Pid) -> 100 | resource_discovery:stop(), 101 | unregister(notify_loop_process), 102 | Pid ! stop, 103 | ok. 104 | 105 | run_notify(_Pid) -> 106 | {inorder, 107 | [ 108 | %% add callback module, local and target resources 109 | ?_assertMatch(ok, resource_discovery:add_callback_module(?MODULE)), 110 | ?_assertMatch(ok, resource_discovery:add_target_resource_types([e,f,n])), 111 | ?_assertMatch(ok, resource_discovery:add_local_resource_tuples([{e,e1}, {f, f1}])), 112 | %% before synch, there is not notification 113 | ?_assertEqual(false, get_state()), 114 | ?_assertMatch(ok, resource_discovery:sync_resources()), 115 | %% after synch, there should be a notification 116 | ?_assertEqual(true, get_state()), 117 | %% now remove the cached resource 118 | ?_assertMatch(ok,resource_discovery:delete_resource_tuple({e,e1})), 119 | %% verify that resource is gone 120 | ?_assertMatch({error, not_found}, resource_discovery:get_resource(e)), 121 | %% remove notification module and synch 122 | ?_assertMatch(ok, resource_discovery:delete_callback_module(?MODULE)), 123 | %% reset the state of loop process 124 | ?_assertMatch(ok, set_state(false)), 125 | ?_assertMatch(ok, resource_discovery:sync_resources()), 126 | %% verify that notification wasn't sent 127 | ?_assertEqual(false, get_state()), 128 | %% verify that resource was still cached 129 | ?_assertMatch({ok,e1}, resource_discovery:get_resource(e)) 130 | ]}. 131 | 132 | 133 | %% function which is called when event happens 134 | %% for test we only care when resource 'e' becomes available 135 | resource_up({e, R}) -> 136 | error_logger:info_msg("resource is up ~p: ~p", [e, R]), 137 | %% callback happend, set state in loop process, so it could be verified in test 138 | set_state(true); 139 | resource_up(Other) -> 140 | error_logger:info_msg("resource is up, dont' care: ~p", [Other]). 141 | 142 | get_state() -> 143 | notify_loop_process ! {self(), get}, 144 | receive 145 | Value -> Value 146 | end. 147 | 148 | set_state(Value) -> 149 | notify_loop_process ! {self(), {set, Value}}, 150 | ok. 151 | 152 | loop(State) -> 153 | receive 154 | {_From, {set, Value}} -> 155 | loop(State#state{value = Value}); 156 | {From, get} -> 157 | From ! State#state.value, 158 | %% reset state back in initial state 159 | loop(State#state{value=false}); 160 | stop -> void 161 | end. 162 | 163 | 164 | %% rpc_call test 165 | rd_rpc_call_test_() -> 166 | {setup, 167 | fun start_adder_loop/0, 168 | fun stop_adder_loop/1, 169 | fun rpc_run/1}. 170 | 171 | start_adder_loop() -> 172 | resource_discovery:start(). 173 | 174 | stop_adder_loop(_P) -> 175 | resource_discovery:stop(). 176 | 177 | %% test function to be called by rpc call 178 | test_adder(A, B) when is_integer(A), is_integer(B) -> 179 | A + B. 180 | 181 | rpc_run(_Pid) -> 182 | Node = node(), 183 | {inorder, 184 | [ 185 | %% add local and target resources 186 | ?_assertMatch(ok, resource_discovery:add_local_resource_tuple({node, Node})), 187 | ?_assertMatch(ok, resource_discovery:add_local_resource_tuple({node, 'not_existing@nohost'})), 188 | ?_assertMatch(ok, resource_discovery:add_target_resource_type(node)), 189 | ?_assertMatch(ok, resource_discovery:sync_resources()), 190 | %% add bad node as resource 191 | ?_assertMatch(['not_existing@nohost', Node], resource_discovery:get_resources(node)), 192 | ?_assertMatch(5, resource_discovery:rpc_call(node, ?MODULE, test_adder, [1,4])), 193 | %% bad resource should be deleted now, we should only get live node 194 | ?_assertMatch({ok, Node}, resource_discovery:get_resource(node)), 195 | %% try using unknown_resource 196 | ?_assertMatch({error, not_found}, resource_discovery:rpc_call(unknown_resource, ?MODULE, test_adder, [1,4])), 197 | %% synch up so bad node is loaded again 198 | ?_assertMatch(ok, resource_discovery:sync_resources()), 199 | %% run multicall 200 | ?_assertMatch({[300],[not_existing@nohost]}, resource_discovery:rpc_multicall(node, ?MODULE, test_adder, [100,200])), 201 | ?_assertMatch({error, no_resources}, resource_discovery:rpc_multicall(unknown_resource, ?MODULE, test_adder, [400,200])) 202 | ]}. 203 | --------------------------------------------------------------------------------