Wire initial VM management frontend

This commit is contained in:
Timothée Floure 2021-04-07 17:23:48 +02:00
parent 053d5a3320
commit 7e6439be67
Signed by: tfloure
GPG key ID: 4502C902C00A1E12
8 changed files with 242 additions and 1 deletions

0
lib/opennebula.ex Normal file
View file

View file

@ -22,12 +22,88 @@ defmodule RecycledCloud.OpenNebula.VM do
{:cloning_failure, 11}
]
@actions [
"terminate-hard",
"terminate",
"undeploy-hard",
"undeploy",
"poweroff-hard",
"poweroff",
"reboot-hard",
"reboot",
"hold",
"release",
"stop",
"suspend",
"resume",
"resched",
"unresched"
]
@events [
{:NONE_ACTION , 0},
{:MIGRATE_ACTION , 1},
{:LIVE_MIGRATE_ACTION , 2},
{:SHUTDOWN_ACTION , 3},
{:SHUTDOWN_HARD_ACTION , 4},
{:UNDEPLOY_ACTION , 5},
{:UNDEPLOY_HARD_ACTION , 6},
{:HOLD_ACTION , 7},
{:RELEASE_ACTION , 8},
{:STOP_ACTION , 9},
{:SUSPEND_ACTION , 10},
{:RESUME_ACTION , 11},
{:BOOT_ACTION , 12},
{:DELETE_ACTION , 13},
{:DELETE_RECREATE_ACTION , 14},
{:REBOOT_ACTION , 15},
{:REBOOT_HARD_ACTION , 16},
{:RESCHED_ACTION , 17},
{:UNRESCHED_ACTION , 18},
{:POWEROFF_ACTION , 19},
{:POWEROFF_HARD_ACTION , 20},
{:DISK_ATTACH_ACTION , 21},
{:DISK_DETACH_ACTION , 22},
{:NIC_ATTACH_ACTION , 23},
{:NIC_DETACH_ACTION , 24},
{:DISK_SNAPSHOT_CREATE_ACTION , 25},
{:DISK_SNAPSHOT_DELETE_ACTION , 26},
{:TERMINATE_ACTION , 27},
{:TERMINATE_HARD_ACTION , 28},
{:DISK_RESIZE_ACTION , 29},
{:DEPLOY_ACTION , 30},
{:CHOWN_ACTION , 31},
{:CHMOD_ACTION , 32},
{:UPDATECONF_ACTION , 33},
{:RENAME_ACTION , 34},
{:RESIZE_ACTION , 35},
{:UPDATE_ACTION , 36},
{:SNAPSHOT_CREATE_ACTION , 37},
{:SNAPSHOT_DELETE_ACTION , 38},
{:SNAPSHOT_REVERT_ACTION , 39},
{:DISK_SAVEAS_ACTION , 40},
{:DISK_SNAPSHOT_REVERT_ACTION , 41},
{:RECOVER_ACTION , 42},
{:RETRY_ACTION , 43},
{:MONITOR_ACTION , 44},
{:DISK_SNAPSHOT_RENAME_ACTION , 45},
{:ALIAS_ATTACH_ACTION , 46},
{:ALIAS_DETACH_ACTION , 47},
]
def state_for(state) when is_atom(state) do
@states |> Keyword.get(state)
end
def state_for(state) when is_integer(state) do
case Enum.find(@states, fn {atom, value} -> value == state end) do
case Enum.find(@states, fn {_atom, value} -> value == state end) do
{atom, _value} -> atom
nil -> nil
end
end
def event_for(event) when is_integer(event) do
case Enum.find(@events, fn {_atom, value} -> value == event end) do
{atom, _value} -> atom
nil -> nil
end
@ -43,4 +119,23 @@ defmodule RecycledCloud.OpenNebula.VM do
{:error, err} -> {:error, err}
end
end
def get_by_username(username) do
{:ok, %{VM: vms}} = RecycledCloud.OpenNebula.VMPool.get(%{
filter_flag: :all,
range_start: :infinite,
range_end: :infinite,
state_filter: state_for(:any_except_done),
kv_filter: ""
})
Enum.filter(vms, fn vm -> List.to_string(Map.get(vm, :UNAME)) == username end)
end
def execute(vm_id, action) when action in @actions do
case ONE.query("one.vm.action", [action, vm_id]) do
{:ok, _raw} -> :ok
{:error, err} -> {:error, err}
end
end
end

View file

