From 824704b65f9a302a6baf8e4aab7c8898478659e3 Mon Sep 17 00:00:00 2001 From: Felipe Ripoll Date: Tue, 5 Jun 2018 15:14:04 -0600 Subject: [PATCH] [#9] Defining the abstract custom protocol --- README.md | 11 ++ assets/backend_architecture.png | Bin 0 -> 56807 bytes lib/poa_backend.ex | 41 ++++--- lib/poa_backend/application.ex | 2 +- lib/poa_backend/protocol.ex | 130 +++++++++++++++++++++++ lib/poa_backend/protocol/data_type.ex | 14 +++ lib/poa_backend/protocol/message.ex | 72 +++++++++++++ lib/poa_backend/protocol/message_type.ex | 19 ++++ mix.exs | 42 +++++++- test/poa_backend_test.exs | 8 -- test/protocol/message_test.exs | 4 + 11 files changed, 316 insertions(+), 27 deletions(-) create mode 100644 assets/backend_architecture.png create mode 100644 lib/poa_backend/protocol.ex create mode 100644 lib/poa_backend/protocol/data_type.ex create mode 100644 lib/poa_backend/protocol/message.ex create mode 100644 lib/poa_backend/protocol/message_type.ex delete mode 100644 test/poa_backend_test.exs create mode 100644 test/protocol/message_test.exs diff --git a/README.md b/README.md index d0a29f1..0d241b6 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,17 @@ Storage and data-processing companion for the [poa-netstats-agent](https://github.com/poanetwork/poa-netstats-agent) +## Documentation + +In order to build the documentation run + +``` +mix deps.get +mix docs +``` + +That command will create a `doc/` folder with the actual Documentation. + ## Run Tests In order to run the tests we have to run the command diff --git a/assets/backend_architecture.png b/assets/backend_architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..9a7f89220d646f5c15cb63ec2eabfe7ea35490c7 GIT binary patch literal 56807 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

)%WOn$i_vJ(t7WUB~YkEUxa#YlGi=W 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�oCM&3Z#gLzZMzM^GB`Xiue9M#cj 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 literal 0 HcmV?d00001 diff --git a/lib/poa_backend.ex b/lib/poa_backend.ex index 864231d..1a6cd01 100644 --- a/lib/poa_backend.ex +++ b/lib/poa_backend.ex @@ -1,18 +1,31 @@ defmodule POABackend do @moduledoc """ - Documentation for PoaBackend. + Storage and data-processing companion for the [poa-netstats-agent](https://github.com/poanetwork/poa-netstats-agent) + + ## Documentation + + In order to build the documentation run + + ``` + mix deps.get + mix docs + ``` + + That command will create a `doc/` folder with the actual Documentation. + + ## Run Tests + + In order to run the tests we have to run the command + + ``` + mix test + ``` + + `POABackend` comes also with a code analysis tool [Credo](https://github.com/rrrene/credo) and a types checker tool [Dialyxir](https://github.com/jeremyjh/dialyxir). In order to run them we have to run + + ``` + mix credo + mix dialyzer + ``` """ - - @doc """ - Hello world. - - ## Examples - - iex> POABackend.hello - :world - - """ - def hello do - :world - end end diff --git a/lib/poa_backend/application.ex b/lib/poa_backend/application.ex index d3a8237..5ddc372 100644 --- a/lib/poa_backend/application.ex +++ b/lib/poa_backend/application.ex @@ -14,4 +14,4 @@ defmodule POABackend.Application do opts = [strategy: :one_for_one, name: POABackend.Supervisor] Supervisor.start_link(children, opts) end -end +end \ No newline at end of file diff --git a/lib/poa_backend/protocol.ex b/lib/poa_backend/protocol.ex new file mode 100644 index 0000000..b273e36 --- /dev/null +++ b/lib/poa_backend/protocol.ex @@ -0,0 +1,130 @@ +defmodule POABackend.Protocol do + @moduledoc """ + + ## POA Protocol + + This protocol defines the communication between the Agents and the POA Backend. + + ![POA Backend Architecture](./backend_architecture.png) + + ### Basic calls + + Only those calls are allowed: + + * session (*not implemented*) - In future in order to add authentication / authorization + * hello - Message sent when starting a communication with the backend + * ping - Ping message + * latency - The Agent will calculate the latency and send it to the Backend + * data - Specific message for a given receiver. It can be a metric itself or something else + * bye - Message sent when the Agent wants to close the communication explicitly + + #### hello call + + abstract request: + + ```json + { + id: String() # agent id + secret: String() # secret string for authentication/authorisation + data: Object() # optional data for receivers (i.e. Dashboard needs specific data here) + } + ``` + + response: + + ```json + { + result: String() # “success” or “error” + payload: any() # optional payload + } + ``` + + #### ping call + + abstract request: + + ```json + { + id: String() # agent id + secret: String() # secret string for authentication/authorisation + } + ``` + + response: + + ```json + + { + result: String() # “success” or “error” + payload: String() # optional payload + } + ``` + + #### latency call + + abstract request: + + ```json + { + id: String() # agent id + secret: String() # secret string for authentication/authorisation + latency: Float() # latency in milliseconds + } + ``` + + response: + + ```json + + { + result: String() # “success” or “error” + payload: String() # optional payload + } + ``` + + #### data call + + abstract request: + + ```json + { + id: String() # agent id + secret: String() # secret string for authentication/authorisation + type: String() # data type (for now only ethereum_metrics) + data: Object() # metric data itself + } + ``` + + response: + + ```json + + { + result: String() # “success” or “error” + payload: String() # optional payload + } + ``` + + #### bye call + + abstract request: + + ```json + { + id: String() # agent id + secret: String() # secret string for authentication/authorisation + } + ``` + + response: + + ```json + + { + result: String() # “success” or “error” + payload: String() # optional payload + } + ``` + + """ +end diff --git a/lib/poa_backend/protocol/data_type.ex b/lib/poa_backend/protocol/data_type.ex new file mode 100644 index 0000000..cf72c24 --- /dev/null +++ b/lib/poa_backend/protocol/data_type.ex @@ -0,0 +1,14 @@ +defmodule POABackend.Protocol.DataType do + + @moduledoc """ + The protocol messages Data type. + + Only one Data type is supported now in the backend and it is the ethereum metric + """ + + @typedoc """ + The Message Data Type. For now only ethereum metrics allowed + """ + @type t :: :ethereum_metric + +end \ No newline at end of file diff --git a/lib/poa_backend/protocol/message.ex b/lib/poa_backend/protocol/message.ex new file mode 100644 index 0000000..15f7bbc --- /dev/null +++ b/lib/poa_backend/protocol/message.ex @@ -0,0 +1,72 @@ +defmodule POABackend.Protocol.Message do + alias __MODULE__ + + @moduledoc """ + The message received from the Agent (inspired in [`Plug.Conn`](https://hexdocs.pm/plug/Plug.Conn.html)). + + This module defines the Message received from the Agent and the main functions in order + to work with it. + + ## Message Fields + + * `agent_id` - The Agent Id which sent the message to the backend. + * `receivers` - The list of the receivers which are going to receive this message. This list is retrieved from the config file and is mapped to the `data_type` + * `data_type` - The kind of data the message is carring. For now only `ethereum_metric` type is defined. + * `message_type` - This is the message type according to the custom protocol. Only `hello`, `data` and `latency` are defined + * `assigns` - Shared user data as a map + * `peer` - The actual TCP peer that connected, example: `{{127, 0, 0, 1}, 12345}`. + * `data` - The message payloda. It is a map + + """ + + defstruct [ + agent_id: nil, + receivers: [], + data_type: nil, + message_type: nil, + assigns: %{}, + peer: nil, + data: nil + ] + + @typedoc """ + The Message struct. + + That keeps all the message data and metadata + """ + @type t :: %__MODULE__{ + agent_id: String.t(), + receivers: [atom()], + data_type: POABackend.CustomProtocol.DataType.t(), + message_type: POABackend.CustomProtocol.MessageType.t(), + assigns: %{atom() => any()}, + peer: {:inet.ip_address(), :inet.port_number()}, + data: Map.t() + } + + @doc """ + Returns a new Message Struct + """ + @spec new() :: t + def new() do + %Message{} + end + + @doc """ + Assigns a value to a key in the connection. + + ## Examples + iex> alias POABackend.Protocol.Message + iex> message = Message.new() + iex> message.assigns[:hello] + nil + iex> message = Message.assign(message, :hello, :world) + iex> message.assigns[:hello] + :world + """ + @spec assign(t, atom, term) :: t + def assign(%Message{assigns: assigns} = message, key, value) when is_atom(key) do + %{message | assigns: Map.put(assigns, key, value)} + end + +end \ No newline at end of file diff --git a/lib/poa_backend/protocol/message_type.ex b/lib/poa_backend/protocol/message_type.ex new file mode 100644 index 0000000..feff9b3 --- /dev/null +++ b/lib/poa_backend/protocol/message_type.ex @@ -0,0 +1,19 @@ +defmodule POABackend.Protocol.MessageType do + + @moduledoc """ + Regarding the POA Protocol only 3 types of message can be processed in the backend. + + Those message types are + + * `hello` - When the communication starts + * `data` - When data is sent to the backend. Data is also called "metric data" + * `latency` - When the agent sent the latency value + + """ + + @typedoc """ + The message type. Only `hello`, `data` and `latency` are allowed + """ + @type t :: :hello | :data | :latency + +end \ No newline at end of file diff --git a/mix.exs b/mix.exs index af0f37f..fdf7d58 100644 --- a/mix.exs +++ b/mix.exs @@ -1,13 +1,17 @@ -defmodule PoaBackend.MixProject do +defmodule POABackend.MixProject do use Mix.Project + @version "0.1.0" + def project do [ app: :poa_backend, - version: "0.1.0", + version: @version, elixir: "~> 1.6", start_permanent: Mix.env() == :prod, deps: deps(), + aliases: aliases(), + docs: docs(), test_coverage: [tool: ExCoveralls] ] end @@ -15,7 +19,7 @@ defmodule PoaBackend.MixProject do # Run "mix help compile.app" to learn about applications. def application do [ - extra_applications: [:logger], + extra_applications: [:logger, :cowboy, :plug], mod: {POABackend.Application, []} ] end @@ -23,10 +27,40 @@ defmodule PoaBackend.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ + {:cowboy, "~> 1.0.0"}, + {:plug, "~> 1.0"}, + # Tests {:credo, "~> 0.9", only: [:dev, :test], runtime: false}, {:dialyxir, "~> 0.5", only: [:dev], runtime: false}, - {:excoveralls, "~> 0.8", only: [:test, :dev], runtime: false} + {:excoveralls, "~> 0.8", only: [:test, :dev], runtime: false}, + + # Docs + {:ex_doc, "~> 0.18", only: :dev, runtime: false} ] end + + defp docs do + [ + source_ref: "v#{@version}", + main: "POABackend", + source_url: "https://github.com/poanetwork/poa-netstats-wharehouse", + groups_for_modules: [ + "POA Protocol": [ + POABackend.Protocol, + POABackend.Protocol.Message, + POABackend.Protocol.MessageType, + POABackend.Protocol.DataType + ] + ] + ] + end + + defp aliases do + [docs: ["docs", &picture/1]] + end + + defp picture(_) do + File.cp("./assets/backend_architecture.png", "./doc/backend_architecture.png") + end end diff --git a/test/poa_backend_test.exs b/test/poa_backend_test.exs deleted file mode 100644 index 9fc4094..0000000 --- a/test/poa_backend_test.exs +++ /dev/null @@ -1,8 +0,0 @@ -defmodule POABackendTest do - use ExUnit.Case - doctest POABackend - - test "greets the world" do - assert POABackend.hello() == :world - end -end diff --git a/test/protocol/message_test.exs b/test/protocol/message_test.exs new file mode 100644 index 0000000..f93d5d8 --- /dev/null +++ b/test/protocol/message_test.exs @@ -0,0 +1,4 @@ +defmodule Protocol.MessageTest do + use ExUnit.Case + doctest POABackend.Protocol.Message +end \ No newline at end of file