Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions tools/astarte_export/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Astarte Export is an easy to use tool that allows to exporting all the devices and data from an existing Astarte realm to XML format.

```iex
iex(astarte_export@127.0.0.1)6> Astarte.Export.export_realm_data("test", "test.xml")
iex(astarte_export@127.0.0.1)6> Astarte.Export.export_realm_data("test", "test.xml", [{:device_id, "ogmcilZpRDe"}, {:db_host_and_port, "localhost:9042"}])
level=info ts=2020-02-03T03:57:21.412+00:00 msg="Export started." module=Astarte.Export function=generate_xml/2 realm=test tag=export_started
level=info ts=2020-02-03T03:57:21.413+00:00 msg="Connecting to \"172.23.0.3\":\"9042\" cassandra database." module=Astarte.Export.FetchData.Queries function=get_connection/0
level=info ts=2020-02-03T03:57:21.414+00:00 msg="Connected to database." module=Astarte.Export.FetchData.Queries function=get_connection/0
Expand Down Expand Up @@ -40,12 +40,26 @@ To export data for all devices in a realm:
To export data for a single device in a realm:

```bash
mix astarte.export <REALM> <FILE_XML> <DEVICE_ID>
mix astarte.export <REALM> <FILE_XML> --device_id <DEVICE_ID>
```

- `<REALM>`: The name of the Astarte realm.
- `<FILE_XML>`: The output file path where the exported data will be saved.
- `<DEVICE_ID>`: The unique identifier of the device (e.g., "ogmcilZpRDeDWwuNfJr0yA").
- `--device_id <DEVICE_ID>`(optional): The unique identifier of the device (e.g., "ogmcilZpRDeDWwuNfJr0yA").

## Export Data for a Realm with a Custom Database Host and Port

To export data from a specific Astarte realm with a custom database host and port:

```bash
mix astarte.export <REALM> <FILE_XML> --device_id <DEVICE_ID> --db_host_and_port <DB_HOST_AND_PORT>
```

- `<REALM>`: The name of the Astarte realm.
- `<FILE_XML>`: The output file path where the exported data will be saved.
- `--device_id <DEVICE_ID>` (optional): The unique identifier of the device (e.g., "ogmcilZpRDeDWwuNfJr0yA").
- `--db_host_and_port <DB_HOST_AND_PORT>` (optional):
The host and port of the database in the format host:port (e.g., "localhost:9042"").

## Example Commands

Expand Down
11 changes: 9 additions & 2 deletions tools/astarte_export/lib/astarte/export.ex
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,14 @@ defmodule Astarte.Export do
- options -> options to export the realm data.
"""

@spec export_realm_data(String.t(), String.t(), keyword()) ::
@spec export_realm_data(
String.t(),
String.t(),
[{:db_host_and_port, String.t()} | {:device_id, String.t()}] | []
) ::
:ok | {:error, :invalid_parameters} | {:error, any()}

@spec export_realm_data(String.t(), String.t()) ::
:ok | {:error, :invalid_parameters} | {:error, any()}

def export_realm_data(realm, file, opts \\ []) do
Expand All @@ -54,7 +61,7 @@ defmodule Astarte.Export do
with {:ok, state} <- XMLGenerate.xml_write_default_header(fd),
{:ok, state} <- XMLGenerate.xml_write_start_tag(fd, {"astarte", []}, state),
{:ok, state} <- XMLGenerate.xml_write_start_tag(fd, {"devices", []}, state),
{:ok, conn} <- FetchData.db_connection_identifier(),
{:ok, conn} <- FetchData.db_connection_identifier(opts),
{:ok, state} <- process_devices(conn, realm, fd, state, opts),
{:ok, state} <- XMLGenerate.xml_write_end_tag(fd, state),
{:ok, _state} <- XMLGenerate.xml_write_end_tag(fd, state),
Expand Down
4 changes: 2 additions & 2 deletions tools/astarte_export/lib/astarte/fetchdata/fetchdata.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ defmodule Astarte.Export.FetchData do
booleanarray: :boolean
}

def db_connection_identifier() do
with {:ok, conn_ref} <- Queries.get_connection() do
def db_connection_identifier(opts \\ []) do
with {:ok, conn_ref} <- Queries.get_connection(opts) do
{:ok, conn_ref}
else
_ -> {:error, :connection_setup_failed}
Expand Down
20 changes: 15 additions & 5 deletions tools/astarte_export/lib/astarte/fetchdata/queries/queries.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,22 @@ defmodule Astarte.Export.FetchData.Queries do

require Logger

def get_connection() do
host = System.get_env("CASSANDRA_DB_HOST")
port = System.get_env("CASSANDRA_DB_PORT")
Logger.info("Connecting to #{inspect(host)}:#{inspect(port)} cassandra database.")
def get_connection(opts \\ []) do
db_host_and_port = Keyword.get(opts, :db_host_and_port)

with {:ok, xandra_conn} <- Xandra.start_link(nodes: ["#{host}:#{port}"], atom_keys: true) do
[db_host, db_port] =
case db_host_and_port do
nil ->
[System.get_env("CASSANDRA_DB_HOST"), System.get_env("CASSANDRA_DB_PORT")]

_ ->
String.split(db_host_and_port, ":")
end

Logger.info("Connecting to #{inspect(db_host)}:#{inspect(db_port)} cassandra database.")

with {:ok, xandra_conn} <-
Xandra.start_link(nodes: ["#{db_host}:#{db_port}"], atom_keys: true) do
Logger.info("Connected to database.")
{:ok, xandra_conn}
else
Expand Down
53 changes: 31 additions & 22 deletions tools/astarte_export/lib/mix/tasks/astarte_export.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,43 @@ defmodule Mix.Tasks.Astarte.Export do
require Logger

@impl Mix.Task
@shortdoc "export data from an existing Astarte realm"
@shortdoc "Export data from an existing Astarte realm"
def run(args) do
case args do
[realm, file_name] ->
Logger.info("Exporting data from realm #{realm} to file #{file_name}")
{realm, file_name, device_id, db_host_and_port} = parse_args(args)

case Application.ensure_all_started(:astarte_export) do
{:ok, _} ->
Export.export_realm_data(realm, file_name)
Logger.info("Exporting data from realm #{realm} to file #{file_name}")

{:error, reason} ->
Logger.error("Cannot start applications: #{inspect(reason)}")
end
options =
case {db_host_and_port, device_id} do
{nil, nil} -> []
{nil, _} -> [device_id: device_id]
{_, nil} -> [db_host_and_port: db_host_and_port]
{_, _} -> [db_host_and_port: db_host_and_port, device_id: device_id]
end

[realm, file_name, device_id] ->
Logger.info(
"Exporting data for device #{device_id} from realm #{realm} to file #{file_name}"
)
case Application.ensure_all_started(:astarte_export) do
{:ok, _} ->
Export.export_realm_data(realm, file_name, options)

options = [device_id: device_id]
{:error, reason} ->
Logger.error("Cannot start applications: #{inspect(reason)}")
end
end

case Application.ensure_all_started(:astarte_export) do
{:ok, _} ->
Export.export_realm_data(realm, file_name, options)
defp parse_args([realm, file_name | opts]) do
{device_id, db_host_and_port} = parse_optional_args(opts)
{realm, file_name, device_id, db_host_and_port}
end

{:error, reason} ->
Logger.error("Cannot start applications: #{inspect(reason)}")
end
end
defp parse_optional_args([]), do: {nil, nil}

defp parse_optional_args(["--device_id", device_id | rest]) do
{device_id, parse_optional_args(rest) |> elem(1)}
end

defp parse_optional_args(["--db_host_and_port", db_host_and_port | rest]) do
{parse_optional_args(rest) |> elem(0), db_host_and_port}
end

defp parse_optional_args([_ | rest]), do: parse_optional_args(rest)
end
24 changes: 24 additions & 0 deletions tools/astarte_export/test/astarte_export_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ defmodule Astarte.ExportTest do
alias Astarte.Export.FetchData
alias Astarte.DatabaseTestdata
@realm "test"
@db_host_and_port "localhost:9042"

@expected_xml """
<?xml version="1.0" encoding="UTF-8"?>
Expand Down Expand Up @@ -77,12 +78,35 @@ defmodule Astarte.ExportTest do
assert @expected_xml == File.read!(file)
end

test "export realm data to xml file with db_host_and_port" do
DatabaseTestdata.initialize_database()
assert {:ok, :export_completed} ==
Export.export_realm_data(@realm, "test.xml", [db_host_and_port: @db_host_and_port])
file = Path.expand("test.xml") |> Path.absname()
assert @expected_xml == File.read!(file)
end

test "export realm data to xmlfile in a absolute path " do
file = File.cwd!() <> "/test.xml"
assert {:ok, :export_completed} == Export.export_realm_data(@realm, file)
assert @expected_xml == File.read!(file)
end

test "export realm data to xml file with db_host_and_port in absolute path" do
file = File.cwd!() <> "/test.xml"
assert {:ok, :export_completed} == Export.export_realm_data(@realm, file, [db_host_and_port: @db_host_and_port])
assert @expected_xml == File.read!(file)
end

test "export fails with invalid realm" do
assert {:error, :database_error} == Export.export_realm_data("invalid_realm", "test.xml")
end

test "export fails with database connection error" do
assert {:error, :database_connection_error} ==
Export.export_realm_data(@realm, "test.xml", [db_host_and_port: "invalid_host:0000"])
end

test "test to export xml data " do
{:ok, stdio} = read_stdio_output_on_port()
{:ok, state} = XMLGenerate.xml_write_default_header(:standard_error)
Expand Down