Building An OTP Application

From Erlang Community

(Difference between revisions)
Revision as of 03:50, 20 October 2006 (edit)
Martinjlogan (Talk | contribs)
(Explains how to build a properly formed OTP application)
← Previous diff
Revision as of 03:53, 20 October 2006 (edit) (undo)
Martinjlogan (Talk | contribs)

Next diff →
Line 1: Line 1:
-== Author == 
-Martin Logan 
-== Building an OTP Application Overview == 
- 
-Erlang is a clean, simple language, and it is easy to learn. That's erlang, now lets talk OTP. The learning curve goes up si 
-gnificantly when getting into OTP. Questions arise, such as; how do I start an application, what is the function of a supervi 
-sor, and how do I make use of gen_server? That is just the beginning it gets far more confusing... "what is a .app file, what 
-is a release, and can someone please tell me what .script and .boot files do?" 
- 
-This tutorial will walk you through creating a new OTP application called location_server. This application will store the loc 
-ation of clients, allow others to query for that location from any node in our network, and allow clients to subscribe to noti 
-fications that are sent in the event of a change of location. Over the course of creating the location_server I will answer al 
-l of the above questions on OTP applications. 
- 
-== Part One: Building the Server == 
- 
-You will need to download the reference build system for this tutorial, this can be found at <b>[http://www.erlang.org www.erl 
-ware.org]</b> under <b>downloads otp_base-&lt;vsn&gt;</b> Once you have the code on your local Unix/Linux machine extract the 
-build system. 
- 
-<code caption="Code listing 1.0"> 
-&gt; tar -xzfv otp_base-R1.tgz 
-&gt; cd otp 
-&gt; ls 
-build lib licence.txt Makefile README release tools 
-</code> 
- 
-The first thing we are going to do is make an initial build of our build system, this is done by typing "make" in the otp dire 
-ctory. 
- 
-The second thing to do is create a skeleton for the application. Once we do I will disect that skeleton and explain the meain 
-ing of all its parts. To create the location_server application skeleton we use the "appgen" utility. 
- 
-<code caption="Code listing 1.1"> 
-&gt; cd tools/utilities 
-&gt; ./appgen location_server ls 
-&gt; cd - 
-</code> 
-We have just created an application under lib/location_server and a release under release/location_server_rel. We will now cd 
- into lib/location_server and see what we have made. 
- 
-<code caption="Code listing 1.2"> 
-&gt; cd lib/location_server 
-&gt; ls 
-include Makefile src vsn.mk 
-</code> 
- 
-The include directory contains a file called location_service.hrl. This is the place we will put all our shared macro and rec 
-ord definitions. The src directory contains all of our erlang (.erl) files. The vsn.mk file contains the make variable LOCATI 
-ON_SERVER_VSN=1.0 which is the initial version of our application (feel free to change this). This version plays a role in ho 
-w releases are handled, more on this later. 
- 
-<code caption="Code listing 1.3"> 
-&gt; cd src 
-&gt; ls 
-&gt; 
-location_server.app.src location_server.erl ls_sup.erl 
-location_server.appup.src ls_server.erl Makefile 
-</code> 
- 
-The src directory contains a file called location_server.erl. Notice that this Erlang source file has the same name as our ap 
-plication. By convention this file contains the API for our application. This file also contains the -behaviour(application) 
- directive. This means that this file can be used as the entry point for the OTP application structure. 
- 
-<code caption="Code listing 1.4"> 
-%%%------------------------------------------------------------------- 
-%%% Author : martinjlogan <martinjlogan@martinjlogan.com> 
-%%% @doc The entry point into our application. 
-%%% @end 
-%%%------------------------------------------------------------------- 
--module(location_server). 
- 
--behaviour(application). 
-%%-------------------------------------------------------------------- 
-%% Include files 
-%%-------------------------------------------------------------------- 
--include("location_server.hrl"). 
- 
-%%-------------------------------------------------------------------- 
-%% External exports 
-%%-------------------------------------------------------------------- 
--export([ 
- start/2, 
- shutdown/0, 
- stop/1 
- ]). 
- 
-%%==================================================================== 
-%% External functions 
-%%==================================================================== 
-%%-------------------------------------------------------------------- 
-%% @doc The starting point for an erlang application. 
-%% @spec start(Type, StartArgs) -> {ok, Pid} | {ok, Pid, State} | {error, Reason} 
-%% @end 
-%%-------------------------------------------------------------------- 
-start(Type, StartArgs) -> 
- case ls_sup:start_link(StartArgs) of 
- {ok, Pid} -> 
- {ok, Pid}; 
- Error -> 
- Error 
- end. 
- 
-%%-------------------------------------------------------------------- 
-%% @doc Called to shudown the auction_server application. 
-%% @spec shutdown() -> ok 
-%% @end 
-%%-------------------------------------------------------------------- 
-shutdown() -> 
- application:stop(auction_server). 
- 
-%%==================================================================== 
-%% Internal functions 
-%%==================================================================== 
- 
-%%-------------------------------------------------------------------- 
-%% Called upon the termintion of an application. 
-%%-------------------------------------------------------------------- 
-stop(State) -> 
- ok. 
-</code> 
- 
-The start/2 function is a callback function used by the OTP application system to start applications specified by the OTP rele 
-ase system. This function must take two arguments (you can read the docs on supervision for more info on the args) and it mus 
-t return {ok, Pid} or {ok, Pid, State}, the former being more typical, for a succesful application startup. Notice that the st 
-art/2 function calls ls_sup:start_link/1. ls_sup.erl contains our top level supervisor. Notice the prefix "ls" in the name ls_ 
-sup it is the first two letters of the two words that make up our application name; location_server. This prefix is a convent 
-ion and it is used in liu of a package structure in Erlang to avoid name conflicts within a release. You can use anything for 
-your prefix, I only offer the first letter of each word in your application name as a suggestion. 
- 
-The top level supervisor starts all the workers and lower level supervisors that our application will require. It specifies a 
- strategy detailing how its processes will be restarted when they crash. This a key concept in Erlang and I suggest reading t 
-he design principles on supervision located at www.erlang.org. 
- 
-<code caption="Code listing 1.5"> 
-%%%------------------------------------------------------------------- 
-%%% Author : martinjlogan <martinjlogan@martinjlogan.com> 
-%%% @doc The top level supervisor for our application. 
-%%% @end 
-%%%------------------------------------------------------------------- 
--module(ls_sup). 
- 
--behaviour(supervisor). 
- 
-%%-------------------------------------------------------------------- 
-%% External exports 
-%%-------------------------------------------------------------------- 
--export([start_link/1]). 
- 
-%%-------------------------------------------------------------------- 
-%% Internal exports 
-%%-------------------------------------------------------------------- 
--export([init/1]). 
- 
-%%-------------------------------------------------------------------- 
-%% Macros 
-%%-------------------------------------------------------------------- 
--define(SERVER, ?MODULE). 
- 
-%%==================================================================== 
-%% External functions 
-%%==================================================================== 
-%%-------------------------------------------------------------------- 
-%% @doc Starts the supervisor. 
-%% @spec start_link(StartArgs) -> {ok, pid()} | Error 
-%% @end 
-%%-------------------------------------------------------------------- 
-start_link(StartArgs) -> 
- supervisor:start_link({local, ?SERVER}, ?MODULE, []). 
- 
-%%==================================================================== 
-%% Server functions 
-%%==================================================================== 
-%%-------------------------------------------------------------------- 
-%% Func: init/1 
-%% Returns: {ok, {SupFlags, [ChildSpec]}} | 
-%% ignore | 
-%% {error, Reason} 
-%%-------------------------------------------------------------------- 
-init([]) -> 
- RestartStrategy = one_for_one, 
- MaxRestarts = 1000, 
- MaxTimeBetRestarts = 3600, 
- 
- SupFlags = {RestartStrategy, MaxRestarts, MaxTimeBetRestarts}, 
- 
- ChildSpecs = 
- [ 
- {ls_server, 
- {ls_server, start_link, []}, 
- permanent, 
- 1000, 
- worker, 
- [ls_server]} 
- ], 
- {ok,{SupFlags, ChildSpecs}}. 
- 
-</code> 
- 
-This file exports two functions start_link/1 and init/1. These are both functions used to conform to the supervisor behaviour, 
- note the -behaviour(supervisor) directive which is present at the top of the file. Naming a function start_link is a conventi 
-on in OTP that means the function will spawn a process and that the caller of the function, the parent process, will be linked 
- (see link/1) to the child process. In this case the OTP application structure will be linked to the supervisor process. 
- 
-The init/1 function is where the true guts of the supervisor are. Remember that you should never put any program logic in your 
- supervisor, it should be as simple as possible and never fail, the fate of your application depends on this. Lets break our 
-super down: 
- 
-<code caption="Code listing 1.6"> 
-RestartStrategy = one_for_one, 
-MaxRestarts = 1000, 
-MaxTimeBetRestarts = 3600, 
-</code> 
- 
-The RestartStrategy one_for_one means that for each process that dies simply restart that process without affecting any of the 
- others. There are two other restart strategies, one_for_all and rest_for_one, you can read about these in the supervisor doc 
-umentation. MaxRestarts is the maxumum number of restarts allowed within MaxTimeBetweenRestarts seconds. 
- 
-<code caption="Code listing 1.7"> 
-{ls_server, 
- {ls_server, start_link, []}, 
- permanent, 
- 1000, 
- worker, 
- [ls_server]} 
-</code> 
- 
-This supervisor has one ChildSpec which means it will start exactly one process. Going in order down the data structure ls_ser 
-ver is the name of the process to be started, {ls_server, start_link, []} is the module function and args that is called to st 
-art the child, permanent refers to the fact that the process should never be alowed to die without being restarted (there are 
-other options, see the docs on supervision at erlang.org), 1000 is the time the process should be given to shutdown before bei 
-ng brutally killed, worker means the child is not a supervisor, [ls_server] is a list of module dependencies of the child. 
- 
-The supervisor callback module function init/1 returns all of this information and the supervisor behaviour takes care of star 
-ting all the children in the order that they were specified in the child specs. 
- 
-Now we move on to the ls_server.erl file. This file exhibits the behaviour gen_server. This means that it follows the callbac 
-k prescription for that behaviour and exports the following callback functions: 
- 
-<code caption="Code listing 1.8"> 
-%%-------------------------------------------------------------------- 
-%% gen_server callbacks 
-%%-------------------------------------------------------------------- 
--export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). 
-</code> 
- 
-Additionally we are also going to create the following external functions: 
- 
-<code caption="Code listing 1.9"> 
-%%-------------------------------------------------------------------- 
-%% External exports 
-%%-------------------------------------------------------------------- 
--export([ 
- start_link/0, 
- stop/0, 
- store_location/3, 
- fetch_location/2, 
- alert_to_change/2 
- ]). 
-</code> 
- 
-Lets get started. It is important to document all of your external functions. The OTP Base build system supports "edoc" with 
-the "make docs" target which will automatically go through and compile all of your documentation and place it in your applicat 
-ion directory under the directory doc (location_server/doc). On to our functions: 
- 
-<code caption="Code listing 1.10"> 
-%%-------------------------------------------------------------------- 
-%% @doc Starts the server. 
-%% @spec start_link() -> {ok, pid()} | {error, Reason} 
-%% @end 
-%%-------------------------------------------------------------------- 
-start_link() -> 
- gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). 
-</code> 
- 
-This function starts the server, registers it locally as ?SERVER (see source for -define(SERVER, ?MODULE).) and indicates to t 
-he gen_server behaviour that the current module exports the needed callback functions. 
- 
-<code caption="Code listing 1.11"> 
-%%-------------------------------------------------------------------- 
-%% @doc Stops the server. 
-%% @spec stop() -> ok 
-%% @end 
-%%-------------------------------------------------------------------- 
-stop() -> 
- gen_server:cast(?SERVER, stop). 
-</code> 
- 
-Stop is simple, but notice the use of gen_server:cast/2. This function is about the same as using the "!" operator. It is us 
-ed to send an asynchronous message to a gen server. In this case we send the atom 'stop' to a locally registered process iden 
-tified by the macro ?SERVER. 
- 
-<code caption="Code listing 1.12"> 
-%%-------------------------------------------------------------------- 
-%% @doc Stores the location of a client. 
-%% &lt;pre&gt; 
-%% Types: 
-%% Node = node() 
-%% Id = atom() 
-%% Location = string() 
-%% &lt;/pre&gt; 
-%% @spec store_location(Node, Id, Location) -> ok 
-%% @end 
-%%-------------------------------------------------------------------- 
-store_location(Node, Id, Location) when is_atom(Id), is_list(Location) -> 
- gen_server:cast({?SERVER, Node}, {store_location, {Id, Location}}). 
-</code> 
- 
-store_location/3 is a very simple function but there is a lot for us to pay attention to here. Notice that we use the guard e 
-xpressions, is_atom/1, and is_list/1. We are making sure that the types of the variables passed in are correct. This is an e 
-xample of not trusting our borders. We check the types here so that we will never again have to check them. Did you notice t 
-hat the name of the function is the same as the "tag" on the message we are sending? No? I will explain; 
- 
-<code caption="Code listing 1.13"> 
-gen_server:cast({?SERVER, Node}, {store_location, {Id, Location}}). 
-</code> 
- 
-store_location in the line above is our "tag" and will differentiate this message from others that this server understands (it 
-s alphabet). {Id, Location} is the payload that we are sending with the tag store_location. Messages sent in the gen_server sh 
-ould follow this convention of {Tag, Value}. There is an even more important thing to notice about the line above. Notice tha 
-t the first argument to this function is {?SERVER, Node} instead of just ?SERVER as we saw in cast. This is the case because 
-we want to be able to contact a process on any node in the network not just our own. To message a remotely registered process 
- we can use the syntax {RegisteredName, Node} where node can be any node in the network including the local node. 
- 
-<code caption="Code listing 1.14"> 
-%%-------------------------------------------------------------------- 
-%% @doc Fetches the location of a client by its Id. 
-%% &lt;pre&gt; 
-%% Types: 
-%% Node = node() 
-%% Id = atom() 
-%% &lt;/pre&gt; 
-%% @spec fetch_location(Node, Id) -> {ok, Location} | {error, Reason} | EXIT 
-%% @end 
-%%-------------------------------------------------------------------- 
-fetch_location(Node, Id) -> 
- gen_server:call({?SERVER, Node}, {fetch_location, Id}). 
-</code> 
- 
-The things to notice here is that we no longer use guards, we don't care, this function will not change the state of our serve 
-r and if it fails it does not matter much to us. Most importantly notice the use of gen_server:call/2. This function makes a 
-synchronous (blocking) call to a gen_server. 
- 
-<code caption="Code listing 1.15"> 
-%%-------------------------------------------------------------------- 
-%% @doc Subscribes the caller to notifications of location change for 
-%% the given Id. When a location changes the caller of this function 
-%% will be sent a message of the form {location_changed, {Id, NewLocation}}. 
-%% &lt;pre&gt; 
-%% Types: 
-%% Node = node() 
-%% Id = atom() 
-%% &lt;/pre&gt; 
-%% @spec alert_to_change(Node, Id) -> bool() | EXIT 
-%% @end 
-%%-------------------------------------------------------------------- 
-alert_to_change(Node, Id) -> 
- gen_server:call({?SERVER, Node}, {alert_to_change, {Id, self()}}). 
-</code> 
- 
-Now that we have all of our external functions finished lets flesh out our server. One thing that those new to Erlang often g 
-et confused by is the idea that these external functions are called in the process space of the calling process but that the m 
-essages they generate are received by clauses in the same module but that exist in an entirely different process. It is import 
-ant to use modules to encapsulate server protocols when possible. 
- 
-<code caption="Code listing 1.16"> 
-%%==================================================================== 
-%% Server functions 
-%%==================================================================== 
- 
-%%-------------------------------------------------------------------- 
-%% Function: init/1 
-%% Description: Initiates the server 
-%% Returns: {ok, State} | 
-%% {ok, State, Timeout} | 
-%% ignore | 
-%% {stop, Reason} 
-%%-------------------------------------------------------------------- 
-init([]) -> 
- error_logger:info_msg("ls_server:init/1 starting~n", []), 
- {ok, dict:new()}. 
-</code> 
- 
-The init function is used to setup process state before the process can be accessed from the outside. Here our state is embod 
-ied by a [http://www.erlang.org/doc/doc-5.5.1/lib/stdlib-1.14.1/doc/html/index.html dict]. This dict will be used to store key 
- value pairs of the form {Id, {Location, ListOfSubscribers}}. The init function is also logging the fact that the ls_server pr 
-ocess is starting up. The location and management of log files will be covered in the second part of this tutorial covering ho 
-w to create a release for your application. 
- 
-<code caption="Code listing 1.17"> 
-%%-------------------------------------------------------------------- 
-%% Function: handle_call/3 
-%% Description: Handling call messages 
-%% Returns: {reply, Reply, State} | 
-%% {reply, Reply, State, Timeout} | 
-%% {noreply, State} | 
-%% {noreply, State, Timeout} | 
-%% {stop, Reason, Reply, State} | (terminate/2 is called) 
-%% {stop, Reason, State} (terminate/2 is called) 
-%%-------------------------------------------------------------------- 
-handle_call({fetch_location, Id}, From, State) -> 
- case dict:find(Id, State) of 
- {ok, {Location, ListOfSubscribers}} -> {reply, {ok, Location}, State}; 
- error -> {reply, {error, no_such_id}, State} 
- end; 
-handle_call({alert_to_change, {Id, Subscriber}}, From, State) -> 
- case dict:find(Id, State) of 
- {ok, {Location, ListOfSubscribers}} -> 
- NewListOfSubscribers = [Subscriber|lists:delete(Subscriber, ListOfSubscribers)], 
- NewState = dict:store(Id, {Location, NewListOfSubscribers}, State), 
- {reply, true, NewState}; 
- error -> 
- {reply, false, State} 
- end. 
-</code> 
- 
-<code caption="Code listing 1.17"> 
-%%-------------------------------------------------------------------- 
-%% Function: handle_cast/2 
-%% Description: Handling cast messages 
-%% Returns: {noreply, State} | 
-%% {noreply, State, Timeout} | 
-%% {stop, Reason, State} (terminate/2 is called) 
-%%-------------------------------------------------------------------- 
-handle_cast(stop, State) -> 
- {stop, normal, State}; 
-handle_cast({store_location, {Id, Location}}, State) -> % Remember that State is a dict() 
- NewState = 
- case dict:find(Id, State) of 
- {ok, {OldLocation, ListOfSubscribers}} -> 
- lists:foreach(fun(Subscriber) -> Subscriber ! {location_changed, {Id, Location}} end, ListOfSubscribers), 
- dict:store(Id, {Location, ListOfSubscribers}, State); 
- error -> 
- dict:store(Id, {Location, []}, State) 
- end, 
- {noreply, NewState}. 
-</code> 
- 
-We are done with the server and all the functionality of our location_server application, but we have one last step to take. 
-It is clearly awkward and ugly to have clients of our application calling ls_server:store_location etc... What we need is a we 
-ll defined external API for clients. The place to put that API is in the module that has the same name as our application, ou 
-r application behaviour module, location_server.erl. 
- 
-<code caption="Code listing 1.18"> 
-%%-------------------------------------------------------------------- 
-%% @doc Stores the location of a client. 
-%% <pre> 
-%% Types: 
-%% Node = node() 
-%% Id = atom() 
-%% Location = string() 
-%% </pre> 
-%% @spec store_location(Node, Id, Location) -> ok 
-%% @end 
-%%-------------------------------------------------------------------- 
-store_location(Node, Id, Location) -> 
- ls_server:store_location(Node, Id, Location). 
- 
-%%-------------------------------------------------------------------- 
-%% @doc Fetches the location of a client by its Id. 
-%% <pre> 
-%% Types: 
-%% Node = node() 
-%% Id = atom() 
-%% </pre> 
-%% @spec fetch_location(Node, Id) -> {ok, Location} | {error, Reason} | EXIT 
-%% @end 
-%%-------------------------------------------------------------------- 
-fetch_location(Node, Id) -> 
- ls_server:fetch_location(Node, Id). 
- 
-%%-------------------------------------------------------------------- 
-%% @doc Subscribes the caller to notifications of location change for 
-%% the given Id. When a location changes the caller of this function 
-%% will be sent a message of the form {location_changed, {Id, NewLocation}}. 
-%% <pre> 
-%% Types: 
-%% Node = node() 
-%% Id = atom() 
-%% </pre> 
-%% @spec alert_to_change(Node, Id) -> bool() | EXIT 
-%% @end 
-%%-------------------------------------------------------------------- 
-alert_to_change(Node, Id) -> 
- ls_server:alert_to_change(Node, Id). 
-</code> 
- 
-Of course the functions must be exported. 
- 
-<code caption="Code listing 1.19"> 
-%%-------------------------------------------------------------------- 
-%% External exports 
-%%-------------------------------------------------------------------- 
--export([ 
- start/2, 
- shutdown/0, 
- store_location/3, 
- fetch_location/2, 
- alert_to_change/2, 
- stop/1 
- ]). 
-</code> 
- 
-Now we are done with the coding of location_server, all we have to do is change directories into our application directory, ty 
-pe "make" and "make docs", and wait for a succesful source and documentation compile. 
- 
-<code caption="Code listing 1.20"> 
-&gt; cd .. 
-&gt; make 
-&gt; make docs 
-&gt; ls 
-doc ebin include Makefile src vsn.mk 
-</code> 
- 
-Notice that there are two new directories in our application root directory "doc" and "ebin". location_server/doc contains htm 
-l documentation of everything we have done. All of our external functions are present in these docs. Lets explore the ebin di 
-rectly a little more deeply. 
- 
-<code caption="Code listing 1.21"> 
-&gt; cd ebin 
-&gt; ls 
-location_server.app location_server.beam ls_sup.beam 
-location_server.appup ls_server.beam 
-</code> 
- 
-The ebin contains a single .beam bytecode compiled file for each of our .erl source files. The ebin directory also contains a 
- .app and a .appup file. The .appup file is used for release upgrade handling and is beyond the scope of this tutorial. The . 
-app file is essential for creating and installing OTP releases so we will look at it in depth. 
- 
-<code caption="Code listing 1.22"> 
-{application, location_server, 
- [ 
- % A quick description of the application. 
- {description, "An Erlang Application."}, 
- 
- % The version of the applicaton 
- {vsn, "1.0"}, 
- 
- % All modules used by the application. 
- {modules, 
- [ 
- location_server, ls_server, ls_sup 
- ]}, 
- 
- % All of the registered names the application uses. This can be ignored. 
- {registered, []}, 
- 
- % Applications that are to be started prior to this one. This can be ignored 
- % leave it alone unless you understand it well and let the .rel files in 
- % your release handle this. 
- {applications, 
- [ 
- kernel, 
- stdlib 
- ]}, 
- 
- % OTP application loader will load, but not start, included apps. Again 
- % this can be ignored as well. To load but not start an application it 
- % is easier to include it in the .rel file followed by the atom 'none' 
- {included_applications, []}, 
- 
- % configuration parameters similar to those in the config file specified 
- % on the command line. can be fetched with gas:get_env 
- {env, []}, 
- 
- % The Module and Args used to start this application. 
- {mod, {location_server, []}} 
- ] 
-}. 
- 
-== Part One: Building the Server == 
- 
-The comments in the above code pretty much explain it all. This .app file is created dynamically by the make process from the 
- location_server/src/location_server.app.src file. This means that you should pretty much never have to manually edit this fi 
-le. 
- 
-Congratulations you have just created your first beautiful OTP application. Now lets move on to the second part of our tutoria 
-l and create a release in order to run and deploy our OTP application. 
- 
-== Part Two: Crafting a Release == 
- 
-From the otp directory: 
- 
-<code caption="Code listing 2.0"> 
-&gt; cd release/location_server_rel 
-&gt; ls 
-location_server_rel.config.src Makefile yaws.conf.src 
-location_server_rel.rel.src vsn.mk 
-</code> 
- 
-This directory contains a vsn.mk file which serves to store the version string for this release. It contains a yaws.conf.src 
-file which will be used to generate a yaws.conf file should you choose to include the yaws webserver in your release. The two 
-files we are going to concentrate on right now are the location_server_rel.config.src file and the location_server.rel.src fil 
-e. The .config.src file is what make will translate into a .config file. This file includes all of the configuration the diff 
-erent applications in our release require to do things such as truncate and rotate our log files. The .rel.src file is what th 
-e make process will turn into a .rel file which will inturn be used to create our .script and .boot files, the files that tell 
- the Erlang system what code to load when starting up. Here is our .rel.src file. 
- 
-<code caption="Code listing 2.1"> 
-{release, 
- {"location_server_rel", "%REL_VSN%"}, 
- erts, 
- [ 
- kernel, 
- stdlib, 
- sasl, 
- fslib, 
- gas, 
- location_server 
- ] 
-}. 
-</code> 
- 
-This file specifies the name of our release followed by a version string that will be filled in when we do a make with the ver 
-sion string present in our vsn.mk file. Following that we specify that we want erts to be present in our release and the kerne 
-l, stdlib, sasl, fslib, gas, and finally our own custom application the location_server. For more details on fslib and gas, th 
-e two applications that together provide configuration and log rotation and truncation for our release you can go to [http://www.erlware.org erlware.org] and see full documentation about them. Alternatively you can cd to either otp/lib/fslib or otp/li 
-b/gas and run "make docs" and just generate all of the docs locally. When we run make this rel.src file will be turned into a 
-n actual .rel file which contains all the version numbers associated with the applications in the .rel.src file. For more inf 
-o on the .rel.src structure take a look at the docs for the fslib file [http://www.erlware.org/fos_erlware/markup/fslib_doc/in 
-dex.html fs_boot_smithe]. 
- 
-Time to build and run our release. 
- 
-<code caption="Code listing 2.2"> 
-&gt; make 
-&gt; ls 
-local location_server_rel.rel Makefile 
-location_server_rel location_server_rel.rel.src vsn.mk 
-location_server_rel.boot location_server_rel.rel.src.tmp yaws.conf.src 
-location_server_rel.config.src location_server_rel.script 
-</code> 
- 
-Quite a number of files and two directories have been generated. We can see a .rel file, a .script file, and a .boot file. Ta 
-ke a look at the contents of each, well, the .boot file might not prove to be too interesting because it is just a binary vers 
-ion of the .script file. Pay attention to the two directories that have been created; local and location_server_rel. location 
-_server_rel is a staging area for installation and production releases of our application, for more info reference [http://www 
-.erlware.org/fos_erlware/project/otp_base OTP Base] documentation at erlware.org. We are going to focus on the local director 
-y here. This directory allows us to run our release interactively. If we wanted to run it as a daemon it would be easy enoug 
-h to invoke local/location_server_rel.sh with the -detached flag to accomplish that. For now we are going to cd into local an 
-d just run our application as is. 
- 
-<code caption="Code listing 2.3"> 
-&gt; cd local 
-&gt; ./location_server_rel.sh 
-Erlang (BEAM) emulator version 5.4.12 [source] [hipe] 
- 
-INFO REPORT 14-Oct-2006::00:18:33 
-gas_sup:init 
- 
-INFO REPORT 14-Oct-2006::00:18:33 
-ls_server:init/1 starting 
- 
-Eshell V5.4.12 (abort with ^G) 
-(martinjlogan_location_server_rel@core.martinjlogan.com)1&gt; 
-</code> 
- 
-We have successfully started the location_server release. Can you see where the log message we placed in ls_server to indicate 
- its having reached the init/1 function has shown up on the console? That is not the only place our message shows up: 
- 
-<code caption="Code listing 2.4"> 
-&gt; ls log/1.0/ 
-err_log sasl_log 
-</code> 
- 
-The err_log file contains all the logs that get printed via the sasl error_logger with info_msg or error_msg. The other log, 
-sasl_log, is where you will find all the sasl error reports. If your release crashes the first place to look is the sasl_log. 
- By default the build system has wired in the use of fs_elwrap_h via the G.A.S application that is part of your release in or 
-der to truncate and rotate logfiles. This prevents the log files from growing indefinitely. To configure or remove this func 
-tionality you can use the location_server.config file located in the "local" directory. 
- 
- 
-<code caption="Code listing 2.4"> 
-{gas, 
- [ 
- % Tell GAS to start elwrap. 
- {mod_specs, [{elwrap, {fs_elwrap_h, start_link}}]}, 
- 
- % elwrap config. 
- {err_log, "/home/martinjlogan/work/otp/release/location_server_rel/local/log/1.0/err_log"}, 
- {err_log_wrap_info, {{err,5000000,10},{sasl,5000000,10}}}, 
- {err_log_tty, true} % Log to the screen 
- ]}, 
-</code> 
- 
-The mod_specs inform the GAS application as to how and what services to start up for a release. Following that are the actual 
-config tuples elwrap uses to do its job. Starting with the location of the err_log and followed by the wrap specs which indica 
-te for both the sasl and error logs how large a file can get before it is rotated out and just how many files there may be for 
- a given logger before old files start getting deleted. The last bit of config toggles whether or not the error logger writes 
-to the screen. 
- 
-== Part Three: Testing What We Have Built == 
- 
-To test our project we are going to start 3 separate Erlang named nodes. Open three terminal windows and start three nodes, o 
-ne is the location server as started by local/location_server.sh, two is started with erl -name a -pz &lt;path to location_ser 
-ver ebin&gt, three is started with erl -name b -pz &lt;path to location_server ebin&gt. The -pz option on both tells the inte 
-rpreter to add the specified path to the code loader search path. This means that we will have the location_server interfaces 
- available for our use in node a and b . (This loading of code without starting an application can be accomplished by adding 
-the tuple 'none' after an included application in the .rel.src file.) So now we have three nodes a@machinename.com b@machinen 
-ame.com and &lt;username&gt;_location_server@machinename.com. To connect our nodes execute nat_adm:ping('a@machinename.com') 
-from the other two nodes. After this is complete running nodes() on any of the nodes should yield a complete list of the othe 
-r nodes in our cloud. Run the following commands on your nodes. Make sure you pay attention to the node names in the following 
- example because the commands are not all run on the same node. 
- 
-<code caption="Code listing 3.0"> 
-(a@core.martinjlogan.com)1> location_server:store_location('martinjlogan_location_server_rel@core.martinjlogan.com', martin, " 
-at work"). 
-ok 
- 
-(b@core.martinjlogan.com)1> location_server:alert_to_change('martinjlogan_location_server_rel@core.martinjlogan.com', martin). 
-true 
-(b@core.martinjlogan.com)2> receive Msg -> io:format("Msg: ~p~n", [Msg]) end. 
- 
-(a@core.martinjlogan.com)2> location_server:store_location('martinjlogan_location_server_rel@core.martinjlogan.com', martin, " 
-at home"). 
-ok 
- 
-(b@core.martinjlogan.com)2> 
-Msg: {location_changed,{martin,"at home"}} 
-ok 
- 
-(a@core.martinjlogan.com)3> location_server:fetch_location('martinjlogan_location_server_rel@core.martinjlogan.com', martin). 
- 
-{ok,"at home"} 
-</code> 
- 
-Here is what happened above, we started a location server as the node 'martinjlogan_location_server_rel@core.martinjlogan.com' 
- via the location_server_rel.sh start-up script. Then we started two nodes a@core.martinjlogan.com and b@core.martinjlogan.co 
-m. We then followed these steps: 
-<ol> 
-<li>At prompt count 1 on "a" we did a store location with the key 'martin' and the value "at work"</li> 
-<li>At prompt count 1 on "b" we did an alert to change for the key 'martin'</li> 
-<li>At prompt count 2 on "b" we drop into a receive for a Msg which we will print on receipt</li> 
-<li>At prompt count 2 on "a" we do a store location, which changes the location for 'martin' to "at home" and causes "b" to ex 
-it 
-the receive clause printing the message: Msg: {location_changed,{martin,"at home"}}. Subscription works!</li> 
-<li>Finally at prompt count 3 on "a" we prove that fetch_location works.</li> 
-</ol> 
- 
- 
-You can download the code for this tutorial [http://www.erlware.org/fos_erlware/priv/downloads/location_server.tgz here] 
-That's it, happy Erlanging. 
- 
-[[Category:HowTo]] 

Revision as of 03:53, 20 October 2006

Erlang/OTP Projects
Personal tools