Skip to content

Commit

Permalink
bbf: big fucking refactoring
Browse files Browse the repository at this point in the history
This is a big boi that introduces a new architecture with a
cluster controller and multiple workers that pull the state from
the controller and try to realize the desired state.
  • Loading branch information
verbit committed Sep 24, 2024
1 parent f240db5 commit 2fd6587
Show file tree
Hide file tree
Showing 15 changed files with 546 additions and 234 deletions.
6 changes: 6 additions & 0 deletions apps/virtuerl/src/virtuerl_app.erl
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ start(_StartType, _StartArgs) ->
end,
[ virtuerl_server:start(Name, Conf) || {Name, Conf} <- maps:to_list(Servers) ],

Clusters = case application:get_env(clusters) of
undefined -> #{};
{ok, Clusters0} -> Clusters0
end,
[ virtuerl_mgt_sup:start(Cluster) || Cluster <- Clusters ],

{ok, Pid}.


Expand Down
164 changes: 164 additions & 0 deletions apps/virtuerl/src/virtuerl_host.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
-module(virtuerl_host).

-behaviour(gen_server).

-export([start_link/2,
home_path/0,
sync/1]).
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3,
handle_continue/2]).

-include_lib("kernel/include/logger.hrl").

-define(APPLICATION, virtuerl).

-record(state, {server_id, vm_proc_mod, table, idmap, controller, dom_tap_map, prefix}).

%%%===================================================================
%%% Spawning and gen_server implementation
%%%===================================================================


home_path() ->
application:get_env(?APPLICATION, home, "var").


sync(Pid) ->
gen_server:cast(Pid, sync).


start_link(ServerId, Conf) ->
gen_server:start_link({via, virtuerl_reg, {ServerId, ?MODULE}}, ?MODULE, [ServerId, Conf], []).


