diff --git a/lib/network/model/network/connection.ex b/lib/network/model/network/connection.ex index 48a8ad8a..ce1974a3 100644 --- a/lib/network/model/network/connection.ex +++ b/lib/network/model/network/connection.ex @@ -65,7 +65,7 @@ defmodule Helix.Network.Model.Network.Connection do |> validate_required(@required_fields) end - @spec update_nic(t, Component.nic) :: + @spec update_nic(t, Component.nic | nil) :: changeset def update_nic(nc = %__MODULE__{}, nic = %Component{type: :nic}) do nc diff --git a/lib/server/action/flow/server.ex b/lib/server/action/flow/server.ex index 0f0abe3f..5eac690a 100644 --- a/lib/server/action/flow/server.ex +++ b/lib/server/action/flow/server.ex @@ -81,8 +81,6 @@ defmodule Helix.Server.Action.Flow.Server do defp update_server_mobo(server = %Server{motherboard_id: mobo_id}, mobo_id), do: {:ok, server} - defp update_server_mobo(server, nil), - do: ServerAction.detach(server) defp update_server_mobo(server, mobo_id), do: ServerAction.attach(server, mobo_id) end diff --git a/lib/server/action/server.ex b/lib/server/action/server.ex index 89301f07..40a3c013 100644 --- a/lib/server/action/server.ex +++ b/lib/server/action/server.ex @@ -41,7 +41,7 @@ defmodule Helix.Server.Action.Server do @spec attach(Server.t, Motherboard.id) :: {:ok, Server.t} - | {:error, Ecto.Changeset.t} + | {:error, :internal} @doc """ Attaches a motherboard to the server @@ -49,18 +49,31 @@ defmodule Helix.Server.Action.Server do are already attached """ def attach(server, motherboard_id) do - ServerInternal.attach(server, motherboard_id) + case ServerInternal.attach(server, motherboard_id) do + {:ok, server} -> + {:ok, server} + + {:error, _} -> + {:error, :internal} + end end @spec detach(Server.t) :: - :ok + {:ok, Server.t} + | {:error, :internal} @doc """ Detaches the motherboard linked to server This function is idempotent """ def detach(server) do - ServerInternal.detach(server) + case ServerInternal.detach(server) do + {:ok, server} -> + {:ok, server} + + {:error, _} -> + {:error, :internal} + end end @spec delete(Server.t) :: diff --git a/lib/server/internal/server.ex b/lib/server/internal/server.ex index c745d3f8..e63dffc4 100644 --- a/lib/server/internal/server.ex +++ b/lib/server/internal/server.ex @@ -59,15 +59,18 @@ defmodule Helix.Server.Internal.Server do end @spec detach(Server.t) :: - :ok + repo_return def detach(server = %Server{}) do - server - |> Server.detach_motherboard() - |> Repo.update!() + result = + server + |> Server.detach_motherboard() + |> Repo.update() - CacheAction.update_server(server) + with {:ok, _} <- result do + CacheAction.update_server(server) + end - :ok + result end @spec delete(Server.t) :: diff --git a/lib/server/model/motherboard.ex b/lib/server/model/motherboard.ex index 37de3666..b266ab06 100644 --- a/lib/server/model/motherboard.ex +++ b/lib/server/model/motherboard.ex @@ -329,7 +329,7 @@ defmodule Helix.Server.Model.Motherboard do alias Helix.Server.Model.Component - @spec by_motherboard(Queryable.t, Motherboard.idt) :: + @spec by_motherboard(Queryable.t, Motherboard.idt, [eager: boolean]) :: Queryable.t def by_motherboard(query \\ Motherboard, motherboard_id, eager?) def by_motherboard(query, mobo = %Component{type: :mobo}, eager?), diff --git a/lib/server/public/index.ex b/lib/server/public/index.ex index 55cbb2ea..8c68b573 100644 --- a/lib/server/public/index.ex +++ b/lib/server/public/index.ex @@ -14,6 +14,7 @@ defmodule Helix.Server.Public.Index do alias Helix.Software.Model.Storage alias Helix.Software.Public.Index, as: FileIndex alias Helix.Server.Model.Server + alias Helix.Server.Public.Index.Hardware, as: HardwareIndex alias Helix.Server.Query.Server, as: ServerQuery @type index :: @@ -72,6 +73,7 @@ defmodule Helix.Server.Public.Index do logs: LogIndex.index, main_storage: Storage.id, storages: FileIndex.index, + hardware: HardwareIndex.index, processes: ProcessIndex.index, tunnels: NetworkIndex.index, } @@ -84,6 +86,7 @@ defmodule Helix.Server.Public.Index do logs: LogIndex.rendered_index, main_storage: String.t, storages: FileIndex.rendered_index, + hardware: HardwareIndex.rendered_index, processes: ProcessIndex.index, tunnels: NetworkIndex.rendered_index, } @@ -93,7 +96,8 @@ defmodule Helix.Server.Public.Index do nips: [Network.nip], logs: LogIndex.index, main_storage: Storage.id, - storages: FileIndex.rendered_index, + storages: FileIndex.index, + hardware: HardwareIndex.index, processes: ProcessIndex.index, tunnels: NetworkIndex.index } @@ -104,6 +108,7 @@ defmodule Helix.Server.Public.Index do logs: LogIndex.rendered_index, main_storage: String.t, storages: FileIndex.rendered_index, + hardware: HardwareIndex.rendered_index, processes: ProcessIndex.index, tunnels: NetworkIndex.rendered_index } @@ -280,6 +285,7 @@ defmodule Helix.Server.Public.Index do filesystem_index = FileIndex.index(server.server_id) tunnel_index = NetworkIndex.index(server.server_id) process_index = ProcessIndex.index(server.server_id, entity_id) + hardware_index = HardwareIndex.index(server) main_storage_id = server.server_id @@ -291,6 +297,7 @@ defmodule Helix.Server.Public.Index do logs: log_index, main_storage: main_storage_id, storages: filesystem_index, + hardware: hardware_index, processes: process_index, tunnels: tunnel_index } @@ -311,6 +318,7 @@ defmodule Helix.Server.Public.Index do logs: LogIndex.render_index(server.logs), main_storage: server.main_storage |> to_string(), storages: FileIndex.render_index(server.storages), + hardware: HardwareIndex.render_index(server.hardware), processes: server.processes, tunnels: NetworkIndex.render_index(server.tunnels) } diff --git a/lib/server/public/index/hardware.ex b/lib/server/public/index/hardware.ex new file mode 100644 index 00000000..f7f49874 --- /dev/null +++ b/lib/server/public/index/hardware.ex @@ -0,0 +1,31 @@ +defmodule Helix.Server.Public.Index.Hardware do + + alias Helix.Server.Model.Server + alias Helix.Server.Public.Index.Motherboard, as: MotherboardIndex + + @type index :: + %{ + motherboard: MotherboardIndex.index | nil + } + + @type rendered_index :: + %{ + motherboard: MotherboardIndex.rendered_index | nil + } + + @spec index(Server.t) :: + index + def index(server = %Server{}) do + %{ + motherboard: MotherboardIndex.index(server) + } + end + + @spec render_index(index) :: + rendered_index + def render_index(index) do + %{ + motherboard: MotherboardIndex.render_index(index.motherboard) + } + end +end diff --git a/lib/server/public/index/motherboard.ex b/lib/server/public/index/motherboard.ex new file mode 100644 index 00000000..87e22eeb --- /dev/null +++ b/lib/server/public/index/motherboard.ex @@ -0,0 +1,97 @@ +defmodule Helix.Server.Public.Index.Motherboard do + + alias Helix.Network.Query.Network, as: NetworkQuery + alias Helix.Server.Model.Motherboard + alias Helix.Server.Model.Server + alias Helix.Server.Query.Motherboard, as: MotherboardQuery + + @type index :: term + @type rendered_index :: term + + def index(%Server{motherboard_id: nil}) do + %{ + motherboard_id: nil, + slots: %{}, + network_connections: %{} + } + end + + def index(server = %Server{}) do + motherboard = MotherboardQuery.fetch(server.motherboard_id) + + network_connections = + motherboard + |> MotherboardQuery.get_nics() + |> Enum.reduce([], fn nic, acc -> + + if nic do + nc = NetworkQuery.Connection.fetch_by_nic(nic) + + acc ++ [nc] + else + acc + end + end) + + %{ + motherboard: motherboard, + network_connections: network_connections, + } + end + + def render_index(index = %{motherboard_id: nil}), + do: index + + def render_index(index) do + %{ + motherboard_id: to_string(index.motherboard.motherboard_id), + slots: render_slots(index.motherboard), + network_connections: render_network_connections(index.network_connections) + } + end + + defp render_slots(motherboard = %Motherboard{}) do + used_slots = + motherboard.slots + |> Enum.map(fn {slot_id, component} -> + comp_data = + %{ + type: to_string(component.type), + component_id: to_string(component.component_id) + } + + {slot_id, comp_data} + end) + |> Enum.into(%{}) + + free_slots = + motherboard + |> MotherboardQuery.get_free_slots() + |> Enum.reduce(%{}, fn {comp_type, free_slots}, acc -> + free_slots + |> Enum.map(fn slot_id -> + + {slot_id, %{type: to_string(comp_type), component_id: nil}} + end) + |> Enum.into(%{}) + |> Map.merge(acc) + end) + + Map.merge(used_slots, free_slots) + end + + defp render_network_connections(network_connections) do + network_connections + |> Enum.reduce(%{}, fn nc, acc -> + client_nip = + %{ + network_id: to_string(nc.network_id), + ip: nc.ip + } + + %{} + |> Map.put(to_string(nc.nic_id), client_nip) + |> Map.merge(acc) + end) + end +end diff --git a/lib/server/query/motherboard.ex b/lib/server/query/motherboard.ex index 410048a7..e716bd89 100644 --- a/lib/server/query/motherboard.ex +++ b/lib/server/query/motherboard.ex @@ -32,4 +32,9 @@ defmodule Helix.Server.Query.Motherboard do to: MotherboardInternal defdelegate get_rams(motherboard), to: MotherboardInternal + + defdelegate get_free_slots(motherboard), + to: MotherboardInternal + defdelegate get_free_slots(mobo, motherboard), + to: MotherboardInternal end diff --git a/test/server/public/index/motherboard_test.exs b/test/server/public/index/motherboard_test.exs new file mode 100644 index 00000000..89033f06 --- /dev/null +++ b/test/server/public/index/motherboard_test.exs @@ -0,0 +1,121 @@ +defmodule Helix.Server.Public.Index.MotherboardTest do + + use Helix.Test.Case.Integration + + alias Helix.Server.Public.Index.Motherboard, as: MotherboardIndex + alias Helix.Server.Query.Motherboard, as: MotherboardQuery + alias Helix.Server.Query.Server, as: ServerQuery + + alias Helix.Test.Network.Helper, as: NetworkHelper + alias Helix.Test.Server.Helper, as: ServerHelper + alias Helix.Test.Server.Setup, as: ServerSetup + + @internet_id NetworkHelper.internet_id() + @internet_id_str to_string(@internet_id) + + describe "index/1" do + test "indexes empty motherboard" do + {server, _} = ServerSetup.server() + + # Remove mobo + ServerHelper.update_server_mobo(server, nil) + + # Look mah, no mobo + server = ServerQuery.fetch(server.server_id) + refute server.motherboard_id + + index = MotherboardIndex.index(server) + + refute index.motherboard_id + assert Enum.empty?(index.network_connections) + assert Enum.empty?(index.slots) + end + + test "indexes motherboard" do + {server, _} = ServerSetup.server() + + index = MotherboardIndex.index(server) + + assert index.motherboard == MotherboardQuery.fetch(server.motherboard_id) + assert [nc] = index.network_connections + + assert nc.network_id == @internet_id + assert nc.ip == ServerHelper.get_ip(server) + assert nc.nic_id == index.motherboard.slots.nic_1.component_id + end + end + + describe "render_index/1" do + test "renders empty motherboard" do + {server, _} = ServerSetup.server() + + # Remove mobo + ServerHelper.update_server_mobo(server, nil) + + # Look mah, no mobo + server = ServerQuery.fetch(server.server_id) + refute server.motherboard_id + + rendered = + server + |> MotherboardIndex.index() + |> MotherboardIndex.render_index() + + refute rendered.motherboard_id + assert Enum.empty?(rendered.network_connections) + assert Enum.empty?(rendered.slots) + end + + test "renders the motherboard index" do + {server, _} = ServerSetup.server() + + ServerHelper.update_server_mobo(server, :mobo_999) + + motherboard = MotherboardQuery.fetch(server.motherboard_id) + + [cpu] = MotherboardQuery.get_cpus(motherboard) + [hdd] = MotherboardQuery.get_hdds(motherboard) + [ram] = MotherboardQuery.get_rams(motherboard) + [nic] = MotherboardQuery.get_nics(motherboard) + + ip = ServerHelper.get_ip(server) + + rendered = + server + |> MotherboardIndex.index() + |> MotherboardIndex.render_index() + + assert rendered.motherboard_id == to_string(server.motherboard_id) + + # NC data is valid + assert ncs = rendered.network_connections + assert Map.has_key?(ncs, to_string(nic.component_id)) + + assert %{ + network_id: @internet_id_str, + ip: ip + } == ncs[to_string(nic.component_id)] + + # Slot data is valid + slots = rendered.slots + + assert rendered.slots.cpu_1.component_id == to_string(cpu.component_id) + assert rendered.slots.cpu_1.type == "cpu" + assert rendered.slots.hdd_1.component_id == to_string(hdd.component_id) + assert rendered.slots.hdd_1.type == "hdd" + assert rendered.slots.ram_1.component_id == to_string(ram.component_id) + assert rendered.slots.ram_1.type == "ram" + assert rendered.slots.nic_1.component_id == to_string(nic.component_id) + assert rendered.slots.nic_1.type == "nic" + + # Returned all slots (available and free slots) + assert map_size(slots) > 10 + + # Available slots have an empty `component_id` + refute rendered.slots.cpu_2.component_id + refute rendered.slots.ram_2.component_id + refute rendered.slots.hdd_2.component_id + refute rendered.slots.nic_2.component_id + end + end +end diff --git a/test/server/public/index_test.exs b/test/server/public/index_test.exs index ea6462b3..83b30243 100644 --- a/test/server/public/index_test.exs +++ b/test/server/public/index_test.exs @@ -186,6 +186,7 @@ defmodule Helix.Server.Public.IndexTest do # Info retrieved from sub-Indexes assert gateway.main_storage assert gateway.storages + assert gateway.hardware assert gateway.logs assert gateway.processes assert gateway.tunnels @@ -211,6 +212,7 @@ defmodule Helix.Server.Public.IndexTest do assert rendered.main_storage assert rendered.storages + assert rendered.hardware assert rendered.logs assert rendered.processes assert rendered.tunnels @@ -235,6 +237,7 @@ defmodule Helix.Server.Public.IndexTest do # Info retrieved from sub-Indexes assert remote.main_storage assert remote.storages + assert remote.hardware assert remote.logs assert remote.processes assert remote.tunnels @@ -258,6 +261,7 @@ defmodule Helix.Server.Public.IndexTest do assert rendered.main_storage assert rendered.storages + assert rendered.hardware assert rendered.logs assert rendered.processes assert rendered.tunnels