Fixing API

This commit is contained in:
Stephane Bourque
2020-12-17 16:20:21 -08:00
parent b656254bfd
commit cef0a5f43f
2 changed files with 197 additions and 115 deletions

View File

@@ -29,6 +29,8 @@
-record(request_state,{
resource = nothing :: nothing | binary(),
id = nothing :: nothing | binary(),
subid = nothing :: nothing | binary(),
subres = nothing :: nothing | binary(),
method :: binary(),
looked_up :: any(),
time_in}).
@@ -36,12 +38,16 @@
init(Req, _State) ->
Res =cowboy_req:binding( restype , Req , nothing ),
Id = cowboy_req:binding( resid , Req , nothing ),
%% io:format("REQUEST: ~p ~p ~n RES: ~p~n ID: ~p~n",[Req,cowboy_req:method(Req),Res,Id]),
SubId = cowboy_req:binding( subid , Req , nothing ),
SubRes = cowboy_req:binding( subres , Req , nothing ),
%% io:format("REQUEST: ~p ~p ~p ~p ~n",[Res,Id,SubRes,SubId]),
Method = cowboy_req:method(Req),
{ cowboy_rest,Req,#request_state{
{ cowboy_rest,restutils:add_CORS(Req),#request_state{
resource = Res,
id = Id,
method = Method,
subid = SubId,
subres = SubRes,
looked_up = undefined,
time_in = os:system_time() }}.
@@ -76,40 +82,57 @@ is_authorized(Req, State) ->
delete_resource(Req, State) ->
{ true , Req , State }.
resource_exists(Req, #request_state{ method = ?HTTP_GET, id=nothing}=State) ->
{true, Req, State};
resource_exists(Req, #request_state{ method = ?HTTP_GET, resource = <<"cas">>, id=nothing }=State) ->
{ true , Req , State };
resource_exists(Req, #request_state{ method = ?HTTP_GET, resource = <<"ouis">>, id=nothing }=State) ->
{ true , Req , State };
resource_exists(Req, #request_state{ method = ?HTTP_GET, resource = <<"vendors">>, id=nothing }=State) ->
{ true , Req , State };
resource_exists(Req, #request_state{ method = ?HTTP_GET, resource = <<"actions">>, id=nothing }=State) ->
{ true , Req , State };
resource_exists(Req, #request_state{ method = ?HTTP_GET, resource = <<"simulations">>, id=nothing, subres = nothing }=State) ->
{ true , Req , State };
resource_exists(Req, #request_state{ method = ?HTTP_GET, resource = <<"cas">>}=State) ->
case inventory:get_ca(State#request_state.id) of
{ok,Record} -> {true, Req, State#request_state{ looked_up = Record }};
{error,Reason} -> create_error(101,Reason,Req,State)
end;
resource_exists(Req, #request_state{ method = ?HTTP_GET, resource = <<"simulations">>}=State) ->
case simengine:get(State#request_state.id) of
{ok,Record} -> {true, Req, State#request_state{ looked_up = Record }};
{error,Reason} -> create_error(101,Reason,Req,State)
end;
resource_exists(Req, #request_state{ method = ?HTTP_GET, resource = <<"actions">>}=State) ->
case simengine:get_action(State#request_state.id) of
{ok,Record} -> {true, Req, State#request_state{ looked_up = Record }};
{error,Reason} -> create_error(101,Reason,Req,State)
end;
resource_exists(Req, #request_state{ method = ?HTTP_POST, resource = <<"simulations">>}=State) ->
case simengine:get(State#request_state.id) of
{ok,_Record} -> {true, Req, State};
_ -> {false, Req, State}
{ok,Record} -> {true, Req, State#request_state{ looked_up = Record }};
{error,_Reason} -> {false,Req,State}
end;
resource_exists(Req, #request_state{ method = ?HTTP_GET, resource = <<"ouis">>}=State) ->
case oui_server:lookup_oui(binary_to_list(State#request_state.id)) of
{ok,Record} -> {true, Req, State#request_state{ looked_up = Record }};
{error,Reason} -> create_error(101,Reason,Req,State)
{error,_Reason} -> {false,Req,State}
end;
resource_exists(Req, #request_state{ method = ?HTTP_GET, resource = <<"vendors">>}=State) ->
case oui_server:lookup_vendor(binary_to_list(State#request_state.id)) of
{ok,Record} -> {true, Req, State#request_state{ looked_up = Record }};
{error,Reason} -> create_error(101,Reason,Req,State)
{error,_Reason} -> {false,Req,State}
end;
resource_exists(Req, #request_state{ method = ?HTTP_GET, resource = <<"actions">>}=State) ->
case simengine:get_action(State#request_state.id) of
{ok,Record} -> {true, Req, State#request_state{ looked_up = Record }};
{error,_Reason} -> {false,Req,State}
end;
resource_exists(Req, #request_state{ method = ?HTTP_GET, resource = <<"simulations">>, subres = <<"devices">>, subid=nothing }=State) ->
case simengine:get(State#request_state.id) of
{ok,Record} -> {true, Req, State#request_state{ looked_up = Record }};
{error,_Reason} -> {false,Req,State}
end;
resource_exists(Req, #request_state{ method = ?HTTP_GET, resource = <<"simulations">>, subres = <<"devices">>}=State) ->
case inventory:get_client(State#request_state.id,State#request_state.subid) of
{ok,Record} -> {true, Req, State#request_state{ looked_up = Record }};
{error,_Reason} -> {false,Req,State}
end;
resource_exists(Req, #request_state{ method = ?HTTP_GET, resource = <<"simulations">>, subres = nothing }=State) ->
case simengine:get(State#request_state.id) of
{ok,Record} -> {true, Req, State#request_state{ looked_up = Record }};
{error,_Reason} -> {false,Req,State}
end;
resource_exists(Req,State)->
{true, Req, State}.
{ false , Req , State }.
options(Req0, State) ->
%% io:format("Calling OPTIONS/2~n"),
@@ -137,108 +160,79 @@ db_to_json(Req, State) ->
do( State#request_state.method , Req , State ).
%%%===================================================================
%%% Internal functions
%%% OUI Management
%%%===================================================================
do( ?HTTP_GET , Req , #request_state{ resource = <<"ouis">> , id = nothing } = State ) ->
PaginationParameters = restutils:get_pagination_parameters(Req),
{ok,OUIs}=oui_server:get_ouis(),
{ SubList, PaginationInfo } = restutils:paginate(PaginationParameters,OUIs),
JSON = restutils:create_paginated_return( "OUIs" , SubList, PaginationInfo),
{JSON,restutils:add_CORS(Req),State};
{restutils:create_paginated_return( "OUIs" , SubList, PaginationInfo),Req,State};
do( ?HTTP_GET , Req , #request_state{ resource = <<"ouis">> } = State ) ->
Maker = State#request_state.looked_up,
JSON = binary:list_to_bin([<<"{ \"OUI\" : \"">> , State#request_state.id, <<"\" , \"Vendor\" : \"">>, Maker, <<"\" }">>]),
{JSON,restutils:add_CORS(Req),State};
{JSON,Req,State};
%%%===================================================================
%%% Vendor Management
%%%===================================================================
do( ?HTTP_GET , Req , #request_state{ resource = <<"vendors">> , id = nothing } = State ) ->
PaginationParameters = restutils:get_pagination_parameters(Req),
{ok,Vendors}=oui_server:get_vendors(),
{ SubList , PaginationInfo } = restutils:paginate(PaginationParameters,Vendors),
JSON = restutils:create_paginated_return("Vendors",SubList,PaginationInfo),
{JSON,restutils:add_CORS(Req),State};
{restutils:create_paginated_return("Vendors",SubList,PaginationInfo),Req,State};
do( ?HTTP_GET , Req , #request_state{ resource = <<"vendors">> } = State ) ->
Maker = State#request_state.id,
JSON = binary:list_to_bin([<<"{ \"Vendor\" : \"">> , Maker, <<"\" , \"OUIs\" : [ ">>,
restutils:dump_string_array(State#request_state.looked_up), <<" ] }">>]),
{JSON,restutils:add_CORS(Req),State};
{JSON,Req,State};
%% list CAs
do( ?HTTP_GET ,Req,#request_state{resource = <<"cas">>,id=nothing}=State)->
%%%===================================================================
%%% Simulation Devices Management
%%%===================================================================
do( ?HTTP_GET ,Req,#request_state{ resource = <<"simulations">>, subres = <<"devices">>, subid = nothing }=State)->
PaginationParameters = restutils:get_pagination_parameters(Req),
{ok,CAs}=inventory:get_cas(),
{SubList,PaginationInfo} = restutils:paginate(PaginationParameters,CAs),
JSON = restutils:create_paginated_return("CAs",SubList,PaginationInfo),
{JSON,restutils:add_CORS(Req),State};
do( ?HTTP_POST ,Req,#request_state{resource = <<"cas">>}=State)->
{ok,DeviceList}=inventory:list_clients(State#request_state.id),
{SubList,PaginationInfo} = restutils:paginate(PaginationParameters,DeviceList),
{restutils:create_paginated_return("SerialNumbers",SubList,PaginationInfo),Req,State};
do( ?HTTP_GET ,Req,#request_state{ resource = <<"simulations">>, subres = <<"devices">> }=State)->
try
{ok,RawData,Req1} = cowboy_req:read_body(Req),
ReqFields = jsx:decode(RawData,[return_maps]), %% do not use jiffy here...
#{ <<"name">> := CAName, <<"key">> := Key, <<"cert">> := Cert, <<"password">> := Password } = ReqFields,
CAName = State#request_state.id,
KeyFileName = filename:join([utils:priv_dir(),"tmp-key-" ++ binary_to_list(CAName)]),
CertFileName = filename:join([utils:priv_dir(),"tmp-cert-" ++ binary_to_list(CAName)]),
ok = file:write_file( KeyFileName, Key ),
ok = file:write_file( CertFileName, Cert),
ok = user_default:import_ca(binary_to_list(CAName),binary_to_list(Password),KeyFileName,CertFileName),
{ok,CA} = inventory:get_ca(CAName),
{ _ , RawKey } = CA#ca_info.key,
CAInfo = #{ name => CA#ca_info.name,
key => list_to_binary(base64:encode_to_string(RawKey)),
cert => list_to_binary(base64:encode_to_string(CA#ca_info.cert))},
JSON = jiffy:encode(CAInfo),
Req2 = cowboy_req:set_resp_body(JSON,Req1),
{true,restutils:add_CORS(Req2),State}
ClientInfo = State#request_state.looked_up,
{_,Key} = ClientInfo#client_info.key,
Map = #{
simulation => State#request_state.id,
serial => State#request_state.subid,
ca => ClientInfo#client_info.ca,
bands => ClientInfo#client_info.bands,
wan_mac => ClientInfo#client_info.wan_mac0,
lan_mac => ClientInfo#client_info.lan_mac0,
lan_clients => make_lan_clients(ClientInfo#client_info.lan_clients),
wan_clients => make_wan_clients(ClientInfo#client_info.wifi_clients),
key => list_to_binary(base64:encode_to_string(Key)),
cert => list_to_binary(base64:encode_to_string(ClientInfo#client_info.cert))
},
create_response(jiffy:encode( Map ),Req,State)
catch
_:_ ->
create_error(102,"Some fields are invalid or missing.",Req,State)
create_error(102,"Cannot find the device.",Req,State)
end;
do( ?HTTP_GET ,Req,#request_state{resource = <<"cas">>}=State)->
CA = State#request_state.looked_up,
{ _ , RawKey } = CA#ca_info.key,
CAInfo = #{ name => CA#ca_info.name,
key => list_to_binary(base64:encode_to_string(RawKey)),
cert => list_to_binary(base64:encode_to_string(CA#ca_info.cert)),
location => CA#ca_info.dir_name,
configuration => CA#ca_info.config_data },
JSON = jiffy:encode(CAInfo),
{JSON,restutils:add_CORS(Req),State};
do( ?HTTP_GET , Req , #request_state{ resource = <<"nodes">> , id = nothing } = State ) ->
PaginationParameters = restutils:get_pagination_parameters(Req),
{ok,AllNodes}=manager:connected_nodes(),
{ SubList, PaginationInfo } = restutils:paginate(PaginationParameters,[{node(),manager}|AllNodes]),
JSON = case restutils:get_parameter(details,0,Req) of
0 -> NamesOnly = [ atom_to_list(X) || {X,Role} <- SubList, Role == node ],
restutils:create_paginated_return( "Nodes" , NamesOnly, PaginationInfo);
1 -> restutils:create_paginated_return( "Nodes" , SubList, PaginationInfo,nodes)
end,
{JSON,restutils:add_CORS(Req),State};
do( ?HTTP_GET ,Req,#request_state{resource = <<"hardware_definitions">>,id=nothing}=State)->
PaginationParameters = restutils:get_pagination_parameters(Req),
{ok,Definitions}=hardware:get_definitions(),
{SubList,PaginationInfo} = restutils:paginate_record_list(PaginationParameters,Definitions),
JSON = restutils:create_paginated_return("HardwareDefinitions",SubList,PaginationInfo,hardware_info),
{JSON,restutils:add_CORS(Req),State};
%%%===================================================================
%%% Simulation Management
%%%===================================================================
do( ?HTTP_GET ,Req,#request_state{resource = <<"simulations">>,id=nothing}=State)->
PaginationParameters = restutils:get_pagination_parameters(Req),
{ok,Simulations}=simengine:list_simulations(),
{SubList,PaginationInfo} = restutils:paginate(PaginationParameters,Simulations),
JSON = restutils:create_paginated_return("Simulations",SubList,PaginationInfo),
{JSON,restutils:add_CORS(Req),State};
create_response(JSON,Req,State);
do( ?HTTP_GET , Req , #request_state{ resource = <<"simulations">> } = State ) ->
S = State#request_state.looked_up,
Sim = #{ name => S#simulation.name, caname => S#simulation.ca, num_devices => S#simulation.num_devices, nodes => S#simulation.nodes,
server => S#simulation.opensync_server_name,
port=> S#simulation.opensync_server_port ,
assets_created => S#simulation.assets_created },
{jiffy:encode(Sim),restutils:add_CORS(Req),State};
server => S#simulation.opensync_server_name,
port=> S#simulation.opensync_server_port ,
assets_created => S#simulation.assets_created },
create_response(jiffy:encode(Sim),Req,State);
do( ?HTTP_POST , Req , #request_state{ resource = <<"simulations">> } = State ) ->
{ok,Data,Req1} = cowboy_req:read_body(Req),
@@ -264,8 +258,7 @@ do( ?HTTP_POST , Req , #request_state{ resource = <<"simulations">> } = State )
assets_created => NewSim#simulation.assets_created },
JSON = jiffy:encode(Sim),
Req2 = cowboy_req:set_resp_header(<<"location">>, URI, Req1),
Req3 = cowboy_req:set_resp_body(JSON,Req2),
{true,restutils:add_CORS(Req3),State};
create_response(JSON,Req2,State);
_ -> %% we are creating a new simulation
NewSim = #simulation{
ca = CAName,
@@ -278,29 +271,95 @@ do( ?HTTP_POST , Req , #request_state{ resource = <<"simulations">> } = State )
simengine:create(NewSim),
URI = << <<"/api/v1/simulations/">>/binary, (State#request_state.id)/binary >>,
Sim = #{ name => NewSim#simulation.name, caname => NewSim#simulation.ca, num_devices => NewSim#simulation.num_devices, nodes => NewSim#simulation.nodes,
server => NewSim#simulation.opensync_server_name,
port=> NewSim#simulation.opensync_server_port ,
assets_created => NewSim#simulation.assets_created },
server => NewSim#simulation.opensync_server_name,
port=> NewSim#simulation.opensync_server_port ,
assets_created => NewSim#simulation.assets_created },
JSON = jiffy:encode(Sim),
Req2 = cowboy_req:set_resp_header(<<"location">>, URI, Req1),
Req3 = cowboy_req:set_resp_body(JSON,Req2),
{true,restutils:add_CORS(Req3),State}
create_response(JSON,Req2,State)
end;
false ->
create_error(102,"Some fields are invalid or missing. Must have at least 11 valid node, port must not be 0, caname must exist",Req1,State)
end;
%%%===================================================================
%%% CAs Management
%%%===================================================================
do( ?HTTP_GET ,Req,#request_state{resource = <<"cas">>,id=nothing}=State)->
PaginationParameters = restutils:get_pagination_parameters(Req),
{ok,CAs}=inventory:get_cas(),
{SubList,PaginationInfo} = restutils:paginate(PaginationParameters,CAs),
create_response(restutils:create_paginated_return("CAs",SubList,PaginationInfo),Req,State);
do( ?HTTP_POST ,Req,#request_state{resource = <<"cas">>}=State)->
try
{ok,RawData,Req1} = cowboy_req:read_body(Req),
ReqFields = jsx:decode(RawData,[return_maps]), %% do not use jiffy here...
#{ <<"name">> := CAName, <<"key">> := Key, <<"cert">> := Cert, <<"password">> := Password } = ReqFields,
CAName = State#request_state.id,
KeyFileName = filename:join([utils:priv_dir(),"tmp-key-" ++ binary_to_list(CAName)]),
CertFileName = filename:join([utils:priv_dir(),"tmp-cert-" ++ binary_to_list(CAName)]),
ok = file:write_file( KeyFileName, Key ),
ok = file:write_file( CertFileName, Cert),
ok = user_default:import_ca(binary_to_list(CAName),binary_to_list(Password),KeyFileName,CertFileName),
{ok,CA} = inventory:get_ca(CAName),
{ _ , RawKey } = CA#ca_info.key,
CAInfo = #{ name => CA#ca_info.name,
key => list_to_binary(base64:encode_to_string(RawKey)),
cert => list_to_binary(base64:encode_to_string(CA#ca_info.cert))},
JSON = jiffy:encode(CAInfo),
Req2 = cowboy_req:set_resp_body(JSON,Req1),
{true,Req2,State}
catch
_:_ ->
create_error(102,"Some fields are invalid or missing.",Req,State)
end;
do( ?HTTP_GET ,Req,#request_state{resource = <<"cas">>}=State)->
CA = State#request_state.looked_up,
{ _ , RawKey } = CA#ca_info.key,
CAInfo = #{ name => CA#ca_info.name,
key => list_to_binary(base64:encode_to_string(RawKey)),
cert => list_to_binary(base64:encode_to_string(CA#ca_info.cert)),
location => CA#ca_info.dir_name,
configuration => CA#ca_info.config_data },
create_response(jiffy:encode(CAInfo),Req,State);
%%%===================================================================
%%% Nodes Management
%%%===================================================================
do( ?HTTP_GET , Req , #request_state{ resource = <<"nodes">> , id = nothing } = State ) ->
PaginationParameters = restutils:get_pagination_parameters(Req),
{ok,AllNodes}=manager:connected_nodes(),
{ SubList, PaginationInfo } = restutils:paginate(PaginationParameters,[{node(),manager}|AllNodes]),
JSON = case restutils:get_parameter(details,0,Req) of
0 -> NamesOnly = [ atom_to_list(X) || {X,Role} <- SubList, Role == node ],
restutils:create_paginated_return( "Nodes" , NamesOnly, PaginationInfo);
1 -> restutils:create_paginated_return( "Nodes" , SubList, PaginationInfo,nodes)
end,
create_response(JSON,Req,State);
%%%===================================================================
%%% Hardware Definitions Management
%%%===================================================================
do( ?HTTP_GET ,Req,#request_state{resource = <<"hardware_definitions">>,id=nothing}=State)->
PaginationParameters = restutils:get_pagination_parameters(Req),
{ok,Definitions}=hardware:get_definitions(),
{SubList,PaginationInfo} = restutils:paginate_record_list(PaginationParameters,Definitions),
JSON = restutils:create_paginated_return("HardwareDefinitions",SubList,PaginationInfo,hardware_info),
create_response(JSON,Req,State);
%%%===================================================================
%%% Actions Management
%%%===================================================================
do( ?HTTP_GET ,Req,#request_state{resource = <<"actions">>,id=nothing}=State)->
PaginationParameters = restutils:get_pagination_parameters(Req),
{ok,Actions}=simengine:list_actions(),
{SubList,PaginationInfo} = restutils:paginate_record_list(PaginationParameters,Actions),
JSON = restutils:create_paginated_return("Actions",SubList,PaginationInfo,sim_action),
{JSON,restutils:add_CORS(Req),State};
create_response(JSON,Req,State);
do( ?HTTP_GET , Req , #request_state{ resource = <<"actions">> } = State ) ->
S = State#request_state.looked_up,
{simengine:sim_action_to_json(S),restutils:add_CORS(Req),State};
create_response(simengine:sim_action_to_json(S),Req,State);
do( ?HTTP_POST , Req , #request_state{ resource = <<"actions">> } = State ) ->
{ok,Data,Req1} = cowboy_req:read_body(Req),
Res = jiffy:decode(Data,[return_maps]),
@@ -330,8 +389,7 @@ do( ?HTTP_POST , Req , #request_state{ resource = <<"actions">> } = State ) ->
%% io:format("URI: ~p~n",[URI]),
Body = #{ action => Action, simulation => SimName, id => Id },
Req2 = cowboy_req:set_resp_header(<<"location">>, URI, Req1),
Req3 = cowboy_req:set_resp_body( jiffy:encode(Body) , Req2),
{true,restutils:add_CORS(Req3),State};
create_response(jiffy:encode(Body),Req2,State);
{ error, _Reason } ->
create_error(102,"Operation request was denied. Service already busy.",Req1,State)
end;
@@ -358,10 +416,17 @@ process_attributes([H|T],Acc)->
process_attributes(T,Acc)
end.
create_error(Error,Reason,Req,State) ->
create_error(Error,Reason,Req,#request_state{ method = ?HTTP_GET} = State ) ->
{restutils:generate_error(Error,Reason), Req,State};
create_error(Error,Reason,Req,#request_state{ method = ?HTTP_POST} = State ) ->
Req1 = cowboy_req:set_resp_body(restutils:generate_error(Error,Reason), Req),
{false, restutils:add_CORS(Req1),State}.
{false,Req1,State}.
create_response(JSON,Req,#request_state{ method = ?HTTP_POST} = State ) ->
Req1 = cowboy_req:set_resp_body(JSON,Req),
{ true, Req1,State};
create_response(JSON,Req,#request_state{ method = ?HTTP_GET} = State ) ->
{ JSON,Req,State}.
validate(simulations,Data)->
try
@@ -407,3 +472,21 @@ validate_name(nodes,[H|T])->
false ->
false
end.
make_lan_clients(Clients)->
make_lan_clients(Clients,#{}).
make_lan_clients([],Acc)->
Acc;
make_lan_clients([{Port,Clients}|T],Acc) ->
make_lan_clients(T,maps:put(Port,Clients,Acc)).
make_wan_clients(Clients)->
make_wan_clients(Clients,#{}).
make_wan_clients([],Acc)->
Acc;
make_wan_clients([{Band,_SSID,Clients}|T],Acc)->
make_wan_clients(T,maps:put(Band,Clients,Acc)).

View File

@@ -59,7 +59,7 @@ init([]) ->
Dispatch = cowboy_router:compile([
{
'_', [
{ "/api/v1/:restype/[:resid]", manager_api_rest_handler, [] },
{ "/api/v1/:restype/[:resid/[:subres/[:subid]]]", manager_api_rest_handler, [] },
{ "/ws", web_socket_handler, [] },
{ "/", cowboy_static, {file,filename:join([PrivDir,"www/index.html"])} },
{ "/[...]", cowboy_static, {dir, filename:join([PrivDir,"www"])} }
@@ -71,17 +71,16 @@ init([]) ->
cowboy:start_tls(
rest_http_listener,
[
{ port, Port } ,
{cacertfile, filename:join([PrivDir,"ssl","sim_cert.pem"])},
{certfile, filename:join([PrivDir,"ssl","server-api-cert.pem"])},
{keyfile, filename:join([PrivDir,"ssl","server-api-key_dec.pem"])} ],
{port,Port},{sndbuf,250000},
{certfile, filename:join([PrivDir,"ssl","web-server-api-cert.pem"])},
{keyfile, filename:join([PrivDir,"ssl","web-server-api-key_dec.pem"])} ],
#{env => #{dispatch => Dispatch}} );
false ->
?L_I("Starting in clear mode."),
cowboy:start_clear(
rest_http_listener,
[
{ port, Port }
{port,Port},{sndbuf,250000}
],
#{env => #{dispatch => Dispatch}} )
end,