Erlang Central

Difference between revisions of "Building An OTP Application"

From ErlangCentral Wiki

(Explains how to build a properly formed OTP application)
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