init([ServerId, Conf]) ->
#{cluster := Cluster, vm_proc_mod := VmProcMod, prefix := IfPrefix} = Conf,
pg:join(Cluster, [self()]),
% TODO: query supervisor and populate dom_tap_map
{ok, #state{server_id = ServerId, vm_proc_mod = VmProcMod, idmap = #{}, dom_tap_map = #{}, prefix = IfPrefix}}.


generate_unique_tap_name(Prefix, TapNames) ->
TapName = iolist_to_binary([Prefix, "tap", binary:encode_hex(<<(rand:uniform(16#ffffff)):24>>)]),
case lists:member(TapName, TapNames) of
false ->
TapName;
true ->
generate_unique_tap_name(Prefix, TapNames)
end.


handle_continue(sync_domains, #state{server_id = ServerId, vm_proc_mod = VmProcMod, idmap = IdMap, dom_tap_map = DomTapMap, prefix = IfPrefix, controller = ControllerPid} = State) ->
Domains0 = virtuerl_mgt:domains_list(ControllerPid, ServerId),
io:format("DOMAINS0: ~p~n", [Domains0]),
{_, Domains1} = lists:foldl(fun(Dom, {IfNames, Doms}) ->
#{id := DomId} = Dom,
case DomTapMap of
#{DomId := TapName} -> {[TapName | IfNames], [maps:put(tap_name, TapName, Dom) | Doms]};
_ ->
TapName = generate_unique_tap_name(IfPrefix, IfNames),
{[TapName | IfNames], [maps:put(tap_name, TapName, Dom) | Doms]}
end
end,
{[], []},
Domains0),

io:format("DOMAINS1: ~p~n", [Domains1]),
Domains = [ {Id, Dom} || #{id := Id} = Dom <- Domains1 ],
TargetDomains = maps:from_list(
[ {Id, case maps:get(host, Domain, localhost) of localhost -> node(); Else -> Else end}
|| {Id, Domain} <- Domains,
case Domain of
#{state := stopped} -> false;
_ -> true
end ]),

AllNodes = nodes([this]),
RunningDomains = maps:from_list(lists:flatten(
[ [ {Id, Node} || {Id, _, _, _} <- virtuerl_sup:which_children(ServerId, Node), is_binary(Id) ]
|| Node <- AllNodes ])),
ToDelete = maps:without(maps:keys(TargetDomains), RunningDomains),
ToAdd = maps:without(maps:keys(RunningDomains), TargetDomains),
[ virtuerl_sup:terminate_child(ServerId, Node, Id) || {Id, Node} <- maps:to_list(ToDelete) ],
[ virtuerl_sup:delete_child(ServerId, Node, Id) || {Id, Node} <- maps:to_list(ToDelete) ],

% cleanup deleted domains
case file:list_dir(filename:join([virtuerl_host:home_path(), "domains"])) of
{ok, Filenames} ->
DirsToDel = sets:subtract(sets:from_list([ iolist_to_binary(FName) || FName <- Filenames ]),
sets:from_list(maps:keys(maps:from_list(Domains)))),
[ file:del_dir_r(filename:join([virtuerl_host:home_path(), "domains", Dir])) || Dir <- sets:to_list(DirsToDel) ];
_ -> ok
end,

% lists:foreach(fun () -> ok end, List)
DomsByNode0 = maps:groups_from_list(
fun({_Id, Dom}) -> case maps:get(host, Dom, localhost) of localhost -> node(); Else -> Else end end,
fun({_Id, Dom}) -> Dom end,
Domains),
DomsByNode = maps:merge(#{node() => []}, DomsByNode0),
[ virtuerl_net:update_net(Node, ServerId, Doms) || {Node, Doms} <- maps:to_list(DomsByNode), lists:member(Node, AllNodes) ],

io:format("DomsByNode: ~p~n", [DomsByNode]),
io:format("RUNNING: ~p~n", [RunningDomains]),
io:format("TOADD: ~p~n", [ToAdd]),
io:format("TODELETE: ~p~n", [ToDelete]),
[ virtuerl_mgt:notify(ControllerPid, {domain_stopped, DomId}) || DomId <- maps:keys(ToDelete) ],

VmPids = [ {Id,
virtuerl_sup:start_child(ServerId,
Node,
{Id,
{VmProcMod, start_link, [maps:get(Id, maps:from_list(Domains))]},
transient,
infinity,
worker,
[]})} || {Id, Node} <- maps:to_list(ToAdd), lists:member(Node, AllNodes) ],
io:format("VMPIDS: ~p~n", [VmPids]),
[ virtuerl_mgt:notify(ControllerPid, {domain_started, DomId}) || {DomId, _} <- VmPids ],
VmPidToDomId = maps:from_list([ {VmPid, DomId} || {DomId, {ok, VmPid}} <- VmPids ]),
[ monitor(process, VmPid) || {_, {ok, VmPid}} <- VmPids ],

NewDomTapMap = maps:from_list([ {Id, TapName} || {Id, #{tap_name := TapName}} <- Domains ]),
{noreply, State#state{idmap = maps:merge(IdMap, VmPidToDomId), dom_tap_map = NewDomTapMap}}.


handle_call(_Request, _From, _State) ->
erlang:error(not_implemented).


handle_cast(sync, State) ->
{noreply, State, {continue, sync_domains}};
handle_cast(_Request, _State) ->
erlang:error(not_implemented).


handle_info({enslave, ControllerPid}, State) ->
?LOG_NOTICE(#{who => ?MODULE, msg => "got enslaved!", controller => ControllerPid}),
{noreply, State#state{controller = ControllerPid}, {continue, sync_domains}};
handle_info({'DOWN', _, process, Pid, normal}, #state{idmap = IdMap} = State) ->
NewIdMap = case IdMap of
#{Pid := DomId} ->
virtuerl_mgt:domain_stop(DomId),
maps:remove(Pid, IdMap);
#{} ->
?LOG_WARNING(#{module => ?MODULE, msg => "process down but not in registry", pid => Pid}),
IdMap
end,
{noreply, State#state{idmap = NewIdMap}};
handle_info(Info, State) ->
?LOG_NOTICE(#{module => ?MODULE, msg => "unhandled info message", info => Info}),
{noreply, State}.


terminate(_Reason, _State) ->
ok.


code_change(_OldVsn, State, _Extra) ->
{ok, State}.
25 changes: 15 additions & 10 deletions apps/virtuerl/src/virtuerl_img.erl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

-behaviour(gen_server).

-export([start_link/0, list_images/0, ensure_image/1]).
-export([start_link/1,
list_images/0, list_images/1,
ensure_image/1, ensure_image/2]).
-export([init/1,
handle_call/3,
handle_cast/2,
Expand All @@ -11,24 +13,27 @@
code_change/3]).
-export([]).

-define(SERVER, ?MODULE).
-define(APPLICATION, virtuerl).

%%%===================================================================
%%% Spawning and gen_server implementation
%%%===================================================================


list_images() ->
gen_server:call(?SERVER, list_images).
list_images() -> list_images(default).


list_images(ServerId) ->
gen_server:call({via, virtuerl_reg, {ServerId, ?MODULE}}, list_images).


ensure_image(ImageName) -> ensure_image(default, ImageName).


ensure_image(ImageName) ->
gen_server:call(?SERVER, {ensure_image, ImageName}, infinity).
ensure_image(ServerId, ImageName) ->
gen_server:call({via, virtuerl_reg, {ServerId, ?MODULE}}, {ensure_image, ImageName}, infinity).


start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
start_link(ServerId) ->
gen_server:start_link({via, virtuerl_reg, {ServerId, ?MODULE}}, ?MODULE, [], []).


init([]) ->
Expand Down
Loading

0 comments on commit 2fd6587

Please sign in to comment.