Skip to content
Snippets Groups Projects
Commit a01a91e5 authored by Sebastien Merle's avatar Sebastien Merle
Browse files

First simple integration with the service component

parent b8096882
No related branches found
No related tags found
2 merge requests!142Release TeraFlowSDN 2.1,!133Integration of TE component
...@@ -67,8 +67,8 @@ get_flows() -> ...@@ -67,8 +67,8 @@ get_flows() ->
update_flow(FlowId, LabelStack) -> update_flow(FlowId, LabelStack) ->
gen_server:call(?MODULE, {update_flow, FlowId, LabelStack}). gen_server:call(?MODULE, {update_flow, FlowId, LabelStack}).
initiate_flow(Name, FromAddr, ToAddr, BindingLabel) -> initiate_flow(Name, From, To, BindingLabel) ->
gen_server:call(?MODULE, {initiate_flow, Name, FromAddr, ToAddr, gen_server:call(?MODULE, {initiate_flow, Name, From, To,
BindingLabel}, ?LARGE_TIMEOUT). BindingLabel}, ?LARGE_TIMEOUT).
...@@ -111,19 +111,26 @@ handle_call({update_flow, FlowId, Labels}, From, ...@@ -111,19 +111,26 @@ handle_call({update_flow, FlowId, Labels}, From,
{noreply, State} {noreply, State}
end end
end; end;
handle_call({initiate_flow, Name, FromAddr, ToAddr, Binding}, From, handle_call({initiate_flow, Name, FromKey, ToKey, Binding}, From,
#state{sessions = SessMap} = State) -> #state{sessions = SessMap} = State) ->
case maps:find(FromAddr, SessMap) of case {pcc_address(FromKey), pcc_address(ToKey)} of
error -> {reply, {error, session_not_found}, State}; {{error, Reason}, _} ->
{ok, #sess{pid = Pid}} -> {reply, {error, Reason}, State};
case compute_path(FromAddr, ToAddr) of {_, {error, Reason}} ->
{error, Reason} -> {reply, {error, Reason}, State};
{reply, {error, Reason}, State}; {{ok, FromAddr}, {ok, ToAddr}} ->
{ok, Labels} -> case maps:find(FromAddr, SessMap) of
InitRoute = routeinit_from_labels(Name, FromAddr, ToAddr, error -> {reply, {error, session_not_found}, State};
[], Binding, Labels), {ok, #sess{pid = Pid}} ->
session_initiate_flow(State, Pid, InitRoute, From), case compute_path(FromAddr, ToAddr) of
{noreply, State} {error, Reason} ->
{reply, {error, Reason}, State};
{ok, Labels} ->
InitRoute = routeinit_from_labels(Name, FromAddr,
ToAddr, [], Binding, Labels),
session_initiate_flow(State, Pid, InitRoute, From),
{noreply, State}
end
end end
end; end;
handle_call({session_opened, Id, Caps, Pid}, _From, handle_call({session_opened, Id, Caps, Pid}, _From,
...@@ -227,18 +234,33 @@ terminate(_Reason, _State) -> ...@@ -227,18 +234,33 @@ terminate(_Reason, _State) ->
%%% INTERNAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% INTERNAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
compute_path(From, To) -> ted_index(Id) when is_binary(Id) -> id;
case epce_ted:compute_path(pcc_address, From, To) of ted_index({_, _, _, _}) -> pcc_address.
{ok, Devices} ->
Labels = tl([L || #{mpls_label := L} <- Devices, L =/= undefined]), pcc_address(Key) ->
logger:debug("Route from ~p to ~p: ~p", [From, To, Labels]), case epce_ted:lookup(ted_index(Key), Key) of
{ok, Labels};
{error, Reason} -> {error, Reason} ->
logger:warning("Failed to find a route from ~p to ~p: ~p", logger:warning("Failed to find a PCC address for router ~p: ~p",
[From, To, Reason]), [Key, Reason]),
{error, route_not_found} {error, router_not_found};
{ok, #{pcc_address := Addr}} ->
{ok, Addr}
end. end.
compute_path(From, To) when is_binary(From), is_binary(To) ->
compute_path_result(From, To, epce_ted:compute_path(id, From, To));
compute_path({_, _, _, _} = From, {_, _, _, _} = To) ->
compute_path_result(From, To, epce_ted:compute_path(pcc_address, From, To)).
compute_path_result(From, To, {error, Reason}) ->
logger:warning("Failed to find a route from ~p to ~p: ~p",
[From, To, Reason]),
{error, route_not_found};
compute_path_result(From, To, {ok, Devices}) ->
Labels = tl([L || #{mpls_label := L} <- Devices, L =/= undefined]),
logger:debug("Route from ~p to ~p: ~p", [From, To, Labels]),
{ok, Labels}.
routereq_from_labels(Source, Destination, Constraints, Labels) -> routereq_from_labels(Source, Destination, Constraints, Labels) ->
#{ #{
source => Source, source => Source,
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
-export([link_updated/2]). -export([link_updated/2]).
-export([link_deleted/1]). -export([link_deleted/1]).
-export([compute_path/3]). -export([compute_path/3]).
-export([lookup/2]).
-export([get_graph/0]). -export([get_graph/0]).
...@@ -69,6 +70,12 @@ compute_path(Index, From, To) ...@@ -69,6 +70,12 @@ compute_path(Index, From, To)
compute_path(Index, _From, _To) -> compute_path(Index, _From, _To) ->
{error, {invalid_index, Index}}. {error, {invalid_index, Index}}.
lookup(Index, Key)
when Index =:= id; Index =:= pcc_address ->
gen_server:call(?MODULE, {lookup, Index, Key});
lookup(Index, _Key) ->
{error, {invalid_index, Index}}.
get_graph() -> get_graph() ->
gen_server:call(?MODULE, get_graph). gen_server:call(?MODULE, get_graph).
...@@ -106,6 +113,13 @@ handle_call({compute_path, Index, From, To}, _From, #state{graph = G} = State) - ...@@ -106,6 +113,13 @@ handle_call({compute_path, Index, From, To}, _From, #state{graph = G} = State) -
{error, Reason} -> {error, Reason} ->
{reply, {error, Reason}, State} {reply, {error, Reason}, State}
end; end;
handle_call({lookup, Index, Key}, _From, #state{graph = G} = State) ->
case as_ids(State, Index, [Key]) of
{ok, [Id]} ->
{reply, do_lookup(G, Id), State};
{error, Reason} ->
{reply, {error, Reason}, State}
end;
handle_call(get_graph, _From, #state{graph = G} = State) -> handle_call(get_graph, _From, #state{graph = G} = State) ->
{reply, G, State}; {reply, G, State};
handle_call(Request, _From, State) -> handle_call(Request, _From, State) ->
...@@ -193,6 +207,12 @@ do_compute_path(G, FromId, ToId) -> ...@@ -193,6 +207,12 @@ do_compute_path(G, FromId, ToId) ->
Ids -> {ok, retrieve_devices(G, Ids, [])} Ids -> {ok, retrieve_devices(G, Ids, [])}
end. end.
do_lookup(G, Id) ->
case digraph:vertex(G, Id) of
{_, Info} -> {ok, Info};
false -> {error, not_found}
end.
retrieve_devices(_G, [], Acc) -> retrieve_devices(_G, [], Acc) ->
lists:reverse(Acc); lists:reverse(Acc);
retrieve_devices(G, [Id | Rest], Acc) -> retrieve_devices(G, [Id | Rest], Acc) ->
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
[kernel, [kernel,
stdlib, stdlib,
tfpb, tfpb,
jsx,
epce epce
]}, ]},
{env,[]}, {env,[]},
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
-export([context_event/1]). -export([context_event/1]).
-export([topology_ready/1]). -export([topology_ready/1]).
-export([topology_event/1]). -export([topology_event/1]).
-export([request_lsp/1]).
-export([delete_lsp/1]).
% Behaviour gen_statem functions % Behaviour gen_statem functions
-export([init/1]). -export([init/1]).
...@@ -28,6 +30,7 @@ ...@@ -28,6 +30,7 @@
%%% Records %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Records %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-record(data, { -record(data, {
services = #{}
}). }).
...@@ -51,6 +54,12 @@ topology_ready(Topology) -> ...@@ -51,6 +54,12 @@ topology_ready(Topology) ->
topology_event(Event) -> topology_event(Event) ->
gen_statem:cast(?MODULE, {topology_event, Event}). gen_statem:cast(?MODULE, {topology_event, Event}).
request_lsp(ServiceMap) ->
gen_statem:cast(?MODULE, {request_lsp, ServiceMap}).
delete_lsp(ServiceId) ->
gen_statem:cast(?MODULE, {delete_lsp, ServiceId}).
%%% BEHAVIOUR gen_statem FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% BEHAVIOUR gen_statem FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
...@@ -83,6 +92,17 @@ handle_event(cast, {topology_ready, _Topology}, ready, _Data) -> ...@@ -83,6 +92,17 @@ handle_event(cast, {topology_ready, _Topology}, ready, _Data) ->
handle_event(cast, {topology_event, _Event}, ready, _Data) -> handle_event(cast, {topology_event, _Event}, ready, _Data) ->
?LOG_DEBUG("Teraflow topology event: ~p", [_Event]), ?LOG_DEBUG("Teraflow topology event: ~p", [_Event]),
keep_state_and_data; keep_state_and_data;
handle_event({call, _From}, {request_lsp, ServiceMap}, ready, Data) ->
#{service_id := ServiceId} = ServiceMap,
?LOG_DEBUG("Teraflow service ~s requested its LSPs",
[format_service_id(ServiceId)]),
{Result, Data2} = do_request_lsp(Data, ServiceMap),
{keep_state, Data2, [{reply, Result}]};
handle_event(cast, {delete_lsp, ServiceId}, ready, Data) ->
?LOG_DEBUG("Teraflow service ~s delete its LSPs",
[format_service_id(ServiceId)]),
{Result, Data2} = do_delete_lsp(Data, ServiceId),
{keep_state, Data2, [{reply, Result}]};
%-- ANY STATE ------------------------------------------------------------------ %-- ANY STATE ------------------------------------------------------------------
handle_event(EventType, EventContent, State, Data) -> handle_event(EventType, EventContent, State, Data) ->
?LOG_WARNING(Data, "Unexpected ~w event in state ~w: ~w", ?LOG_WARNING(Data, "Unexpected ~w event in state ~w: ~w",
...@@ -98,3 +118,42 @@ code_change(_OldVsn, OldState, OldData, _Extra) -> ...@@ -98,3 +118,42 @@ code_change(_OldVsn, OldState, OldData, _Extra) ->
%%% INTERNAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% INTERNAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
format_service_id(#{context_id := #{context_uuid := #{uuid := ContextName}},
service_uuid := #{uuid := ServiceUUID}}) ->
iolist_to_binary(io_lib:format("~s:~s", [ContextName, ServiceUUID])).
do_request_lsp(#data{services = Services} = Data,
#{service_type := 'SERVICETYPE_TE'} = ServiceMap) ->
#{service_config := Config,
service_endpoint_ids := Endpoints,
service_id := ServiceId} = ServiceMap,
#{binding_label := BindingLabel1, symbolic_name := SymbolicName1}
= tfte_util:custom_config(Config, <<"/lsp-fw">>),
#{binding_label := BindingLabel2, symbolic_name := SymbolicName2}
= tfte_util:custom_config(Config, <<"/lsp-bw">>),
[#{device_id := #{device_uuid := #{uuid := Id1}}},
#{device_id := #{device_uuid := #{uuid := Id2}}}] = Endpoints,
case epce_server:initiate_flow(SymbolicName1, Id1, Id2, BindingLabel1) of
{error, Reason} ->
?LOG_ERROR("Error while setting up service ~s forward LSP: ~p",
[format_service_id(ServiceId), Reason]),
{'SERVICESTATUS_UNDEFINED', Data};
{ok, ForwardFlow} ->
case epce_server:initiate_flow(SymbolicName2, Id2, Id1, BindingLabel2) of
{error, Reason} ->
?LOG_ERROR("Error while setting up service ~s backward LSP: ~p",
[format_service_id(ServiceId), Reason]),
%TODO: Cleanup forward flow ?
{'SERVICESTATUS_UNDEFINED', Data};
{ok, BackwardFlow} ->
ServiceData = {ServiceMap, ForwardFlow, BackwardFlow},
Services2 = Services#{ServiceId => ServiceData},
Data2 = Data#data{services = Services2},
{'SERVICESTATUS_ACTIVE', Data2}
end
end.
do_delete_lsp(Data, ServiceId) ->
?LOG_INFO("LSP DELETION REQUESTED ~p", [ServiceId]),
{'SERVICESTATUS_UNDEFINED', Data}.
\ No newline at end of file
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
%%% INCLUDES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% INCLUDES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-include_lib("grpcbox/include/grpcbox.hrl"). -include_lib("grpcbox/include/grpcbox.hrl").
-include_lib("kernel/include/logger.hrl").
%%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
...@@ -18,14 +19,22 @@ ...@@ -18,14 +19,22 @@
%%% BEHAVIOUR te_te_service_bhvr CALLBACK FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% BEHAVIOUR te_te_service_bhvr CALLBACK FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%
request_lsp(_Ctx, _Service) -> request_lsp(_Ctx, Service) ->
{error, {?GRPC_STATUS_UNIMPLEMENTED, <<"Not yet implemented">>}, ?LOG_ERROR("Requesting LSP: ~p", [Service]),
#{headers => #{}, trailers => #{}}}. try tfte_server:request_lsp(Service)
catch E:R:S ->
?LOG_ERROR("Error while requesting LSP: ~p:~p ~p", [E, R, S]),
'SERVICESTATUS_UNDEFINED'
end.
update_lsp(_Ctx, _Service) -> update_lsp(_Ctx, _ServiceId) ->
{error, {?GRPC_STATUS_UNIMPLEMENTED, <<"Not yet implemented">>}, {error, {?GRPC_STATUS_UNIMPLEMENTED, <<"Not yet implemented">>},
#{headers => #{}, trailers => #{}}}. #{headers => #{}, trailers => #{}}}.
delete_lsp(_Ctx, _Service) -> delete_lsp(_Ctx, ServiceId) ->
{error, {?GRPC_STATUS_UNIMPLEMENTED, <<"Not yet implemented">>}, ?LOG_ERROR("Deleting LSP: ~p", [ServiceId]),
#{headers => #{}, trailers => #{}}}. try tfte_server:delete_lsp(ServiceId)
catch E:R:S ->
?LOG_ERROR("Error while deleting LSP: ~p:~p ~p", [E, R, S]),
'SERVICESTATUS_UNDEFINED'
end.
...@@ -281,37 +281,31 @@ device_status(#{device_operational_status := 'DEVICEOPERATIONALSTATUS_ENABLED'}) ...@@ -281,37 +281,31 @@ device_status(#{device_operational_status := 'DEVICEOPERATIONALSTATUS_ENABLED'})
enabled. enabled.
device_mpls_label(Device) -> device_mpls_label(Device) ->
case device_config_value(<<"mpls_label">>, Device) of case device_config_value(<<"/te_data/mpls_label">>, Device) of
undefined -> undefined; undefined -> undefined;
LabelBin -> LabelJson ->
try binary_to_integer(LabelBin) try jsx:decode(LabelJson)
catch error:badarg -> undefined catch error:badarg -> undefined
end end
end. end.
device_pcc_address(Device) -> device_pcc_address(Device) ->
case device_config_value(<<"pcc_address">>, Device) of case device_config_value(<<"/te_data/pcc_address">>, Device) of
undefined -> undefined; undefined -> undefined;
AddressBin -> AddressJson ->
case inet_parse:address(binary_to_list(AddressBin)) of try jsx:decode(AddressJson) of
{ok, Address} -> Address; AddressBin ->
{error,einval} -> undefined case inet_parse:address(binary_to_list(AddressBin)) of
{ok, Address} -> Address;
{error,einval} -> undefined
end
catch
error:badarg -> undefined
end end
end. end.
device_config_value(Key, #{device_config := #{config_rules := Rules}}) -> device_config_value(Key, #{device_config := Config}) ->
device_config_value(Key, Rules); tfte_util:custom_config(Config, Key).
device_config_value(_Key, []) ->
undefined;
device_config_value(Key, [#{action := 'CONFIGACTION_SET',
config_rule := {custom, Rule}} | Rest]) ->
case Rule of
#{resource_key := Key, resource_value := Value} -> Value;
_ -> device_config_value(Key, Rest)
end;
device_config_value(Key, [_Rule | Rest]) ->
device_config_value(Key, Rest).
device_endpoints(Device) -> device_endpoints(Device) ->
device_endpoints(Device, []). device_endpoints(Device, []).
......
-module(tfte_util).
%%% INCLUDES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-include_lib("kernel/include/logger.hrl").
%%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% API functions
-export([custom_config/2]).
%%% API FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
custom_config(#{config_rules := Rules}, Key) ->
custom_config(Rules, Key);
custom_config([], _Key) ->
undefined;
custom_config([#{action := 'CONFIGACTION_SET',
config_rule := {custom, Rule}} | Rest], Key) ->
case Rule of
#{resource_key := Key, resource_value := Value} -> jsx:decode(Value);
_ -> custom_config(Rest, Key)
end;
custom_config([_Rule | Rest], Key) ->
custom_config(Rest, Key).
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
{deps, [ {deps, [
grpcbox, grpcbox,
jsx,
{pcep_server, {git, "https://github.com/stritzinger/pcep_server.git", {branch, "master"}}} {pcep_server, {git, "https://github.com/stritzinger/pcep_server.git", {branch, "master"}}}
]}. ]}.
...@@ -17,6 +18,7 @@ ...@@ -17,6 +18,7 @@
runtime_tools, runtime_tools,
epce, epce,
grpcbox, grpcbox,
jsx,
tfpb, tfpb,
tfte tfte
]}, ]},
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
{<<"gproc">>,{pkg,<<"gproc">>,<<"0.8.0">>},1}, {<<"gproc">>,{pkg,<<"gproc">>,<<"0.8.0">>},1},
{<<"grpcbox">>,{pkg,<<"grpcbox">>,<<"0.15.0">>},0}, {<<"grpcbox">>,{pkg,<<"grpcbox">>,<<"0.15.0">>},0},
{<<"hpack">>,{pkg,<<"hpack_erl">>,<<"0.2.3">>},2}, {<<"hpack">>,{pkg,<<"hpack_erl">>,<<"0.2.3">>},2},
{<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},0},
{<<"pcep_codec">>, {<<"pcep_codec">>,
{git,"https://github.com/stritzinger/pcep_codec.git", {git,"https://github.com/stritzinger/pcep_codec.git",
{ref,"ca5eb0822d9971ec4bcfb427a49b2e516081a126"}}, {ref,"ca5eb0822d9971ec4bcfb427a49b2e516081a126"}},
...@@ -26,6 +27,7 @@ ...@@ -26,6 +27,7 @@
{<<"gproc">>, <<"CEA02C578589C61E5341FCE149EA36CCEF236CC2ECAC8691FBA408E7EA77EC2F">>}, {<<"gproc">>, <<"CEA02C578589C61E5341FCE149EA36CCEF236CC2ECAC8691FBA408E7EA77EC2F">>},
{<<"grpcbox">>, <<"97C7126296A091602D372EBF5860A04F7BC795B45B33A984CAD2B8E362774FD8">>}, {<<"grpcbox">>, <<"97C7126296A091602D372EBF5860A04F7BC795B45B33A984CAD2B8E362774FD8">>},
{<<"hpack">>, <<"17670F83FF984AE6CD74B1C456EDDE906D27FF013740EE4D9EFAA4F1BF999633">>}, {<<"hpack">>, <<"17670F83FF984AE6CD74B1C456EDDE906D27FF013740EE4D9EFAA4F1BF999633">>},
{<<"jsx">>, <<"D12516BAA0BB23A59BB35DCCAF02A1BD08243FCBB9EFE24F2D9D056CCFF71268">>},
{<<"ranch">>, <<"FBF3D79661C071543256F9051CAF19D65DAA6DF1CF6824D8F37A49B19A66F703">>}]}, {<<"ranch">>, <<"FBF3D79661C071543256F9051CAF19D65DAA6DF1CF6824D8F37A49B19A66F703">>}]},
{pkg_hash_ext,[ {pkg_hash_ext,[
{<<"acceptor_pool">>, <<"0CBCD83FDC8B9AD2EEE2067EF8B91A14858A5883CB7CD800E6FCD5803E158788">>}, {<<"acceptor_pool">>, <<"0CBCD83FDC8B9AD2EEE2067EF8B91A14858A5883CB7CD800E6FCD5803E158788">>},
...@@ -34,5 +36,6 @@ ...@@ -34,5 +36,6 @@
{<<"gproc">>, <<"580ADAFA56463B75263EF5A5DF4C86AF321F68694E7786CB057FD805D1E2A7DE">>}, {<<"gproc">>, <<"580ADAFA56463B75263EF5A5DF4C86AF321F68694E7786CB057FD805D1E2A7DE">>},
{<<"grpcbox">>, <<"161ABE9E17E7D1982EFA6488ADEAA13C3E847A07984A6E6B224E553368918647">>}, {<<"grpcbox">>, <<"161ABE9E17E7D1982EFA6488ADEAA13C3E847A07984A6E6B224E553368918647">>},
{<<"hpack">>, <<"06F580167C4B8B8A6429040DF36CC93BBA6D571FAEAEC1B28816523379CBB23A">>}, {<<"hpack">>, <<"06F580167C4B8B8A6429040DF36CC93BBA6D571FAEAEC1B28816523379CBB23A">>},
{<<"jsx">>, <<"0C5CC8FDC11B53CC25CF65AC6705AD39E54ECC56D1C22E4ADB8F5A53FB9427F3">>},
{<<"ranch">>, <<"C20A4840C7D6623C19812D3A7C828B2F1BD153EF0F124CB69C54FE51D8A42AE0">>}]} {<<"ranch">>, <<"C20A4840C7D6623C19812D3A7C828B2F1BD153EF0F124CB69C54FE51D8A42AE0">>}]}
]. ].
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment