diff --git a/src/manager_api_rest_handler.erl b/src/manager_api_rest_handler.erl index ff2bfd2..43608d6 100644 --- a/src/manager_api_rest_handler.erl +++ b/src/manager_api_rest_handler.erl @@ -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)). + + + diff --git a/src/manager_rest_api.erl b/src/manager_rest_api.erl index d09ca5d..e530f1c 100644 --- a/src/manager_rest_api.erl +++ b/src/manager_rest_api.erl @@ -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,