@ -0,0 +1,58 @@
defmodule RecycledCloudWeb.VirtualMachineHostingController do
use RecycledCloudWeb, :controller
alias RecycledCloud.OpenNebula.VM
def index(conn, _params) do
username = conn.assigns.current_user.username
vms = VM.get_by_username(username)
render(conn, "index.html", vms: vms)
end
def start(conn, %{"id" => id}) do
case VM.execute(String.to_integer(id), "resume") do
:ok ->
conn
|> put_flash(:info, "Start request sent to VMM.")
|> redirect(to: Routes.virtual_machine_hosting_path(conn, :index))
{:error, err} ->
conn
|> put_flash(:error, "Something went wrong: #{inspect(err)}")
|> redirect(to: Routes.virtual_machine_hosting_path(conn, :index))
end
end
def stop(conn, %{"id" => id}) do
case VM.execute(String.to_integer(id), "poweroff") do
:ok ->
conn
|> put_flash(:stop, "Stop request sent to VMM.")
|> redirect(to: Routes.virtual_machine_hosting_path(conn, :index))
{:error, err} ->
conn
|> put_flash(:error, "Something went wrong: #{inspect(err)}")
|> redirect(to: Routes.virtual_machine_hosting_path(conn, :index))
end
end
def show(conn, %{"id" => id}) do
case VM.get(String.to_integer(id)) do
{:error, err} ->
conn
|> put_flash(:error, "Could not fetch VM details: #{inspect(err)}")
|> redirect(to: Routes.virtual_machine_hosting_path(conn, :index))
{:ok, vm} ->
owner = vm |> Map.get(:UNAME) |> List.to_string
if owner == conn.assigns.current_user.username do
conn
|> assign(:vm, vm)
|> render("show.html")
else
conn
|> put_flash(:error, "You are not the owner of this machine.")
|> redirect(to: Routes.virtual_machine_hosting_path(conn, :index))
end
end
end
end

View file

@ -74,6 +74,11 @@ defmodule RecycledCloudWeb.Router do
get "/users/settings/keys/:key_id/delete", UserKeysController, :delete
get "/billing", BillingController, :index
post "/billing/partner/update", BillingController, :update
get "/hosting/vm", VirtualMachineHostingController, :index
get "/hosting/vm/:id", VirtualMachineHostingController, :show
get "/hosting/vm/:id/start", VirtualMachineHostingController, :start
get "/hosting/vm/:id/stop", VirtualMachineHostingController, :stop
end
scope "/", RecycledCloudWeb do

View file

@ -6,13 +6,25 @@
<li><%= link "Settings", to: Routes.user_settings_path(@conn, :edit) %></li>
<li><%= link "Billing", to: Routes.billing_path(@conn, :index) %></li>
<li><%= link "Log out", to: Routes.user_session_path(@conn, :delete), method: :delete %></li>
<hr />
<!--
<li><%= link "Web Hosting", to: Routes.page_path(@conn, :index) %></li>
<li><%= link "Name Service", to: Routes.page_path(@conn, :index) %></li>
<li><%= link "Storage Services", to: Routes.page_path(@conn, :index) %></li>
-->
<li><%= link "Virtual Machines", to: Routes.virtual_machine_hosting_path(@conn, :index) %></li>
<% else %>
<li><%= link "Log in", to: Routes.user_session_path(@conn, :new) %></li>
<li><%= link "Register", to: Routes.user_registration_path(@conn, :new) %></li>
<% end %>
<hr />
<li>
<a href="https://status.recycled.cloud">
Infrastructure status
</a>
</li>
<a href="https://recycled.cloud">
Back to recycled.cloud &crarr;
</a>
</li>
</ul>

View file

@ -0,0 +1,39 @@
<h1>Virtual Machines</h1>
<p>This page list all the Virtual Machines linked to your account. It is
not possible to interect with them yet.</p>
<%= for location <- ["LNTH"] do %>
<h2>Location: <%= location %></h2>
<table>
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>State</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<%= for vm <- @vms do %>
<tr>
<td><%= Map.get(vm, :ID) %></td>
<td><%= Map.get(vm, :NAME) %></td>
<td><%= VM.state_for(Map.get(vm, :STATE)) %></td>
<td>
<%= case VM.state_for(Map.get(vm, :STATE)) do
:poweroff ->
link "start", to: Routes.virtual_machine_hosting_path(@conn, :start, Map.get(vm, :ID))
:active ->
link "stop", to: Routes.virtual_machine_hosting_path(@conn, :stop, Map.get(vm, :ID))
_ -> ""
end %>
<%= link "show details", to: Routes.virtual_machine_hosting_path(@conn, :show, Map.get(vm, :ID)) %>
</td>
</tr>
<% end %>
</tbody>
</table>
<% end %>

View file

@ -0,0 +1,27 @@
<h1>VM#<%= Map.get(@vm, :ID) %> - <%= Map.get(@vm, :NAME) %></h1>
<ul>
<li>State: <b><%= VM.state_for(Map.get(@vm, :STATE)) %></b></li>
<li>LCM State: <b><%= Map.get(@vm, :LCM_STATE) %></b></li<
</ul>
<h2>History</h2>
<table>
<thead>
<tr>
<th>Time</th>
<th>Action</th>
<th>Host</th>
</tr>
</thead>
<tbody>
<%= for entry <- @vm |> Map.get(:HISTORY_RECORDS) |> Map.get(:HISTORY) do %>
<tr>
<td><%= DateTime.from_unix!(Map.get(entry, :STIME)) %></td>
<td><%= VM.event_for(Map.get(entry, :ACTION)) %></td>
<td><%= Map.get(entry, :HOSTNAME) %></td>
</tr>
<% end %>
</tbody>
</table>

View file

@ -0,0 +1,5 @@
defmodule RecycledCloudWeb.VirtualMachineHostingView do
use RecycledCloudWeb, :view
alias RecycledCloud.OpenNebula.VM
end