Skip to content

Commit

Permalink
virtuerl: first working version with remote control
Browse files Browse the repository at this point in the history
  • Loading branch information
verbit committed Mar 8, 2024
1 parent 8fc0b7e commit 1d483e6
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 49 deletions.
15 changes: 15 additions & 0 deletions virtuerl/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,18 @@ Build
-----

$ rebar3 compile


## Running

On the server

```sh
sudo -s ./erts-13.1.5/bin/erl -mode embedded -boot releases/0.7.0+build.61.ref8fc0b7e/start -config releases/0.7.0+build.61.ref8fc0b7e/sys.config -proto_dist inet6_tcp -name [email protected] -setcookie abcdef
```

Locally
```sh
rebar3 compile
erl -name moi -proto_dist inet6_tcp -setcookie abcdef -pa _build/default/lib/*/ebin -hidden
```
2 changes: 1 addition & 1 deletion virtuerl/rebar.config
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
]}.

{relx, [
{release, {virtuerl, git}, [virtuerl, {khepri, load}, {mnesia, load}]},
{release, {virtuerl, git}, [virtuerl, {khepri, load}, {mnesia, load}, erts]},
{mode, prod}
]}.

Expand Down
2 changes: 1 addition & 1 deletion virtuerl/src/virtuerl_mgt.erl
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ handle_continue(setup_base, State) ->
false ->
TempImagePath = "/tmp/virtuerl/debian-12-genericcloud-amd64-20230910-1499.qcow2",
ok = filelib:ensure_dir(TempImagePath),
httpc:request(get, "https://cloud.debian.org/images/cloud/bookworm/20230910-1499/debian-12-genericcloud-arm64-20230910-1499.qcow2", [],
{ok, _} = httpc:request(get, {"https://cloud.debian.org/images/cloud/bookworm/20230910-1499/debian-12-genericcloud-amd64-20230910-1499.qcow2", []}, [],
[{stream, TempImagePath}]),
file:rename(TempImagePath, BaseImagePath)
end,
Expand Down
2 changes: 1 addition & 1 deletion virtuerl/src/virtuerl_qemu.erl
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ create_cloud_config(#{id := DomainID, name := DomainName, mac_addr := MacAddr, c
UserDataPath = filename:join(IsoBasePath, "user-data"),
ok = file:write_file(UserDataPath, UserData),
IsoCmd = ["genisoimage -output ", filename:join(DomainBasePath, "cloud_config.iso"), " -volid cidata -joliet -rock ", UserDataPath, " ", MetaDataPath, " ", NetConfPath],
os:cmd(binary_to_list(iolist_to_binary(IsoCmd))),
ok = virtuerl_util:cmd(binary_to_list(iolist_to_binary(IsoCmd))),
ok = file:del_dir_r(IsoBasePath),
ok.

Expand Down
76 changes: 39 additions & 37 deletions virtuerl/src/virtuerl_ui.erl
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,22 @@
-module(virtuerl_ui).
-include_lib("wx/include/wx.hrl").

-export([start/0,
-export([start/0, start/1,
init/1, handle_info/2, handle_event/2, handle_call/3,
code_change/3, terminate/2]).

-behaviour(wx_object).

-record(state, {win, info_panel, info, domain_panel, domain_info, toolbar, domain_list_box, domains, page, net_list_box}).
-record(state, {win, info_panel, info, domain_panel, domain_info, toolbar, domain_list_box, domains, page, net_list_box, node}).

start() ->
wx_object:start_link(?MODULE, [], []).
start(node()).
start(Node) ->
wx_object:start_link(?MODULE, [Node], []).

%% Init is called in the new process.
init([]) ->
virtuerl_pubsub:subscribe(),
init([Node]) ->
%% virtuerl_pubsub:subscribe(),

wx:new(),
Frame = wxFrame:new(wx:null(),
Expand Down Expand Up @@ -84,7 +86,7 @@ init([]) ->

wxNotebook:addPage(Notebook, NetworkPanel, "Networks"),

{ok, Nets} = virtuerl_ipam:ipam_list_nets(),
{ok, Nets} = erpc:call(Node, virtuerl_ipam, ipam_list_nets, []),
Choices = maps:keys(Nets),
ListBox = wxListBox:new(NetworkSplitter, 42, [{choices, Choices}]),
wxListBox:connect(ListBox, command_listbox_selected), % command_listbox_doubleclicked
Expand All @@ -108,7 +110,7 @@ init([]) ->
wxNotebook:connect(Notebook, command_notebook_page_changed),
wxNotebook:setSelection(Notebook, 1),

Domains = virtuerl_mgt:domains_list(),
Domains = erpc:call(Node, virtuerl_mgt, domains_list, []),
ColumnNames = ["ID", "Name", "IPs", "CPU", "RAM"],
ColumnAlignment = [?wxLIST_FORMAT_LEFT, ?wxLIST_FORMAT_LEFT, ?wxLIST_FORMAT_LEFT, ?wxLIST_FORMAT_RIGHT, ?wxLIST_FORMAT_RIGHT],
DomainsTuples = [{Id, Name, lists:join($,, [virtuerl_net:format_ip(Ip) || {Ip, _Prefixlen} <- Cidrs]), integer_to_binary(Vcpu), [integer_to_binary(Memory), $M]} || #{id := Id, name := Name, cidrs := Cidrs, vcpu := Vcpu, memory := Memory} <- Domains],
Expand Down Expand Up @@ -152,7 +154,7 @@ init([]) ->
wxWindow:raise(Frame),
wxWindow:setFocus(Frame),
wxWindow:layout(Frame),
{Frame, #state{toolbar = Toolbar, page = 1, win=Frame, net_list_box = ListBox, info_panel = Info, info=InfoGrid, domain_panel = DomainInfo, domain_info=DomainInfoGrid, domain_list_box=DomainListBox, domains = DomainsIds}}.
{Frame, #state{node = Node, toolbar = Toolbar, page = 1, win=Frame, net_list_box = ListBox, info_panel = Info, info=InfoGrid, domain_panel = DomainInfo, domain_info=DomainInfoGrid, domain_list_box=DomainListBox, domains = DomainsIds}}.

create_toolbar(Frame, BaseNum) ->
PlayIconDC = wxMemoryDC:new(),
Expand Down Expand Up @@ -233,8 +235,8 @@ handle_event(#wx{event = #wxBookCtrl{nSel = Index}}, State) ->
{noreply, State#state{page = Index}};
handle_event(#wx{id = 42, event = #wxCommand{type = command_listbox_selected,
cmdString = Choice}},
State = #state{info_panel = Panel, info=Info, domains = DomainIds}) ->
{ok, Nets} = virtuerl_ipam:ipam_list_nets(),
State = #state{info_panel = Panel, info=Info, domains = DomainIds, node = Node}) ->
{ok, Nets} = erpc:call(Node, virtuerl_ipam, ipam_list_nets, []),
Net = maps:get(list_to_binary(Choice), Nets),
#{cidr4 := #{address := Address, prefixlen := Prefixlen}} = Net,
wxFlexGridSizer:clear(Info, [{delete_windows, true}]),
Expand All @@ -244,8 +246,8 @@ handle_event(#wx{id = 42, event = #wxCommand{type = command_listbox_selected,
{noreply, State};
handle_event(#wx{event = #wxList{type = command_list_item_selected,
itemIndex = ItemIndex}},
State = #state{domain_panel = DomainPanel, domain_info = DomainInfo, toolbar=Toolbar, domains = DomainIds}) ->
Domains = maps:from_list([{Id, Domain} || Domain = #{id := Id} <- virtuerl_mgt:domains_list()]),
State = #state{domain_panel = DomainPanel, domain_info = DomainInfo, toolbar=Toolbar, domains = DomainIds, node = Node}) ->
Domains = maps:from_list([{Id, Domain} || Domain = #{id := Id} <- erpc:call(Node, virtuerl_mgt, domains_list, [])]),
wxGridBagSizer:clear(DomainInfo, [{delete_windows, true}]),

DomainId = lists:nth(ItemIndex + 1, DomainIds),
Expand Down Expand Up @@ -277,7 +279,7 @@ handle_event(#wx{event = #wxList{type = command_list_item_selected,
%% wxGridBagSizer:add(DomainInfo, SerialOut, {3, 0}, [{span, {1, 2}}, {flag, ?wxEXPAND}]),
%% WebView = wxWebView:new(DomainPanel, 999, [{url, "http://0.0.0.0:9000/noVNC-1.4.0/vnc.html?port=5700&?path=&resize=scale&autoconnect=true"}]),
%% wxGridBagSizer:add(DomainInfo, WebView, {3, 0}, [{span, {1, 2}}, {flag, ?wxEXPAND}]),
VncWindow = virtuerl_vnc:start(DomainPanel, DomainId),
VncWindow = virtuerl_vnc:start(DomainPanel, DomainId, Node),
wxGridBagSizer:add(DomainInfo, VncWindow, {0, 2}, [{span, {3, 1}}, {flag, ?wxEXPAND}]),

wxGridBagSizer:addGrowableRow(DomainInfo, 2),
Expand All @@ -296,53 +298,53 @@ handle_event(#wx{event = #wxList{type = command_list_item_selected,
wxPanel:layout(DomainPanel),
io:format("dblclick ~p (~p)~n", [DomainId, Domain]),
{noreply, State};
handle_event(#wx{id = 4044, event = #wxCommand{type = command_button_clicked}}, #state{domain_list_box = DomainListBox, domains = DomainIds} = State) ->
handle_event(#wx{id = 4044, event = #wxCommand{type = command_button_clicked}}, #state{domain_list_box = DomainListBox, domains = DomainIds, node = Node} = State) ->
SelectedItem = wxListCtrl:getNextItem(DomainListBox, -1, [{state, ?wxLIST_STATE_SELECTED}]),
DomainId = lists:nth(SelectedItem + 1, DomainIds),
{ok, Domain} = virtuerl_mgt:domain_get(#{id => DomainId}),
create_domain_dialog(Domain),
{ok, Domain} = erpc:call(Node, virtuerl_mgt, domain_get, [#{id => DomainId}]),
create_domain_dialog(Node, Domain),
{noreply, State};
handle_event(#wx{id = 100, obj = Toolbar, event = #wxCommand{type = command_menu_selected}} = Event, #state{page = 1, domain_list_box = DomainListBox, domains = DomainIds} = State) ->
handle_event(#wx{id = 100, obj = Toolbar, event = #wxCommand{type = command_menu_selected}} = Event, #state{page = 1, domain_list_box = DomainListBox, domains = DomainIds, node = Node} = State) ->
io:format("~p~n", [Event]),
SelectedItem = wxListCtrl:getNextItem(DomainListBox, -1, [{state, ?wxLIST_STATE_SELECTED}]),
DomainId = lists:nth(SelectedItem + 1, DomainIds),
Choice = DomainId,
io:format("~p~n", [Choice]),
wxToolBar:enableTool(Toolbar, 100, false),
wxToolBar:enableTool(Toolbar, 101, true),
ok = virtuerl_mgt:domain_start(Choice),
ok = erpc:call(Node, virtuerl_mgt, domain_start, [Choice]),
{noreply, State};
handle_event(#wx{id = 101, obj = Toolbar, event = #wxCommand{type = command_menu_selected}} = Event, #state{page = 1, domain_list_box = DomainListBox, domains = DomainIds} = State) ->
handle_event(#wx{id = 101, obj = Toolbar, event = #wxCommand{type = command_menu_selected}} = Event, #state{page = 1, domain_list_box = DomainListBox, domains = DomainIds, node = Node} = State) ->
io:format("~p~n", [Event]),
SelectedItem = wxListCtrl:getNextItem(DomainListBox, -1, [{state, ?wxLIST_STATE_SELECTED}]),
DomainId = lists:nth(SelectedItem + 1, DomainIds),
Choice = DomainId,
io:format("~p~n", [Choice]),
wxToolBar:enableTool(Toolbar, 100, true),
wxToolBar:enableTool(Toolbar, 101, false),
ok = virtuerl_mgt:domain_stop(Choice),
ok = erpc:call(Node, virtuerl_mgt, domain_stop, [Choice]),
{noreply, State};
handle_event(#wx{id = 102, obj = Toolbar, event = #wxCommand{type = command_menu_selected}} = Event, #state{page = 1, domain_list_box = DomainListBox, domains = DomainIds} = State) ->
handle_event(#wx{id = 102, obj = Toolbar, event = #wxCommand{type = command_menu_selected}} = Event, #state{page = 1, domain_list_box = DomainListBox, domains = DomainIds, node = Node} = State) ->
io:format("~p~n", [Event]),
SelectedItem = wxListCtrl:getNextItem(DomainListBox, -1, [{state, ?wxLIST_STATE_SELECTED}]),
DomainId = lists:nth(SelectedItem + 1, DomainIds),
Choice = DomainId,
io:format("~p~n", [Choice]),
%% wxToolBar:enableTool(Toolbar, 100, true),
%% wxToolBar:enableTool(Toolbar, 101, false),
%% ok = virtuerl_mgt:domain_stop(Choice),
virtuerl_mgt:domain_delete(#{id => DomainId}),
%% ok = erpc:call(Node, virtuerl_mgt, domain_stop, (Choice),
erpc:call(Node, virtuerl_mgt, domain_delete, [#{id => DomainId}]),
{noreply, State};
handle_event(#wx{id = 103, obj = Toolbar, event = #wxCommand{type = command_menu_selected}} = Event, #state{page = 1, domain_list_box = DomainListBox, domains = DomainIds} = State) ->
create_domain_dialog(#{network_id => "", name=> "", user_data => "", vcpu => 2, memory => 1024}),
handle_event(#wx{id = 103, obj = Toolbar, event = #wxCommand{type = command_menu_selected}} = Event, #state{page = 1, domain_list_box = DomainListBox, domains = DomainIds, node = Node} = State) ->
create_domain_dialog(Node, #{network_id => "", name=> "", user_data => "", vcpu => 2, memory => 1024}),
{noreply, State};
%% BEGIN: Network Toolbar
handle_event(#wx{id = 102, event = #wxCommand{type = command_menu_selected}}, #state{page = 0, net_list_box = ListBox} = State) ->
handle_event(#wx{id = 102, event = #wxCommand{type = command_menu_selected}}, #state{page = 0, net_list_box = ListBox, node = Node} = State) ->
NetId = wxListBox:getStringSelection(ListBox),
virtuerl_ipam:ipam_delete_net(NetId),
erpc:call(Node, virtuerl_ipam, ipam_delete_net, [NetId]),
{noreply, State};
handle_event(#wx{id = 103, event = #wxCommand{type = command_menu_selected}}, #state{page = 0} = State) ->
create_network_dialog(),
handle_event(#wx{id = 103, event = #wxCommand{type = command_menu_selected}}, #state{page = 0, node = Node} = State) ->
create_network_dialog(Node),
{noreply, State};
%% END: Network Toolbar
handle_event(#wx{event=#wxClose{}}, State = #state{win=Frame}) ->
Expand All @@ -365,7 +367,7 @@ code_change(_, _, State) ->
terminate(_Reason, _State) ->
ok.

create_network_dialog() ->
create_network_dialog(Node) ->
Dialog = wxDialog:new(wx:null(), ?wxID_ANY, "Create Network", [{size, {1000, 500}}]),
DialogSizer = wxBoxSizer:new(?wxVERTICAL),
DialogGridSizer = wxFlexGridSizer:new(1, 2, 0, 0),
Expand All @@ -381,13 +383,13 @@ create_network_dialog() ->
case wxDialog:showModal(Dialog) of
?wxID_OK ->
NetDef = virtuerl_net:parse_cidr(wxTextCtrl:getValue(CidrCtrl)),
{ok, NetId} = virtuerl_ipam:ipam_create_net([NetDef]);
{ok, NetId} = erpc:call(Node, virtuerl_ipam, ipam_create_net, [[NetDef]]);
_ -> ok
end,
wxDialog:destroy(Dialog).

create_domain_dialog(#{network_id := NetworkId, name:= DomainName, user_data := UserData, vcpu := Vcpu, memory := Memory} = Domain) ->
{ok, Nets} = virtuerl_ipam:ipam_list_nets(),
create_domain_dialog(Node, #{network_id := NetworkId, name:= DomainName, user_data := UserData, vcpu := Vcpu, memory := Memory} = Domain) ->
{ok, Nets} = erpc:call(Node, virtuerl_ipam, ipam_list_nets, []),
Choices = maps:keys(Nets),
Dialog = wxDialog:new(wx:null(), ?wxID_ANY, "Create Domain", [{size, {1000, 500}}]),
DialogSizer = wxBoxSizer:new(?wxVERTICAL),
Expand Down Expand Up @@ -417,15 +419,15 @@ create_domain_dialog(#{network_id := NetworkId, name:= DomainName, user_data :=
case wxDialog:showModal(Dialog) of
?wxID_OK ->
io:format("true ~p~n", [wxChoice:getStringSelection(NetworkChoice)]),
virtuerl_mgt:domain_create(#{
erpc:call(Node, virtuerl_mgt, domain_create, [#{
name => wxTextCtrl:getValue(NameCtrl),
network_id => list_to_binary(wxChoice:getStringSelection(NetworkChoice)),
user_data => wxStyledTextCtrl:getText(UserDataCtrl),
vcpu => wxSpinCtrl:getValue(VcpuCtrl),
memory => wxSpinCtrl:getValue(MemoryCtrl)
});
}]);
_ -> ok
end,
wxDialog:destroy(Dialog);
create_domain_dialog(Domain) ->
create_domain_dialog(Domain#{user_data => ""}).
create_domain_dialog(Node, Domain) ->
create_domain_dialog(Node, Domain#{user_data => ""}).
9 changes: 8 additions & 1 deletion virtuerl/src/virtuerl_util.erl
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
-author("ilya-stroeer").

%% API
-export([uuid4/0, mac_to_str/1, delete_file/1]).
-export([uuid4/0, mac_to_str/1, delete_file/1, cmd/1]).

uuid4() ->
ID = string:lowercase(binary:encode_hex(<<(rand:uniform(16#FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)-1):128>>)),
Expand All @@ -28,3 +28,10 @@ delete_file(Filename) ->
{error, enoent} -> ok;
Other -> Other
end.

cmd(Cmd) ->
Port = open_port({spawn, Cmd}, [exit_status]),
receive
{Port, {exit_status, 0}} -> ok;
{Port, {exit_signal, _}} -> error
end.
10 changes: 5 additions & 5 deletions virtuerl/src/virtuerl_vnc.erl
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
-export([
init/1, handle_info/2, handle_event/2,
code_change/3, terminate/2]).
-export([start/2]).
-export([start/3]).

-behaviour(wx_object).

Expand All @@ -51,11 +51,11 @@
-record(state, {win, socket, panel, tex_id, vnc_conf, parent}).
-record(vnc_conf, {width, height}).

start(Parent, DomainId) ->
wx_object:start_link(?MODULE, [Parent, DomainId], []).
start(Parent, DomainId, Node) ->
wx_object:start_link(?MODULE, [Parent, DomainId, Node], []).

%% Init is called in the new process.
init([Parent, DomainId]) ->
init([Parent, DomainId, Node]) ->
{Mx, My, _, _} = wxWindow:getTextExtent(Parent, "M"),
%% wxFrame:setClientSize(Frame, {60*Mx, 20*My}),
Panel = wxGLCanvas:new(Parent, [{attribList, [?WX_GL_RGBA,?WX_GL_DOUBLEBUFFER,0]}]),
Expand All @@ -67,7 +67,7 @@ init([Parent, DomainId]) ->

wxGLCanvas:setCurrent(Panel, Context),

VncProxy = virtuerl_vnc_proxy:start(DomainId),
VncProxy = virtuerl_vnc_proxy:start(DomainId, Node),
{VncWidth, VncHeight} = receive
{conf, TVncWidth, TVncHeight} -> {TVncWidth, TVncHeight}
after 1000 ->
Expand Down
6 changes: 3 additions & 3 deletions virtuerl/src/virtuerl_vnc_proxy.erl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
%%%-------------------------------------------------------------------
-module(virtuerl_vnc_proxy).

-export([start/1]).
-export([start/2]).

-define(SERVER, ?MODULE).
-define(APPLICATION, virtuerl).
Expand All @@ -29,10 +29,10 @@
%%% Spawning and gen_server implementation
%%%===================================================================

start(DomainId) ->
start(DomainId, Node) ->
VncSocketPath = filename:join([virtuerl_mgt:home_path(), "domains", DomainId, "vnc.sock"]),
Self = self(),
spawn(fun() -> init(VncSocketPath, Self) end).
spawn(Node, fun() -> init(VncSocketPath, Self) end).

init(VncSocketPath, Sender) ->
{ok, Socket} = gen_tcp:connect({local, VncSocketPath}, 0, [{active, true}, binary, local]),
Expand Down

0 comments on commit 1d483e6

Please sign in to comment.