Copyright (C) 2014-2016, Jaguar Land Rover
This document is licensed under Creative Commons
Attribution-ShareAlike 4.0 International.
**Version 0.5.0**
# CONFIGURING AN RVI NODE
This document describes the process of configuring an RVI node so that it
can serve locally connected services, and also find other RVI nodes in a network.
## READER ASSUMPTIONS
The reader is assumed to be able to:
1. Have a basic understanding of Linux directory structures.
2. Start and stop programs on the RVI-hosting system
3. Edit configuration files.
4. Understand the basic concepts of IP addresses, ports and URLs.
## PREREQUISITES
1. Erlang runtime 18.2 or later has to be installed on the hosting system.
2. The `setup_rvi_node.sh` tool is available to build a release.
3. `rvi_sample.config` is used as a starting point for a customized setup.
Root access is not needed.
## CONFIGURATION PROCESS OVERVIEW
To bring up an RVI node so that it can be used by locally
connected services and communicate with other RVI nodes, the following
steps must be taken.
1. Specify the node service prefix
This node will handle traffic to all services that start with the
given prefix.
2. Provide paths to keys, certificates and service credentials
RVI Core uses X.509 keys certificates for authentication, and credentials
specifying which services can be registered and invoked.
3. Configure static nodes
Backend / Cloud-based RVI nodes have non-changing network addresses that
should be known by other nodes in a network. This is acheived by
setting up service prefixes and addresses of the static nodes in
all other nodes deployed in a network.
4. Specify Service Edge URL that local services connect to
The Service Edge URL is used by local services to send traffic that is
to be forwarded to services on the local and remote nodes.
5. Specify URLs for RVI components
In addition to the Service Edge URL, the remaining components must
have their URLs configured so that the components can locate each
other and exchange commands.
6. Build the development release
The `setup_rvi_node.sh` script is executed to read the configuration file
and generate a development or production release.
7. Start the release
The `rvi_node.sh` script is executed to launch the built development
release. `$REL_HOME/rvi/bin/rvi start` is used to launch the
production release.
## CONFIGURATION FILE LOCATION AND FORMATS
There is a single configuration file, with the setup for all
components and modules in the node, used for each release.
A documented example file is provided as `priv/config/rvi_sample.config`
The configuration file consists of an array of erlang tuples (records
/ structs / entries), where the `env` tuple contains configuration data for
all components. The `rvi` tuple under `env` has all the
configuration data for the RVI system. With the possible exception for
the lager logging system, only the `rvi` tuple needs to be edited.
The term tuple and entry will be intermixed throughout this document.
### Erlang terms
For a full description of Erlang types, read [the Erlang Reference Manual](http://erlang.org/doc/reference_manual/users_guide.html). The following is a brief summary:
* Tuples, written as `{ Elem1, ..., ElemN }` are like arrays whose elements are accessed by position.
* Lists, written as `[ Elem1, ..., ElemN ]` are linked lists whose elements are accessed by iterating from the beginning of the list. Another notation is `[ Head | Tail ]`. Strings are actually lists of integers, and `"RVI"` is equivalent to `[82,86,73]`, or `[$R,$V,$I]`.
* Numbers, e.g. `17`, `1.44`, `2#101` (binary notation), `16#5A` (hex notation).
* Atoms, names starting with a lowercase letter or enclosed in single quotes, are essentially labels.
* Variable names start with an uppercase letter.
### Setup config files
RVI Core uses the [setup](https://github.com/uwiger/setup) tool to build from configuration files.
The files used by `setup` are evaluated using the [file:script/1](http://erlang.org/doc/man/file.html#script-1) function, meaning that the file can contain a sequence of executable Erlang expressions, each terminated with a full stop (`.`). Anything starting with `%` and to the end of the line
constitutes a comment.
Example from `rvi_core/priv/config/rvi_sample.config`:
Env = fun(V, Def) ->
case os:getenv(V) of
false -> Def;
Str when is_integer(Def) -> list_to_integer(Str);
Str when is_atom(Def) -> list_to_atom(Str);
Str -> Str
end
end.
IPPort = fun(IP, Port) ->
IP ++ ":" ++ integer_to_list(Port)
end.
MyPort = Env("RVI_PORT", 9000).
MyIP = Env("RVI_MYIP", "127.0.0.1").
MyNodeAddr = Env("RVI_MY_NODE_ADDR", IPPort(MyIP, MyPort)).
BackendIP = Env("RVI_BACKEND", "38.129.64.31").
BackendPort = Env("RVI_BACKEND_PORT", 8807).
LogLevel = Env("RVI_LOGLEVEL", notice).
The above defines two function objects used to check for the existence of a given OS environment variable, and using the corresponding value, or else using a default value. For example, the variable `MyPort` is set to either the value of `RVI_PORT` or else to `9000`. These variables are used further down in the configuration file.
[ ... { env, [ ... { rvi_core, [ ... { node_service_prefix, "jaguarlandrover.com/backend/" } ]} ]} ]#PROVIDE PATHS TO KEYS, CERTIFICATES AND SERVICE CREDENTIALS The following settings are required for the RVI Core authentication framework: * `{device_key, DevKeyFile}` * `{root_cert, RootCertFile}` * `{device_cert, DevCertFile}` * `{cred_dir, CredDirectory}` The [doc/rvi_protocol.md](doc/rvi_protocol.md) document explains the authentication protocol and how to create the necessary keys and certificates. Note that the `cred_dir` option needs to be a directory. RVI Core will pick up any valid credentials present in that directory. Default values - the sample keys and certs used in `rvi_protocol.md` - are specified in [rvi_common.config](priv/doc/rvi_common.config). The defaults should *only* be used for testing and demos - never for live use. #CONFIGURE DATA LINK LAYERS The `data_link` components are specified as `{Module, Type, Options}`, e.g.
[ ... { env, [ ... { rvi_core, [ ... { components, [ ... { dlink_tls_rpc, gen_server, [ ... ] } ] } ] } ] } ]In the data link component, `dlink_tls_rpc`, you can specify the following options: { server_opts, Opts } These are options for the TLS listener and subsequent connections: * `{port, Port}` - which port to listen to * `{ip, Address}` - optionally specifies which interface to listen to * `{max_msg_size, Sz}` - maximum fragment size (see [doc/rvi_protocol.md](doc/rvi_protocol.md#chunking-of-large-messages) for details * `{reliable, true | false}` - if `max_msg_size` isn't specified, this will enable reliable transmission using the fragmentation protocol. { persistent_connections, Addresses } `Addresses` is a list of IP:Port strings signifying other RVI nodes to connect to. The connection- and fragmentation-related options above will also be applied to these connections. An example tuple is given below:
[ ... { env, [ ... { rvi_core, [ ... { components, [ ... { data_link, [ ... { dlink_tls_rpc, gen_server, [ ... { server_opts, [ { ip, "192.168.11.234"}, { port, 8807 }, { max_msg_size, 1024 } ]}, { persistent_connections, [ "38.129.64.13:8807" ]} ] }] }] }] }] } ]If `dlink_tcp_rpc` is to listen to the port on all network interfaces, the `ip` tuple can be omitted. The `persistent_connections` section lists the IP:Port pair of all remote RVI nodes that this node should maintain a connection with. If the address is not available, a reconnection attempt will be made every five seconds. This allows a solid connection between RVI nodes where only one node can initiate a connection (such as a vehicle-to-server link in a mobile network). # ROUTING RULES Routing rules determining how to get a message targeting a specific service to its destination. A routing rule specifies a number of different way to reach an RVI node hosting a specific service prefix, such as `jlr.com/vin/ABCD/sota/`. Please note that if a remotely initiated (== client) data link is available and has announced that the targeted service is available, that data link will be used regardless of what it is. Service name prefix that rules are specified for The service prefix with the longest match against the service targeted by the message will be used. Example: Targeted service = `jlr.com/backend/sota/get_updates` Prefix 1: `{ "jlr.com/backend", [...]}`
{ routing_rules, [ { "", [ { proto_json_rpc, dlink_tcp_rpc} ]} ]}This rule specifies that, unless another rule has a longer prefix match, a request shall be encoded using `proto_json_rpc`, and transmitted using `dlink_tcp_rpc`. To direct an in-vehicle RVI-node to send all its backend requests to a specific address, add the following rule.
{ routing_rules, [ { "", [ { proto_json_rpc, dlink_tcp_rpc} ]}, { "jlr.com/backend/", [ { proto_json_rpc, { dlink_tcp_rpc, [ { target, "38.129.64.31:8807" } ]}} ]} ]}This rule specifies that any message to a service starting with `jlr.com/backend` shall first be encoded using `proto_json_rpc`, and transmitted using `dlink_tcp_rpc`. The `dlink_tcp_rcp` data link module will be instructed to send all messages targeting `jlr.com/backend/...` to the IP-address:port `38.129.64.31:8807`. To setup Vehicle-to-Vehicle communication, where a vehicle can reach services on other vehicle's starting with `jlr.com/vin/`, add the following rule.
{ routing_rules, [ { "", [ { proto_json_rpc, dlink_tcp_rpc} ]}, { "jlr.com/backend/", [ { proto_json_rpc, { dlink_tcp_rpc, [ { target, "38.129.64.31:8807" } ]}} ]}, { "jlr.com/vin/", [ { proto_json_rpc, { dlink_tcp_rpc, [ broadcast, { interface, "wlan0" } ] } }, { proto_json_rpc, { dlink_3g_rpc, [ initiate_outbound ]} }, { proto_sms_rpc, { dlink_sms_rpc, [ { max_msg_size, 140 } ] } } ] } ]}This rule specifies that any message to a service starting with `jlr.com/vin` shall first be encoded using the protocol - data link pair `proto_json_rpc` - `dlink_tcp_rpc`, where WiFi broadcasts shall be used (thrugh `wlan0` and `broadcast`) to find other vehiclces. If that does not work, a 3G connection to the vehicle shall be attempted, through the `proto_json_rpc` - `dlink_3g_rpc` pair, where we are allowed to initiate outbound connections to the 3G network in case a connection is not already available. Finally, an SMS can be sent through the `proto_sms_rpc` - `dlink_sms_rpc` pair, maximizing message size to 140 bytes. # SPECIFY SERVICE EDGE URL # The Service Edge URL is that which will be used by locally connected services to interact, through JSON-RPC, with the RVI node. In cases where JSON-RPC is used instead of Erlang-internal gen\_server calls, other components in the RVI node use the same URL to send traffic to Service Edge The URL of Service Edge is specified through the `service_edge_rpc` tuple's `json_rpc_address` entry, read by the other components in the node to locate it. An example entry is gven below:
[ ... { env, [ ... { rvi_core, [ ... { components, [ ... { service_edge, [ { service_edge_rpc, json_rpc, [ { json_rpc_address, { "127.0.0.1", 8801 } }, { websocket, [ { port, 8808}]} ]} ]} ]} ]} ]} ]*Please note that IP addresses, not DNS names, should be used in all network addresses.* # SPECIFY URLS OF RVI COMPONENTS # The remaining components in an RVI system needs to have their URLs and listening ports setup as well. It is recommended that consecutive ports after that used for `service_edge_rpc` are used. Please note that if only erlang components are used (as is the case in the reference implementation), native erlang gen\_server calls can be used instead of URLs, providing a significant transactional speedup. Please see the genserver components chapter below for details. Below is an example of a complete port/url configuration for all components, including the `bert_rpc_server` entry described in the external node address chapter:
[ ... { env, [ ... { rvi_core , [ ... { components, [ ... { service_edge, [ { service_edge_rpc, json_rpc, [ { json_rpc_address, { "127.0.0.1", 8801 } }, ... ]} ]}, { service_discovery, [ { service_discovery_rpc, json_rpc, [ { json_rpc_address, { "127.0.0.1", 8802 }} ]} ]}, { schedule, [ { schedule_rpc, json_rpc, [ { json_rpc_address, { "127.0.0.1", 8803 }} ]} ]}, { authorize, [ { authorize_rpc, json_rpc, [ { json_rpc_address, { "127.0.0.1", 8804 } } ]} ]}, { protocol, [ { proto_json_rpc, json_rpc, [ { json_rpc_address, { "127.0.0.1", 8805 } } ]} ]}, { data_link, [ { dlink_tcp_rpc, json_rpc, [ { json_rpc_address, { "127.0.0.1", 8806 } }, ... ]} ]} ]} ]} ]} ]*Please note that IP addresses, not DNS names, should be used in all network addresses.* # SPECIFY GEN\_SERVER ADDRESSES FOR RVI COMPONENTS # Communication between the RVi components can be either JSON-RPC or Erlang-internal gen\_server calls. JSON-RPC calls provide compatibility with replacement components written in languages other than Erlang. gen\_server calls provide native erlang inter-process calls that are signficantly faster than JSON-RPC when transmitting large data volumes. If one or more of the RVI components are replaced with external components, use JSON-RPC by `json_rpc_address` for all components. If an all-native erlang system is configured, use gen\_server calls by configuring `gen_server`. If both `gen_server` and `json_rpc_address` are specified, the gen\_server communicaiton path will be used for inter component communication. Please note that communication between two RVI nodes are not affected by this since data_link_bert_rpc will use the protocol and data links specified by the matching routing rule to communicate. See [Routing Rules](#ROUTING RULES) chapter for details. Below is an example of where gen\_server is used where appropriate. Please note that `service_edge_rpc` always need to have its `json_rpc_address` specified since local services need an HTTP port to send JSON-RPC to. However, gen\_server can still be specified in parallel, allowing for gen\_server calls to be made between Servie Edge and other RVI components.
[ ... { env, [ ... { rvi_core , [ ... { components, [ ... { service_edge, [ { service_edge_rpc, gen_server, [ { json_rpc_address, { "127.0.0.1", 8801 } }, ... ]} ]}, { service_discovery, [ { service_discovery_rpc, gen_server, [] } ]}, { schedule, [ { schedule_rpc, gen_server, [] } ]}, { authorize, [ { authorize_rpc, gen_server, [] } ]}, { protocol, [ { proto_json_rpc, gen_server, [] } ]}, { data_link, [ { dlink_tcp_rpc, gen_server, [ ... ]} ]} ]} ]} ]} ]# SETTING UP WEBSOCKET SUPPORT The service edge can, optionally, turn on its websocket support in order to support locally connected services written in javascript. This allows an RVI node to host services running in a browser, on node.js or similar web environments. Websocket support is enabled by adding a `websocket` entry to the configuration data of `servide_edge_rpc`. Below is the previous configuration example with such a setup.
[ ... { env, [ ... { rvi_core, [ ... { components, [ ... { service_edge, [ { service_edge_rpc, json_rpc, [ { json_rpc_address, { "127.0.0.1", 8801 } }, { websocket, [ { port, 8808}]} ]} ]} ]} ]} ]} ]Websocket clients can now connect to: `ws://127.0.0.1:8801/websession` and issue JSON-RPC commands to Service Edge. Outbound service invocations, sent from the RVI node to the javascript code, will be transmitted over the same socket. # COMPILING THE RVI SOURCE CODE Before a development release can be built, the source code needs to be compiled. Please see BUILD.md for details on this process. # CREATING A DEVELOPMENT RELEASE *Please note that a new release must be created each time the configuration file has been updated* Once a configuration file has been completed, a development release is created. The difference between a development and a production release is that the development release needs the compiled files located in the source tree to operate, while a production release is completely self contained (including the erlang runtime system) in its own subdirectory. Each release will have a name, which will also be the name of the newly created subdirectory containing the files necessary to start the release. If a configuration file, `rvi_sample.config` is to be used when building release `test_rel`, the following command can be run from the build root: ./scripts/setup_rvi_node.sh -d -n test_rel -c rvi_sample.config Once executed (and no errors were found in test.config), a subdirectory called `test_rel` has been created. This directory contains the erlang configuration and boot files necessary to bring up the RVI node. # STARTING A DEVELOPMENT RELEASE The newly built development release is started using the `rvi_node.sh` tool. In order to start the test release, named `test_rel`, created in the previous chapter, the following command is run from the build root: ./scripts/rvi_node.sh -n tes_rel When a development release is started the erlang console prompt will be displayed at the end of the startup process, allowing for manual inspection of the running system. Once the RVI node has been brought up, services can connect to its Service Edge and start routing traffic. # CREATING A PRODUCTION RELEASE *Please note that a new release must be created each time the configuration file has been updated* To create a self contained production release using `prod.config` as the configuration file, and name the release `prod_rel`, the following command can be run from the build root: ./script/setup_rvi_node.sh -n prod_rel -c prod.config Once executed (and no errors were found in test.config), a subdirectory called `rel/prod_rel` has been created. The `prod_rel` directory contains a complete erlang runtime system, the RVI application, and the configuration data generated from `prod.config` the RVI node. The `prod_rel` directory can be moved to anywhere in the file system, or to another host with the same architecture and OS setup. # STARTING A PRODUCTION RELEASE The newly built product release is started using the `rel/prod_rel/rvi` tool: ./rel/prod_rel/rvi start Stopping is done in a similar manner: ./rel/prod_rel/rvi stop To check if a node is up, retrieve its process ID with: ./rel/prod_rel/rvi getpid To attach to the console of a started node in order to inspect it run: ./rel/prod_rel/rvi attach *Note that you need to exit from the console with Ctrl-d. Pressing Ctrl-c will bring down the node itself.* # Loggings To get debug output on a console, start a development release, or attach to a production release, and set the log level manually: 1> lager:set_loglevel(lager_console_backend, debug) Replace debug with info, notice, warning, or error for different log levels. A production release will also produce logs to `rel/[release]/log/erlang.log.?`. Check the file modification date to find which of the log files are currently written to. You can configure the log level through the lager configuration entry:
{env, [ {lager, [ { handlers, [ {lager_console_backend, debug} ] } ]} ...Additional handlers can also be added for different log destinations. See Basho's lager documentation at [github](https://github.com/basho/lager) for details on logging.