diff --git a/include/ovsdb_ap_tables.hrl b/include/ovsdb_ap_tables.hrl index ae522f0..c537f20 100644 --- a/include/ovsdb_ap_tables.hrl +++ b/include/ovsdb_ap_tables.hrl @@ -38,7 +38,7 @@ -record ('AWLAN_Node',{ - key_id :: binary() | ets_dont_care(), + '**key_id**' = <<>>:: binary() | ets_dont_care(), mqtt_settings = [<<"map">>,[]] :: term() | ets_dont_care(), sku_number = [<<"set">>,[]] :: term() | ets_dont_care(), model = <<>>:: term() | ets_dont_care(), @@ -67,7 +67,7 @@ -record ('Wifi_Stats_Config', { - key_id :: binary() | ets_dont_care(), + '**key_id**' = <<>>:: binary() | ets_dont_care(), '_version' = [<<"uuid">>,<<"4ad2c67d-99d6-4431-a6a7-09a0fa95b8e2">>] :: term(), radio_type = <<"2.4G">> :: term() | ets_dont_care(), sampling_interval = 10 :: term() | ets_dont_care(), @@ -83,19 +83,19 @@ }). -record ('Hotspot20_Config', { - key_id :: binary() | ets_dont_care() + '**key_id**' = <<>>:: binary() | ets_dont_care() }). -record ('Hotspot20_OSU_Providers', { - key_id :: binary() | ets_dont_care() + '**key_id**' = <<>>:: binary() | ets_dont_care() }). -record ('Hotspot20_Icon_Config', { - key_id :: binary() | ets_dont_care() + '**key_id**' :: binary() | ets_dont_care() }). -record ('Wifi_RRM_Config', { - key_id :: binary() | ets_dont_care(), + '**key_id**' = <<>> :: binary() | ets_dont_care(), '_version' = [<<"uuid">>,<<"9bbd18e7-ed7e-4ff3-b89d-a54c12b27ed7">>] :: term(), freq_band = <<"5GU">> :: term(), probe_resp_threshold = -90 :: term(), @@ -109,13 +109,13 @@ }). -record ('Command_State', { - key_id :: binary() | ets_dont_care() + '**key_id**' = <<>>:: binary() | ets_dont_care() }). -record ('Wifi_VIF_Config', { - key_id :: binary() | ets_dont_care(), + '**key_id**' = <<>> :: binary() | ets_dont_care(), vif_radio_idx = [<<"set">>,[]] :: term(), if_name = <<"wlan0">> :: term() | ets_dont_care(), ap_bridge = <<"">> :: term() | ets_dont_care(), @@ -154,12 +154,12 @@ -record ('Wifi_VIF_State', { - key_id :: binary() | ets_dont_care(), + '**key_id**' = <<>> :: binary() | ets_dont_care(), vif_radio_idx = [<<"set">>,[]]:: term() }). -record ('Wifi_Associated_Clients', { - key_id :: binary() | ets_dont_care(), + '**key_id**' = <<>> :: binary() | ets_dont_care(), '_version' = [<<"uuid">>,<<"5bc3eb0f-1cc3-4dae-aae5-af02c8d2f1c7">>] :: term(), mac = <<"">> :: term(), state = <<"">> :: term(), @@ -171,7 +171,7 @@ -record ('DHCP_leased_IP', { - key_id :: binary() | ets_dont_care(), + '**key_id**' = <<>> :: binary() | ets_dont_care(), db_status = 1 :: term(), subnet_mask = <<"255.255.255.0">> :: term(), hostname = <<"">> :: term(), @@ -184,14 +184,14 @@ vendor_class = <<"">> :: term(), device_type = 0 :: term(), dhcp_server = <<"192.168.1.1">> :: term(), - device_name = <<"">> :: term(), + device_name = <<"Simulation">> :: term(), fingerprint = <<"1,121,3,6,15,114,119,252">> :: term(), primary_dns = <<"192.168.1.1">> :: term(), gateway = <<"192.168.1.1">> :: term() }). -record ('Wifi_Radio_Config', { - key_id :: binary() | ets_dont_care(), + '**key_id**' = <<>> :: binary() | ets_dont_care(), dfs_demo = [<<"set">>,[]] :: term(), if_name = <<"radio0">> :: term() | ets_dont_care(), temperature_control = [<<"map">>,[]] :: term(), @@ -222,7 +222,7 @@ }). -record ('Wifi_Radio_State',{ - key_id :: binary() | ets_dont_care(), + '**key_id**' = <<>> :: binary() | ets_dont_care(), if_name = <<>> :: term(), dfs_demo = [<<"set">>,[]] :: term(), thermal_downgraded = [<<"set">>,[]] :: term(), @@ -259,7 +259,7 @@ }). -record ('Wifi_Inet_Config', { - key_id :: binary() | ets_dont_care(), + '**key_id**' = <<>> :: binary() | ets_dont_care(), if_name = <<"">> :: term(), dhcpd = [<<"map">>,[]] :: term(), dhcp_sniff = false :: term(), @@ -294,7 +294,7 @@ }). -record ('Wifi_Inet_State',{ - key_id :: binary() | ets_dont_care(), + '**key_id**' = <<>> :: binary() | ets_dont_care(), dhcpd = [<<"map">>,[]] :: term(), if_name = <<"">> :: term(), upnp_mode = [<<"set">>,[]] :: term(), @@ -333,6 +333,7 @@ initial :: boolean(), insert :: boolean(), delete :: boolean(), - modify :: boolean() + modify :: boolean(), + published = true :: boolean() }). diff --git a/src/ovsdb_ap.erl b/src/ovsdb_ap.erl index cd5d7dc..ac853f7 100644 --- a/src/ovsdb_ap.erl +++ b/src/ovsdb_ap.erl @@ -14,6 +14,7 @@ -include("../include/common.hrl"). -include("../include/ovsdb_definitions.hrl"). -include("../include/ovsdb_ap_tables.hrl"). +-include("../include/opensync_stats.hrl"). -define(SERVER, ?MODULE). @@ -25,7 +26,7 @@ -export([start_ap/1,stop_ap/1,pause_ap/1,cancel_ap/1]). %% comm API --export([rpc_cmd/2,rpc_request/2,reset_comm/1,mqtt_conf/2,post_event/4,post_event/3]). +-export([rpc_cmd/2,rpc_request/2,reset_comm/1,mqtt_conf/2,post_event/4,post_event/3,check_publish_monitor/1,check_for_mqtt_updates/1]). %% gen_server callbacks @@ -48,7 +49,8 @@ store :: ets:tid(), % the tables where OVSDB server stores info req_queue :: ets:tid(), % not used at the moment ... used to que request IDs reporting :: timer:tref(), % statistics reporting interval timer reference - stats_ets :: ets:tid() % statistics table + stats_ets :: ets:tid(), % statistics table + updates = none :: none | timer:tref() % timer reference for periodic updates from MQTT while running }). @@ -147,6 +149,14 @@ post_event (Node, Event, Args, Comment) -> post_event (Event, Args, Comment) -> post_event(self(),Event,Args,Comment). +-spec check_publish_monitor (Node :: pid()) -> ok. +check_publish_monitor (Node) -> + gen_server:cast(Node,check_publish_monitor). + +-spec check_for_mqtt_updates (Node :: pid()) -> ok. +check_for_mqtt_updates (Node) -> + gen_server:cast(Node,check_mqtt_updates). + @@ -226,6 +236,14 @@ handle_cast ({stats_update,Event},State) -> true = update_statistics(Event,State#ap_state.stats_ets), {noreply, State}; +handle_cast (check_mqtt_updates, State) -> + S = request_mqtt_updates(State), + {noreply, S}; + +handle_cast (check_publish_monitor, State) -> + ovsdb_ap_monitor:publish_unpublished(State#ap_state.store), + {noreply, State}; + handle_cast (send_report,State) -> {noreply, report_statistics(State)}; @@ -337,6 +355,9 @@ handle_info({'EXIT', Pid, Reason}, State) -> ?L_E(?DBGSTR("Abnormal exit from ~p with reason: ~p",[Pid,Reason])), {noreply, State}; +handle_info({client_stats,Serial,Stats},State) -> + S = handle_mqtt_stats_update(Serial,Stats,State), + {noreply, S}; handle_info (Msg,State) -> ?L_E(?DBGSTR("got unexpected info message ~p",[Msg])), @@ -385,7 +406,7 @@ code_change (_,OldState,_) -> prepare_state (CAName, ID, Options) -> Store = ets:new(ovsdb_ap,[bag,private,{keypos, 1}]), Stats = ets:new(ovsdb_ap_stats,[ordered_set,private,{keypos, 2}]), - {'ok', Tref} = timer:apply_interval(proplists:get_value(report_int,Options,?AP_REPORT_INTERVAL), + {ok, Tref} = timer:apply_interval(proplists:get_value(report_int,Options,?AP_REPORT_INTERVAL), gen_server,cast,[self(),send_report]), Redirector = proplists:get_value(redirector,Options,<<"">>), update_statistics({report_mark,{},<<>>},Stats), @@ -415,7 +436,21 @@ set_status (Status, #ap_state{status=OldStatus, config=Cfg}=State) -> ovsdb_client_handler:ap_status(Status,ovsdb_ap_config:id(Cfg)), post_event(status_change,{OldStatus,Status},io_lib:format("status change := ~p -> ~p",[OldStatus,Status])), ?L_I(?DBGSTR("AP ~p : status change := ~p -> ~p",[self(),OldStatus,Status])), - State#ap_state{status=Status}. + start_stop_mqtt_updates(State#ap_state{status=Status}). + +-spec start_stop_mqtt_updates (State :: #ap_state{}) -> NewState :: #ap_state{}. +start_stop_mqtt_updates(#ap_state{status=running, updates=none}=State) -> + {ok, Ref} = timer:apply_interval(60000,?MODULE,check_for_mqtt_updates,[self()]), + State#ap_state{updates=Ref}; +start_stop_mqtt_updates(#ap_state{status=running}=State) -> + State; +start_stop_mqtt_updates(#ap_state{updates=U}=State) when U =/= none-> + {ok, cancel} = timer:cancel(U), + State#ap_state{updates=none}; +start_stop_mqtt_updates(State) -> + State. + + @@ -598,6 +633,20 @@ stop_mqtt(#ap_state{mqtt=idle}=State) -> stop_mqtt(#ap_state{ca_name=CAName, id=ID}=State) -> _ = mqtt_client_manager:stop_client(CAName,ID), State#ap_state{mqtt=idle}. + +-spec request_mqtt_updates (State :: #ap_state{}) -> NewState :: #ap_state{}. +request_mqtt_updates (#ap_state{config=Cfg} = State) -> + CA = ovsdb_ap_config:caname(Cfg), + Serial = ovsdb_ap_config:serial(Cfg), + Mqtt = mqtt_client_manager:get_client_pid(CA,Serial), + Mqtt ! {send_stats, self()}, + State. + +-spec handle_mqtt_stats_update (Serial :: binary(), Stats :: #{binary() => #'Client.Stats'{}}, State :: #ap_state{}) -> NewState :: #ap_state{}. +handle_mqtt_stats_update (_Serial,_Stats,State) -> + io:format("GOT MQTT STATS:~n"), + State. + diff --git a/src/ovsdb_ap_config.erl b/src/ovsdb_ap_config.erl index 5be040d..c1053e7 100644 --- a/src/ovsdb_ap_config.erl +++ b/src/ovsdb_ap_config.erl @@ -19,6 +19,7 @@ -record (cfg, { ca_name :: string() | binary(), redirector :: binary(), + serial :: binary(), id :: binary(), store_ref :: ets:tid(), cacert = <<>> :: binary(), % pem file (in memory) of the server certificate chain @@ -31,7 +32,7 @@ -export([new/4,configure/1]). --export ([id/1,ca_certs/1,client_cert/1,client_key/1,tip_redirector/2,tip_manager/2]). +-export ([id/1,ca_certs/1,client_cert/1,client_key/1,tip_redirector/2,tip_manager/2,caname/1,serial/1]). %%------------------------------------------------------------------------------ @@ -65,7 +66,8 @@ configure (#cfg{ca_name=CAName, id=ID, redirector=R}=Config) -> ], initialize_ap_tables(Config#cfg.store_ref,validate_config(APC)), Config#cfg{ - cacert = Info#client_info.cacert, + cacert = Info#client_info.cacert, + serial = Info#client_info.serial, cert = Info#client_info.cert, key = Info#client_info.key }. @@ -112,6 +114,14 @@ initialize_ap_tables (Store, APC) -> id(Cfg) -> Cfg#cfg.id. +-spec caname (Config :: cfg()) -> CAName :: binary(). +caname(Cfg) -> + Cfg#cfg.ca_name. + +-spec serial (Config :: cfg()) -> Serial :: binary(). +serial(Cfg) -> + Cfg#cfg.serial. + -spec ca_certs (Config :: cfg()) -> binary(). ca_certs (Cfg) -> Cfg#cfg.cacert. @@ -169,7 +179,7 @@ modify_mac (MAC) -> -spec create_table (Table :: atom(), AP_Config :: [{atom(),term()}], Store :: ets:tid()) -> true. create_table ('Wifi_Radio_State',APC,Store) -> ets:insert(Store, #'Wifi_Radio_State'{ - key_id = utils:uuid_b(), + '**key_id**' = utils:uuid_b(), if_name = <<"radio0">>, mac = modify_mac(proplists:get_value(lan_mac,APC)), bcn_int = 100, @@ -188,7 +198,7 @@ create_table ('Wifi_Radio_State',APC,Store) -> freq_band = <<"5GU">> }), ets:insert(Store, #'Wifi_Radio_State' { - key_id = utils:uuid_b(), + '**key_id**' = utils:uuid_b(), if_name = <<"radio1">>, mac = modify_mac(proplists:get_value(lan_mac,APC)), bcn_int = 100, @@ -208,7 +218,7 @@ create_table ('Wifi_Radio_State',APC,Store) -> freq_band = <<"2.4G">> }), ets:insert(Store, #'Wifi_Radio_State'{ - key_id = utils:uuid_b(), + '**key_id**' = utils:uuid_b(), if_name = <<"radio2">>, mac = modify_mac(proplists:get_value(lan_mac,APC)), bcn_int = 100, @@ -229,20 +239,20 @@ create_table ('Wifi_Radio_State',APC,Store) -> create_table ('Wifi_Radio_Config',_APC,Store) -> ets:insert(Store, #'Wifi_Radio_Config'{ - key_id = <<"830bd195-7114-4e99-9b51-5622e47ce221">>, + '**key_id**' = <<"830bd195-7114-4e99-9b51-5622e47ce221">>, '_uuid' = [<<"uuid">>, <<"830bd195-7114-4e99-9b51-5622e47ce221">>], freq_band = <<"5GU">>, if_name = <<"radio0">> }), ets:insert(Store, #'Wifi_Radio_Config'{ - key_id = <<"94f9b810-8c71-4961-a9c0-7f3a96869368">>, + '**key_id**' = <<"94f9b810-8c71-4961-a9c0-7f3a96869368">>, '_uuid' = [<<"uuid">>, <<"94f9b810-8c71-4961-a9c0-7f3a96869368">>], freq_band = <<"5GL">>, if_name = <<"radio2">> }), ets:insert(Store, #'Wifi_Radio_Config'{ - key_id = <<"fb11d840-cbe9-4e32-9744-ebcda9162e52">>, + '**key_id**' = <<"fb11d840-cbe9-4e32-9744-ebcda9162e52">>, '_uuid' = [<<"uuid">>, <<"fb11d840-cbe9-4e32-9744-ebcda9162e52">>], freq_band = <<"2.4G">>, if_name = <<"radio1">> @@ -250,7 +260,7 @@ create_table ('Wifi_Radio_Config',_APC,Store) -> create_table ('Wifi_Inet_State',APC,Store) -> ets:insert(Store, #'Wifi_Inet_State'{ - key_id = utils:uuid_b(), + '**key_id**' = utils:uuid_b(), if_name= <<"wwan">>, if_type = <<"eth">>, enabled = false, @@ -258,7 +268,7 @@ create_table ('Wifi_Inet_State',APC,Store) -> inet_config = [<<"uuid">>,<<"7e38a63b-526a-4b83-b30e-edd4c17ab3f6">>] }), ets:insert(Store, #'Wifi_Inet_State'{ - key_id = utils:uuid_b(), + '**key_id**' = utils:uuid_b(), dhcpd = [<<"map">>,[[<<"lease_time">>,<<"12h">>],[<<"start">>,<<"100">>],[<<"stop">>,<<"150">>]]], if_name= <<"lan">>, if_type = <<"bridge">>, @@ -273,7 +283,7 @@ create_table ('Wifi_Inet_State',APC,Store) -> inet_config = [<<"uuid">>,<<"19484645-8519-4bd0-98dd-13f1fec83395">>] }), ets:insert(Store, #'Wifi_Inet_State'{ - key_id = utils:uuid_b(), + '**key_id**' = utils:uuid_b(), if_name= <<"wan6">>, if_type = <<"eth">>, enabled = false, @@ -281,7 +291,7 @@ create_table ('Wifi_Inet_State',APC,Store) -> inet_config = [<<"uuid">>,<<"b803af39-e392-437b-8c86-dd87d24f8b49">>] }), ets:insert(Store, #'Wifi_Inet_State'{ - key_id = utils:uuid_b(), + '**key_id**' = utils:uuid_b(), if_name= <<"wan">>, if_type = <<"bridge">>, enabled = true, @@ -300,7 +310,7 @@ create_table ('Wifi_Inet_State',APC,Store) -> create_table ('Wifi_Inet_Config',APC,Store) -> ets:insert(Store, #'Wifi_Inet_Config'{ - key_id = <<"1a533ecc-90d7-499e-a76c-0d593a446fdb">>, + '**key_id**' = <<"1a533ecc-90d7-499e-a76c-0d593a446fdb">>, '_uuid' = [<<"uuid">>, <<"1a533ecc-90d7-499e-a76c-0d593a446fdb">>], dhcpd = [<<"map">>,[]], if_name = <<"wan">>, @@ -318,7 +328,7 @@ create_table ('Wifi_Inet_Config',APC,Store) -> inet_addr = [<<"set">>,[]] }), ets:insert(Store, #'Wifi_Inet_Config'{ - key_id = <<"b803af39-e392-437b-8c86-dd87d24f8b49">>, + '**key_id**' = <<"b803af39-e392-437b-8c86-dd87d24f8b49">>, '_uuid' = [<<"uuid">>, <<"b803af39-e392-437b-8c86-dd87d24f8b49">>], if_name = <<"wan6">>, network = true, @@ -327,7 +337,7 @@ create_table ('Wifi_Inet_Config',APC,Store) -> 'NAT' = false }), ets:insert(Store, #'Wifi_Inet_Config'{ - key_id = <<"7e38a63b-526a-4b83-b30e-edd4c17ab3f6">>, + '**key_id**' = <<"7e38a63b-526a-4b83-b30e-edd4c17ab3f6">>, '_uuid' = [<<"uuid">>, <<"7e38a63b-526a-4b83-b30e-edd4c17ab3f6">>], if_name = <<"wwan">>, network = true, @@ -337,7 +347,7 @@ create_table ('Wifi_Inet_Config',APC,Store) -> ip_assign_scheme = <<"dhcp">> }), ets:insert(Store, #'Wifi_Inet_Config'{ - key_id = <<"19484645-8519-4bd0-98dd-13f1fec83395">>, + '**key_id**' = <<"19484645-8519-4bd0-98dd-13f1fec83395">>, '_uuid' = [<<"uuid">>, <<"19484645-8519-4bd0-98dd-13f1fec83395">>], dhcpd = [<<"map">>,[[<<"lease_time">>,<<"12h">>],[<<"start">>,<<"100">>],[<<"stop">>,<<"150">>]]], if_name = <<"lan">>, @@ -352,7 +362,7 @@ create_table ('Wifi_Inet_Config',APC,Store) -> create_table ('Wifi_RRM_Config',_APC,Store) -> ets:insert(Store,#'Wifi_RRM_Config'{ - key_id = <<"d1f9874c-d8e7-4426-9d70-c856c4dc6126">>, + '**key_id**' = <<"d1f9874c-d8e7-4426-9d70-c856c4dc6126">>, '_version' = [<<"uuid">>,<<"9bbd18e7-ed7e-4ff3-b89d-a54c12b27ed7">>], freq_band = <<"2.4G">>, min_load = 50, @@ -361,7 +371,7 @@ create_table ('Wifi_RRM_Config',_APC,Store) -> snr_percentage_drop = 20 }), ets:insert(Store,#'Wifi_RRM_Config'{ - key_id = <<"8cf973a6-a268-4de4-9bf2-5f7d9222f806">>, + '**key_id**' = <<"8cf973a6-a268-4de4-9bf2-5f7d9222f806">>, '_version' = [<<"uuid">>,<<"9bbd18e7-ed7e-4ff3-b89d-a54c12b27ed7">>], freq_band = <<"5GL">>, min_load = 40, @@ -370,7 +380,7 @@ create_table ('Wifi_RRM_Config',_APC,Store) -> snr_percentage_drop = 30 }), ets:insert(Store,#'Wifi_RRM_Config'{ - key_id = <<"44deb01a-a2a8-4b5b-a2be-0bdf04050b97">>, + '**key_id**' = <<"44deb01a-a2a8-4b5b-a2be-0bdf04050b97">>, '_version' = [<<"uuid">>,<<"9bbd18e7-ed7e-4ff3-b89d-a54c12b27ed7">>], freq_band = <<"5GU">>, min_load = 40, @@ -383,7 +393,7 @@ create_table ('Wifi_Associated_Clients',APC,Store) -> %io:format("CONFIGURED WIFI CLIENTS:~n~p~n",[proplists:get_value(wifi_clients,APC)]), F = fun({_,_,[MAC|_]}) -> ets:insert(Store, #'Wifi_Associated_Clients'{ - key_id = utils:uuid_b(), + '**key_id**' = utils:uuid_b(), '_version' = [<<"uuid">>, utils:uuid_b()], mac = MAC, state = <<"active">> @@ -391,7 +401,7 @@ create_table ('Wifi_Associated_Clients',APC,Store) -> end, [F(X) || X <- proplists:get_value(wifi_clients,APC)]; % ets:insert(Store, #'Wifi_Associated_Clients'{ - % key_id = <<"ee49ed4e-5a04-4100-bf6a-ebfbbc54250e">>, + % '**key_id**' = <<"ee49ed4e-5a04-4100-bf6a-ebfbbc54250e">>, % '_version' = [<<"uuid">>,<<"5bc3eb0f-1cc3-4dae-aae5-af02c8d2f1c7">>], % mac = <<"52:b6:76:03:6d:f2">>, % state = <<"active">>, @@ -402,40 +412,42 @@ create_table ('Wifi_Associated_Clients',APC,Store) -> % }); create_table ('DHCP_leased_IP',APC,Store) -> + CL = proplists:get_value(wifi_clients,APC), + NM = proplists:get_value(name,APC), F = fun(N,{_,_,[MAC|_]}) -> ets:insert(Store, #'DHCP_leased_IP'{ - key_id = utils:uuid_b(), + '**key_id**' = utils:uuid_b(), '_version' = [<<"uuid">>, utils:uuid_b()], hostname = iolist_to_binary([proplists:get_value(name,APC),"_",integer_to_list(N)]), inet_addr = iolist_to_binary(["192.168.1.",integer_to_list(N+1)]), - hwaddr = MAC + hwaddr = MAC, + device_name = iolist_to_binary([NM,".SimClient_",integer_to_list(N+1)]) }) end, - CL = proplists:get_value(wifi_clients,APC), [F(N,X) || {N,X} <- lists:zip(lists:seq(1,length(CL)),CL)]; create_table ('Wifi_Stats_Config',_APC,Store) -> ets:insert(Store, #'Wifi_Stats_Config'{ - key_id = <<"f84b6834-80d6-4fd6-af73-98e3f4f96033">>, + '**key_id**' = <<"f84b6834-80d6-4fd6-af73-98e3f4f96033">>, '_uuid' = [<<"uuid">>,<<"f84b6834-80d6-4fd6-af73-98e3f4f96033">>], radio_type = <<"2.4G">> }), ets:insert(Store, #'Wifi_Stats_Config'{ - key_id = <<"682166f4-8d40-47b9-8ddc-827940cae8ef">>, + '**key_id**' = <<"682166f4-8d40-47b9-8ddc-827940cae8ef">>, '_uuid' = [<<"uuid">>,<<"682166f4-8d40-47b9-8ddc-827940cae8ef">>], radio_type = <<"5GL">> }), ets:insert(Store, #'Wifi_Stats_Config'{ - key_id = <<"21b32c56-5011-455c-9c7c-c58b9d43d583">>, + '**key_id**' = <<"21b32c56-5011-455c-9c7c-c58b9d43d583">>, '_uuid' = [<<"uuid">>,<<"21b32c56-5011-455c-9c7c-c58b9d43d583">>], radio_type = <<"5GU">> }); create_table ('AWLAN_Node',APC,Store) -> ets:insert(Store, #'AWLAN_Node'{ - key_id = utils:uuid_b(), + '**key_id**' = utils:uuid_b(), redirector_addr = proplists:get_value(tip_redirector,APC), serial_number = proplists:get_value(serial,APC), id = proplists:get_value(serial,APC), diff --git a/src/ovsdb_ap_monitor.erl b/src/ovsdb_ap_monitor.erl new file mode 100644 index 0000000..74027dd --- /dev/null +++ b/src/ovsdb_ap_monitor.erl @@ -0,0 +1,139 @@ +%%%----------------------------------------------------------------------------- +%%% @author helge +%%% @copyright (C) 2020, Arilia Wireless Inc. +%%% @doc +%%% +%%% @end +%%% Created : 17. December 2020 @ 11:05:46 +%%%----------------------------------------------------------------------------- +-module(ovsdb_ap_monitor). +-author("helge"). + +-include("../include/common.hrl"). +-include("../include/ovsdb_ap_tables.hrl"). + + + -export ([req_monitor/3,maybe_publish_data/6,publish_unpublished/1]). + + +-spec req_monitor (NameSpace :: binary(), ToMonitor :: [#{binary()=>term()}], Store :: ets:tid()) -> Result :: #{binary()=>term()}. +req_monitor (NameSpace,[{Table,Operations}|_],Store) -> + monitor (NameSpace,Table,Operations,Store); + % case Table of + % <<"Wifi_Associated_Clients">> -> + % QRes = ovsdb_dba:select_with_key(Table,[],Store), + % timer:apply_after(5000,?MODULE,publish_monitor,[self(),NameSpace,monitor_result(Table,QRes,[])]), + % #{}; + % <<"DHCP_leased_IP">> -> + % QRes = ovsdb_dba:select_with_key(Table,[],Store), + % timer:apply_after(5500,?MODULE,publish_monitor,[self(),NameSpace,monitor_result(Table,QRes,[])]), + % #{}; + % _ -> + % Ret + % end; +req_monitor (NameSpace,OPS,_) -> + ?L_EA("Monitor request for namespace '~s' with unsupported operatiosn format ~p",[NameSpace,OPS]), + #{}. + +-spec monitor (NameSpace :: binary(), Table :: binary(), Operations :: #{binary()=>term()}, Store :: ets:tid()) -> Result :: #{binary()=>term()}. +monitor (NameSpace, Table, Operations, Store) -> + Sel = maps:get(<<"select">>,Operations,#{<<"modify">>=>true}), + M = #monitors{ + namespace = NameSpace, + table = Table, + initial = maps:get(<<"initial">>,Sel,false), + insert = maps:get(<<"insert">>,Sel,false), + delete = maps:get(<<"delete">>,Sel,false), + modify = maps:get(<<"modify">>,Sel,false) + }, + ets:insert(Store, M), + case should_return_value(initial,Table,Store) of + true -> + QRes = ovsdb_dba:select_with_key(Table,[],Store), + monitor_result(Table,QRes,[]); + % R = monitor_result(Table,QRes,[]), + % io:format("MONITOR RESULT:~n~p~n",[R]), + % R; + false -> + M2 = M#monitors{published=false}, + ets:delete_object(Store,M), + ets:insert(Store, M2), + #{} + end. + +-spec should_return_value(State :: initial | insert | delete | modify, TableName :: binary(), Store :: ets:tid()) -> boolean(). +should_return_value (initial,Table,Store) -> + length(ets:match_object(Store,#monitors{table=Table, initial=true, _='_'})) =/= 0; +should_return_value (insert,Table,Store) -> + length(ets:match_object(Store,#monitors{table=Table, initial=true, _='_'})) =/= 0; +should_return_value (delete,Table,Store) -> + length(ets:match_object(Store,#monitors{table=Table, initial=true, _='_'})) =/= 0; +should_return_value (modify,Table,Store) -> + length(ets:match_object(Store,#monitors{table=Table, initial=true, _='_'})) =/= 0. + +-spec monitor_result (TableName :: binary(), NewRows :: [{Key :: binary(), #{binary()=>any()}}], OldRows :: [{Key :: binary(), #{binary()=>any()}}]) -> #{binary()=>any()}. +monitor_result (_,[],[]) -> + #{}; +monitor_result (T,NewRows,[]) -> + L = [{K,#{<<"new">>=>M}} || {K,M} <- NewRows], + #{T => maps:from_list(L)}; +monitor_result (T,NewRows,OldRows) -> + OldMap = maps:from_list(OldRows), + F = fun ({K,Map}) -> + case maps:is_key(K,OldMap) of + true -> + {K,#{<<"new">>=>Map, <<"old">>=>maps:get(K,OldMap)}}; + false -> + {K,#{<<"new">>=>Map}} + end + end, + L = [F(X) || X <- NewRows], + #{T => maps:from_list(L)}. + + + + +-spec publish_monitor (NameSpace :: binary(), Data :: #{binary()=>term()}) -> ok. +publish_monitor (NameSpace,Data) -> + RPC = #{ + <<"id">> => null, + <<"method">> => <<"update">>, + <<"params">> => [NameSpace,Data] + }, + % Json = iolist_to_binary(jiffy:encode(RPC)), + % io:format("PUBLISHING: ~s~n~s~n",[NameSpace,Json]), + ?L_IA("PUBLISHING: ~s",[NameSpace]), + ovsdb_ap:rpc_request(self(),RPC). + +-spec maybe_publish_data (NameSpace :: binary(), + Tablename :: binary(), + Operation :: insert | delete | modify, + NewRows :: [{Key :: binary(), #{binary()=>any()}}], + OldRows :: [{Key :: binary(), #{binary()=>any()}}], + Store :: ets:tid()) -> ok. +maybe_publish_data (NS,T,Op,New,Old,Store) -> + case should_return_value(Op,T,Store) of + true -> + Res = monitor_result(T,New,Old), + publish_monitor(NS,Res), + ok; + false -> + ok + end. + +-spec publish_unpublished (Store :: ets:tid()) -> ok. +publish_unpublished (Store) -> + ToPublish = ets:match_object(Store,#monitors{published=false, modify=true, _='_'}), + %ToPublish = ets:match_object(Store,#monitors{_='_'}), + %io:format ("UNPUBLISHED:~n~p~n",[ToPublish]), + F = fun (#monitors{namespace=NS, table=T}=P) -> + QRes = ovsdb_dba:select_with_key(T,[],Store), + publish_monitor(NS,monitor_result(T,QRes,[])), + P#monitors{published=true} + end, + N = [F(X) || X <- ToPublish], + [ets:delete_object(Store,X) || X <- ToPublish], + ets:insert(Store,N). + + + diff --git a/src/ovsdb_ap_rpc.erl b/src/ovsdb_ap_rpc.erl index 21649ce..df0b454 100644 --- a/src/ovsdb_ap_rpc.erl +++ b/src/ovsdb_ap_rpc.erl @@ -12,7 +12,7 @@ -include("../include/common.hrl"). -include("../include/ovsdb_ap_tables.hrl"). --export ([eval_req/4,eval_resp/4,publish_monitor/3]). +-export ([eval_req/4,eval_resp/4]). %%------------------------------------------------------------------------------ @@ -30,11 +30,12 @@ eval_req(<<"transact">>,Id,#{<<"params">>:=_P},_Store) -> {ok, make_result(Id,<<>>)}; eval_req(<<"monitor">>,Id,#{<<"params">>:=[<<"Open_vSwitch">>,NSpace|Tables]},Store) when length(Tables)==1 -> - Mon = req_monitor(NSpace,maps:to_list(hd(Tables)),Store), + Mon = ovsdb_ap_monitor:req_monitor(NSpace,maps:to_list(hd(Tables)),Store), Res = make_result(Id,Mon), % Json = iolist_to_binary(jiffy:encode(Res,[pretty])), % io:format("MONITOR REQUEST (~s):~n~s~n",[NSpace,Json]), io:format("MONITOR REQUEST (~s):~n",[NSpace]), + timer:apply_after(3000,ovsdb_ap,check_publish_monitor,[self()]), {ok, Res}; eval_req(<<"monitor">>,Id,P,_) -> ?L_EA("unrecognized monitor request: ~p",[P]), @@ -79,7 +80,7 @@ eval_resp (Id, _Data, Queue, _Store) -> %%------------------------------------------------------------------------------ -%% transaction and monitor handling +%% transaction handling -spec run_transactions (Id :: binary(), Transactions :: [#{binary() => term()}], Store :: ets:tid(), Acc :: [#{binary()=>any()}]) -> [#{binary()=>any()}]. run_transactions (_,[],_,Acc) -> @@ -90,89 +91,6 @@ run_transactions (Id,[Trans|More],Store,Acc) when is_map(Trans) -> Qr = table_query(Trans,Store), run_transactions (Id,More,Store,[Qr|Acc]). --spec req_monitor (NameSpace :: binary(), ToMonitor :: [#{binary()=>term()}], Store :: ets:tid()) -> Result :: #{binary()=>term()}. -req_monitor (NameSpace,[{Table,Operations}|_],Store) -> - {M,Ret} = monitor (NameSpace,Table,Operations,Store), - case Table of - <<"Wifi_Associated_Clients">> -> - Res = monitor_result(modify,M,Store), - timer:apply_after(5000,?MODULE,publish_monitor,[self(),NameSpace,Res]), - #{}; - <<"DHCP_leased_IP">> -> - Res = monitor_result(modify,M,Store), - timer:apply_after(5500,?MODULE,publish_monitor,[self(),NameSpace,Res]), - #{}; - _ -> - Ret - end; -req_monitor (NameSpace,OPS,_) -> - ?L_EA("Monitor request for namespace '~s' with unsupported operatiosn format ~p",[NameSpace,OPS]), - #{}. - --spec monitor (NameSpace :: binary(), Table :: binary(), Operations :: #{binary()=>term()}, Store :: ets:tid()) -> Result :: #{binary()=>term()}. -monitor (NameSpace, Table, Operations, Store) -> - Sel = maps:get(<<"select">>,Operations,#{<<"modify">>=>true}), - M = #monitors{ - namespace = NameSpace, - table = Table, - initial = maps:get(<<"initial">>,Sel,false), - insert = maps:get(<<"insert">>,Sel,false), - delete = maps:get(<<"delete">>,Sel,false), - modify = maps:get(<<"modify">>,Sel,false) - }, - ets:insert(Store, M), - {M,monitor_result(initial,M,Store)}. - --spec monitor_result (State :: initial | insert | delete | modify, Monitor :: #monitors{}, Store :: ets:tid()) -> Result :: #{binary()=>term()}. -monitor_result (initial,#monitors{table=T, initial=true}, Store) -> - monitor_table_query(T, new, Store); -monitor_result (insert,#monitors{table=T, insert=true}, Store) -> - monitor_table_query(T, new, Store); -monitor_result (delete,#monitors{table=T, delete=true}, Store) -> - monitor_table_query(T, old, Store); -monitor_result (modify,#monitors{table=T, modify=true}, Store) -> - monitor_table_query(T, both, Store); -monitor_result (_,_,_) -> - #{}. - --spec monitor_table_query (Table :: binary(), Which :: new | old | both,Store :: ets:tid()) -> Result :: #{binary()=>term()}. -monitor_table_query (Table, Which, Store) -> - Fields = rec_fields(Table), - F = fun(X) -> - M = maps:from_list(lists:zip(Fields,X)), - {KeyId,M2} = case Table of - <<"Wifi_Associated_Clients">> -> - {KeyId2,Mt} = maps:take(<<"key_id">>,M), - {KeyId2,Mt#{<<"key_id">>=><<"">>}}; - _ -> - maps:take(<<"key_id">>,M) - end, - case Which of - new -> {KeyId,#{<<"new">>=>M2}}; - old -> {KeyId,#{<<"old">>=>M2}}; - both -> {KeyId,#{<<"new">>=>M2}} %, <<"old">>=>#{}}} - end - end, - case ets:select(Store,create_match_spec(Table,[])) of - [] -> - #{}; - Res -> - #{Table=>maps:from_list([F(X) || X <- Res])} - end. - - --spec publish_monitor (AP :: pid(), NameSpace :: binary(), Data :: #{binary()=>term()}) -> ok. -publish_monitor (AP,NameSpace,Data) -> - RPC = #{ - <<"id">> => null, - <<"method">> => <<"update">>, - <<"params">> => [NameSpace,Data] - }, - Json = iolist_to_binary(jiffy:encode(RPC)), - io:format("PUBLISHING: ~s~n~s~n",[NameSpace,Json]), - ?L_IA("PUBLISHING: ~s",[NameSpace]), - ovsdb_ap:rpc_request(AP,RPC). - %%------------------------------------------------------------------------------ %% handling OVSDB tables @@ -180,80 +98,29 @@ publish_monitor (AP,NameSpace,Data) -> -spec table_query (P :: map(), Store :: ets:tid()) -> map(). table_query (#{<<"table">>:=T, <<"op">>:= <<"select">>, <<"columns">>:=C, <<"where">>:=W},S) -> - Res = ets:select(S,create_match_spec(T,W)), - #{ <<"rows">> => make_res_rows(T,Res,C,[])}; + Res = ovsdb_dba:select(T,W,S), + #{ <<"rows">> => make_res_rows(Res,C,[])}; table_query (#{<<"table">>:=T, <<"op">>:= <<"select">>, <<"where">>:=W},S) -> - Res = ets:select(S,create_match_spec(T,W)), - [_|Cols] = rec_fields(T), - #{ <<"rows">> => make_res_rows(T,Res,Cols,[])}; + Res = ovsdb_dba:select(T,W,S), + #{ <<"rows">> => make_res_rows(Res,all,[])}; table_query (#{<<"table">>:=T, <<"op">>:= <<"delete">>, <<"where">>:=W},S) -> - M = create_match_spec(T,W), - D = ets:select_delete(S,[setelement(3,hd(M),[true])]), + D = ovsdb_dba:delete(T,W,S), #{ <<"count">> => D}; table_query (#{<<"table">>:=T, <<"op">>:= <<"insert">>, <<"row">>:=R},S) -> - Fields = rec_fields(T), - UUID = utils:uuid_b(), - Default = maps:from_list(lists:zip(Fields,tl(tuple_to_list(default_record(T))))), - Rwi = maps:merge(Default,R#{<<"key_id">>=>utils:uuid_b(),<<"_uuid">>=>[<<"uuid">>,UUID]}), - Rec = list_to_tuple([binary_to_atom(T)|[maps:get(X,Rwi,<<"###WILL_CRASH###">>) || X<-Fields]]), - % case T of - % <<"Wifi_VIF_Config">> -> - % io:format("INSERT VIF:~nInput=~p~nResult=~p~n",[R,Rec]); - % _ -> - % ok - % end, - ets:insert(S,Rec), - #{uuid=>[uuid,UUID]}; + UUIDs = ovsdb_dba:insert(T,R,S), + #{uuid=>[uuid|UUIDs]}; table_query (#{<<"table">>:=T, <<"op">>:= <<"mutate">>, <<"mutations">>:=Mut, <<"where">>:=W},S) -> - D = mutate_table(T,Mut,W,S), + D = ovsdb_dba:mutate_table(T,Mut,W,S), #{<<"count">> => D}; table_query (#{<<"table">>:=T, <<"op">>:= <<"update">>, <<"row">>:=R, <<"where">>:=W},S) -> - M = create_match_spec(T,W), - Res = ets:select(S,M), - D = ets:select_delete(S,[setelement(3,hd(M),[true])]), - Upd = update_records(T,R,Res,[]), - ets:insert(S,Upd), + D = ovsdb_dba:update(T,R,W,S), check_update_actions(R), #{<<"count">> => D}. -%--------mutate_table/4------------------hndle mutations to fields in a table row (or rows) - --spec mutate_table (Table :: binary(), Mutations :: [[any()]], Where :: [any()], Store :: ets:tid()) -> RowsAffected :: integer(). -mutate_table (Table,Mut,Where,Store) -> - mutate_table_a (Table,Mut,Where,Store,0). - --spec mutate_table_a (Table :: binary(), Mutations :: [[any()]], Where :: [any()], Store :: ets:tid(), Acc :: integer()) -> RowsAffected :: integer(). -mutate_table_a (_,[],_,_,A) -> - A; -mutate_table_a (Table,[Mut|Tail],Where,Store,A) -> - MSpec = create_match_spec(Table,Where), - [ToMutate] = ets:select(Store,MSpec), - Mutated = apply_mutations(Mut, Table, ToMutate), - %io:format("MUTATION: MUT=~p~nToMutate=~p~n,Mutated=~p~n",[Mut,ToMutate,Mutated]), - D = ets:select_delete(Store,[setelement(3,hd(MSpec),[true])]), - ets:insert(Store,list_to_tuple([binary_to_atom(Table)|Mutated])), - mutate_table_a (Table,Tail,Where,Store,A+D). - --spec apply_mutations (Mutations :: [], Table :: binary(), ToMutate :: tuple()) -> MutatedRecord :: tuple(). -apply_mutations ([Field,<<"insert">>,What],Table,Record) -> - RecMap = lists:zip(rec_fields(Table),Record), - case proplists:get_value(Field,RecMap) of - undefined -> - Record; - [T,L] -> - F = fun (X,_) when X=:=Field -> [T,[What|L]]; (_,V) -> V end, - [F(Key,Val) || {Key,Val}<-RecMap] - end. - - - - - - %--------check_update_actions/1----------special handling for some updates that need to trigger actions @@ -267,40 +134,14 @@ check_update_actions (_) -> ok. %--------make_res_rows/3-----------------formats query results into proper rows map --spec make_res_rows (Record :: binary(), Res :: [[{binary(),any()}]], Cols :: [binary()], Acc :: [#{}]) -> [#{}]. -make_res_rows (R,Res,[],Acc) -> - make_res_rows (R,Res,rec_fields(R),Acc); -make_res_rows (_,[],_,Acc) -> +-spec make_res_rows (Res :: [#{binary()=>any()}], Cols :: all | [binary()], Acc :: [#{binary()=>any()}]) -> [#{binary()=>any()}]. +make_res_rows (Res,all,_) -> + Res; +make_res_rows ([],_,Acc) -> lists:reverse(Acc); -make_res_rows (R,[H|T],C,Acc) -> - M = maps:from_list([{F,V}|| {F,V}<-lists:zip(rec_fields(R),H), lists:member(F,C)]), - make_res_rows(R,T,C,[M|Acc]). - -%--------create_match_spec/3-------------creates proper match specification from RPC command for ETS search --spec create_match_spec (TableName :: binary(), Where :: []) -> [{tuple(),list(),list()}]. -create_match_spec (R,W) -> - Op = #{<<"==">>=>'==', <<"!=">>=>'/=', <<"<=">>=>'<=', <<"<">>=>'<', <<">=">>=>'>=', <<">">>=>'>'}, - Fields = rec_fields(R), - MP = [binary_to_atom(list_to_binary([$$,integer_to_list(X)])) || X<-lists:seq(1,length(Fields))], - C = [{maps:get(O,Op,'=='),field_idx(A1,Fields,1),field_idx(A2,Fields,1)}|| [A1,O,A2] <- W], - [{list_to_tuple([binary_to_atom(R)|MP]),C,['$$']}]. - -field_idx (F,[],_) -> F; -field_idx (F,[F|_],N) -> binary_to_atom(list_to_binary([$$,integer_to_list(N)])); -field_idx (F,[_|T],N) -> field_idx(F,T,N+1). - -%--------update_records/4-----------------create an updated record to be inserted into ETS from RCP call --spec update_records (TableName :: binary(), NewValues :: #{binary():=any()}, Records :: [[any()]], Acc :: [[any()]]) -> [tuple()]. -update_records (_,_,[],Acc) -> - Acc; -update_records (T,V,[R|Rest],Acc) -> - Fields = rec_fields(T), - Default = maps:from_list(lists:zip(Fields,tl(tuple_to_list(default_record(T))))), - Cand = maps:from_list(lists:zip(Fields,R)), - DefCand = maps:merge(Default,Cand), - ResMap = maps:merge(DefCand,V), - Rec = list_to_tuple([binary_to_atom(T)|[maps:get(X,ResMap,<<"###WILL_CRASH###">>) || X<-Fields]]), - update_records(T,V,Rest,[Rec|Acc]). +make_res_rows ([H|T],C,Acc) -> + M = maps:from_list([{F,V}|| {F,V}<-maps:to_list(H), lists:member(F,C)]), + make_res_rows(T,C,[M|Acc]). %--------read_schema/1-------------------reads the schema from disk for a particular device type -spec read_schema(Store :: ets:tid()) -> binary(). @@ -315,70 +156,3 @@ read_schema (Store) -> ?L_EA("Cannot read schmea file for model: ~s",[Model]), <<>> end. - - -%%------------------------------------------------------------------------------ -%% record convertion helpers --spec rec_fields (RecordName :: binary()) -> Fieldnames :: [binary()]. -rec_fields (<<"Wifi_Inet_Config">>) -> - [atom_to_binary(X)||X<-record_info(fields,'Wifi_Inet_Config')]; -rec_fields (<<"Wifi_Radio_Config">>) -> - [atom_to_binary(X)||X<-record_info(fields,'Wifi_Radio_Config')]; -rec_fields (<<"Wifi_VIF_Config">>) -> - [atom_to_binary(X)||X<-record_info(fields,'Wifi_VIF_Config')]; -rec_fields (<<"Wifi_VIF_State">>) -> - [atom_to_binary(X)||X<-record_info(fields,'Wifi_VIF_State')]; -rec_fields (<<"Wifi_Associated_Clients">>) -> - [atom_to_binary(X)||X<-record_info(fields,'Wifi_Associated_Clients')]; -rec_fields (<<"Command_State">>) -> - [atom_to_binary(X)||X<-record_info(fields,'Command_State')]; -rec_fields (<<"DHCP_leased_IP">>) -> - [atom_to_binary(X)||X<-record_info(fields,'DHCP_leased_IP')]; -rec_fields (<<"Wifi_RRM_Config">>) -> - [atom_to_binary(X)||X<-record_info(fields,'Wifi_RRM_Config')]; -rec_fields (<<"Hotspot20_Icon_Config">>) -> - [atom_to_binary(X)||X<-record_info(fields,'Hotspot20_Icon_Config')]; -rec_fields (<<"Hotspot20_OSU_Providers">>) -> - [atom_to_binary(X)||X<-record_info(fields,'Hotspot20_OSU_Providers')]; -rec_fields (<<"Hotspot20_Config">>) -> - [atom_to_binary(X)||X<-record_info(fields,'Hotspot20_Config')]; -rec_fields (<<"Wifi_Stats_Config">>) -> - [atom_to_binary(X)||X<-record_info(fields,'Wifi_Stats_Config')]; -rec_fields (<<"Wifi_Radio_State">>) -> - [atom_to_binary(X)||X<-record_info(fields,'Wifi_Radio_State')]; -rec_fields (<<"Wifi_Inet_State">>) -> - [atom_to_binary(X)||X<-record_info(fields,'Wifi_Inet_State')]; -rec_fields (<<"AWLAN_Node">>) -> - [atom_to_binary(X)||X<-record_info(fields,'AWLAN_Node')]. - --spec default_record(RecordName::binary) -> Record :: tuple(). -default_record (<<"Wifi_Inet_Config">>) -> - #'Wifi_Inet_Config'{}; -default_record (<<"Wifi_Radio_Config">>) -> - #'Wifi_Radio_Config'{}; -default_record (<<"Wifi_VIF_Config">>) -> - #'Wifi_VIF_Config'{}; -default_record (<<"Wifi_VIF_State">>) -> - #'Wifi_VIF_State'{}; -default_record (<<"Wifi_Associated_Clients">>) -> - #'Wifi_Associated_Clients'{}; -default_record (<<"Command_State">>) -> - #'Command_State'{}; -default_record (<<"DHCP_leased_IP">>) -> - #'DHCP_leased_IP'{}; -default_record (<<"Wifi_RRM_Config">>) -> - #'Wifi_RRM_Config'{}; -default_record (<<"Hotspot20_Icon_Config">>) -> - #'Hotspot20_Icon_Config'{}; -default_record (<<"Hotspot20_OSU_Providers">>) -> - #'Hotspot20_OSU_Providers'{}; -default_record (<<"Hotspot20_Config">>) -> - #'Hotspot20_Config'{}; -default_record (<<"Wifi_Stats_Config">>) -> - #'Wifi_Stats_Config'{}; -default_record (<<"Wifi_Radio_State">>) -> - #'Wifi_Radio_State'{}; -default_record (<<"Wifi_Inet_State">>) -> - #'Wifi_Inet_State'{}; -default_record (<<"AWLAN_Node">>) -> - #'AWLAN_Node'{}. diff --git a/src/ovsdb_dba.erl b/src/ovsdb_dba.erl new file mode 100644 index 0000000..5d31e98 --- /dev/null +++ b/src/ovsdb_dba.erl @@ -0,0 +1,257 @@ +%%%----------------------------------------------------------------------------- +%%% @author helge +%%% @copyright (C) 2020, Arilia Wireless Inc. +%%% @doc +%%% +%%% @end +%%% Created : 17. December 2020 @ 11:21:29 +%%%----------------------------------------------------------------------------- +-module(ovsdb_dba). +-author("helge"). + +-include("../include/common.hrl"). +-include("../include/ovsdb_ap_tables.hrl"). + +-export ([select/3,select_with_key/3,delete/3,delete_ret/3,insert/3,update/4,mutate_table/4]). + + +%%----------------------------------------------------------------------------- +%% common types +%%----------------------------------------------------------------------------- + +-type selspec() :: [binary(),...]. +-type matchspec() :: [{tuple(),list(),list()},...]. +-type db_record() :: #{binary()=>term()}. +-type db_records() :: [db_record()]. + +-export_type ([selspec/0]). + + +%%----------------------------------------------------------------------------- +%% API: table / database access +%%----------------------------------------------------------------------------- + + +-spec select (TableName :: binary(), Where :: selspec(), Store :: ets:tid()) -> Rows :: db_records(). +select (T,W,S) -> + MSpec = create_match_spec(T,W), + R = ets:select(S,MSpec), + Fields = tl(rec_fields(T)), % drop the first field since that is our internal key index + [ maps:from_list(lists:zip(Fields,tl(X))) || X <- R ]. + +-spec select_with_key (TableName :: binary(), Where :: selspec(), Store :: ets:tid()) -> [{Key :: binary(), db_record()}]. +select_with_key(T,W,S) -> + MSpec = create_match_spec(T,W), + R = ets:select(S,MSpec), + [ maps:take(<<"**key_id**">>,X) || X <- [ maps:from_list(lists:zip(rec_fields(T),X)) || X <- R ]]. + + + +-spec delete (TableName :: binary(), Where :: selspec(), Store :: ets:tid()) -> NumDeleted :: integer(). +delete (T,W,S) -> + MSpec = create_match_spec(T,W), + DelSpec = [ setelement(3,X,[true]) || X <- MSpec ], + ets:select_delete(S,DelSpec). + +-spec delete_ret (TableName :: binary(), Where :: selspec(), Store :: ets:tid()) -> DeletedRows :: [tuple()]. +delete_ret (T,W,S) -> + MSpec = create_match_spec(T,W), + DelSpec = [ setelement(3,X,[true]) || X <- MSpec ], + R = ets:select(S,MSpec), + _ = ets:select_delete(S,DelSpec), + Fields = tl(rec_fields(T)), % drop the first field since that is our internal key index + [ maps:from_list(lists:zip(Fields,tl(X))) || X <- R ]. + +-spec insert (TableName :: binary(), Records :: db_record() | db_records(), Store :: ets:tid()) -> RowUUIDs :: [binary()]. +insert (T,R,S) when is_map(R) -> + insert (T,[R],S); +insert (T,R,S) -> + {UUIDs,ModRec} = lists:unzip([ make_row_uuid(T,X) || X <- R ]), + ets:insert(S,ModRec), + UUIDs. + +-spec update (TableName :: binary(), Record :: db_record(), Where :: selspec(), Store :: ets:tid()) -> RowUUIDs :: [binary()]. +update (T,R,W,S) -> + MSpec = create_match_spec(T,W), + DelSpec = [ setelement(3,X,[true]) || X <- MSpec ], + Res = ets:select(S,MSpec), + _ = ets:select_delete(S,DelSpec), + Old = [ maps:from_list(lists:zip(rec_fields(T),X)) || X <- Res ], + {UUIDs,ModRec} = lists:unzip([ modify_row_record(T,X,R) || X <- Old ]), + ets:insert(S,ModRec), + UUIDs. + +-spec mutate_table (TableName :: binary(), Mutations :: [[any()]], Where :: selspec(), Store :: ets:tid()) -> RowsAffected :: integer(). +mutate_table (Table,Mut,Where,Store) -> + MSpec = create_match_spec(Table,Where), + DelSpec = [ setelement(3,X,[true]) || X <- MSpec ], + Res = ets:select(Store,MSpec), + _ = ets:select_delete(Store,DelSpec), + ToMutate = [ maps:from_list(lists:zip(rec_fields(Table),X)) || X <- Res ], + Mutated = [ mutate_table_row(Table,Mut,X) || X <- ToMutate ], + ets:insert(Store,Mutated), + length(Mutated). + +%%----------------------------------------------------------------------------- +%% internal DB access functions +%%----------------------------------------------------------------------------- + + +-spec make_row_uuid (TableName :: binary(), Record :: db_record()) -> {UUID :: binary(), ModRecord :: tuple()}. +make_row_uuid (T,R) -> + Fields = rec_fields(T), + D_map = maps:from_list(lists:zip(Fields,default_values(T))), + Ins_map = maps:merge(D_map,R), + UUID = utils:uuid_b(), + F = fun ({<<"_uuid">>,[<<"uuid">>,_]}) -> + {<<"_uuid">>,[<<"uuid">>,UUID]}; + ({<<"**key_id**">>,_}) -> + {<<"**key_id**">>,UUID}; + (X) -> + X + end, + URec = maps:from_list([ F(X) || X <- maps:to_list(Ins_map) ]), + { UUID, map_to_record(T,URec)}. + +-spec modify_row_record (TableName :: binary(), OldRecord :: db_record(), NewRecord :: db_record()) -> {UUID :: binary(), ModRecord :: tuple()}. +modify_row_record(T,O,N) -> + Fields = rec_fields(T), + D_map = maps:from_list(lists:zip(Fields,default_values(T))), + Ins_map = maps:merge(maps:merge(D_map,O),N), + #{<<"**key_id**">>:=KeyID } = O, + UUID = case N of + #{<<"_uuid">>:=[<<"uuid">>,V]} -> + V; + #{<<"_uuid">>:=V} when is_binary(V) -> + V; + _ -> + utils:uuid_b() + end, + F = fun ({<<"_uuid">>,[<<"uuid">>,_]}) -> + {<<"_uuid">>,[<<"uuid">>,UUID]}; + ({<<"**key_id**">>,_}) -> + {<<"**key_id**">>,KeyID}; + (X) -> + X + end, + URec = maps:from_list([ F(X) || X <- maps:to_list(Ins_map) ]), + { KeyID, map_to_record(T,URec)}. + +-spec mutate_table_row (TableName :: binary(), Mutations :: [[any()]], Where :: [any()]) -> MutatedRecord :: tuple(). +mutate_table_row (T,[],MutRow) -> + map_to_record(T,MutRow); +mutate_table_row (Table,[Mut|Tail],MutRow) -> + Mutated = apply_mutation(Mut,MutRow), + mutate_table_row(Table,Tail,Mutated). + +-spec apply_mutation (Mutations :: [], ToMutate :: #{binary()=>any()}) -> Mutated :: #{binary()=>any()}. +apply_mutation ([Field,<<"insert">>,What],RowMap) when is_map_key(Field,RowMap) -> + #{Field:=V} = RowMap, + NewValue = case V of + [K,Up] when is_list(V) -> + [K,lists:reverse([What | lists:reverse(Up)])]; + _ -> + V + end, + RowMap#{Field=>NewValue}; +apply_mutation ([F,Op,_],RowMap) -> + ?L_EA("table mutation with operation '~s' on Fields '~s' not supported!",[Op,F]), + RowMap. + + + + +%%----------------------------------------------------------------------------- +%% internal functions +%%----------------------------------------------------------------------------- + +-spec create_match_spec (TableName :: binary(), Where :: selspec()) -> MatchSpec :: matchspec(). +create_match_spec (R,W) -> + Op = #{<<"==">>=>'==', <<"!=">>=>'/=', <<"<=">>=>'<=', <<"<">>=>'<', <<">=">>=>'>=', <<">">>=>'>'}, + Fields = rec_fields(R), + MP = [binary_to_atom(list_to_binary([$$,integer_to_list(X)])) || X<-lists:seq(1,length(Fields))], + C = [{maps:get(O,Op,'=='),field_idx(A1,Fields,1),field_idx(A2,Fields,1)}|| [A1,O,A2] <- W], + [{list_to_tuple([binary_to_atom(R)|MP]),C,['$$']}]. + +field_idx (F,[],_) -> F; +field_idx (F,[F|_],N) -> binary_to_atom(list_to_binary([$$,integer_to_list(N)])); +field_idx (F,[_|T],N) -> field_idx(F,T,N+1). + +-spec map_to_record (TableName :: binary(), Map :: #{binary()=>any()}) -> Record :: tuple(). +map_to_record (T,M) -> + Fields = rec_fields(T), + List = [ maps:get(X,M,<<"###CRASH###">>) || X <- Fields], + list_to_tuple([binary_to_atom(T) | List]). + + +%%------------------------------------------------------------------------------ +%% record convertion helpers +-spec rec_fields (RecordName :: binary()) -> Fieldnames :: [binary()]. +rec_fields (<<"Wifi_Inet_Config">>) -> + [atom_to_binary(X)||X<-record_info(fields,'Wifi_Inet_Config')]; +rec_fields (<<"Wifi_Radio_Config">>) -> + [atom_to_binary(X)||X<-record_info(fields,'Wifi_Radio_Config')]; +rec_fields (<<"Wifi_VIF_Config">>) -> + [atom_to_binary(X)||X<-record_info(fields,'Wifi_VIF_Config')]; +rec_fields (<<"Wifi_VIF_State">>) -> + [atom_to_binary(X)||X<-record_info(fields,'Wifi_VIF_State')]; +rec_fields (<<"Wifi_Associated_Clients">>) -> + [atom_to_binary(X)||X<-record_info(fields,'Wifi_Associated_Clients')]; +rec_fields (<<"Command_State">>) -> + [atom_to_binary(X)||X<-record_info(fields,'Command_State')]; +rec_fields (<<"DHCP_leased_IP">>) -> + [atom_to_binary(X)||X<-record_info(fields,'DHCP_leased_IP')]; +rec_fields (<<"Wifi_RRM_Config">>) -> + [atom_to_binary(X)||X<-record_info(fields,'Wifi_RRM_Config')]; +rec_fields (<<"Hotspot20_Icon_Config">>) -> + [atom_to_binary(X)||X<-record_info(fields,'Hotspot20_Icon_Config')]; +rec_fields (<<"Hotspot20_OSU_Providers">>) -> + [atom_to_binary(X)||X<-record_info(fields,'Hotspot20_OSU_Providers')]; +rec_fields (<<"Hotspot20_Config">>) -> + [atom_to_binary(X)||X<-record_info(fields,'Hotspot20_Config')]; +rec_fields (<<"Wifi_Stats_Config">>) -> + [atom_to_binary(X)||X<-record_info(fields,'Wifi_Stats_Config')]; +rec_fields (<<"Wifi_Radio_State">>) -> + [atom_to_binary(X)||X<-record_info(fields,'Wifi_Radio_State')]; +rec_fields (<<"Wifi_Inet_State">>) -> + [atom_to_binary(X)||X<-record_info(fields,'Wifi_Inet_State')]; +rec_fields (<<"AWLAN_Node">>) -> + [atom_to_binary(X)||X<-record_info(fields,'AWLAN_Node')]. + +-spec default_record(RecordName::binary()) -> Record :: tuple(). +default_record (<<"Wifi_Inet_Config">>) -> + #'Wifi_Inet_Config'{}; +default_record (<<"Wifi_Radio_Config">>) -> + #'Wifi_Radio_Config'{}; +default_record (<<"Wifi_VIF_Config">>) -> + #'Wifi_VIF_Config'{}; +default_record (<<"Wifi_VIF_State">>) -> + #'Wifi_VIF_State'{}; +default_record (<<"Wifi_Associated_Clients">>) -> + #'Wifi_Associated_Clients'{}; +default_record (<<"Command_State">>) -> + #'Command_State'{}; +default_record (<<"DHCP_leased_IP">>) -> + #'DHCP_leased_IP'{}; +default_record (<<"Wifi_RRM_Config">>) -> + #'Wifi_RRM_Config'{}; +default_record (<<"Hotspot20_Icon_Config">>) -> + #'Hotspot20_Icon_Config'{}; +default_record (<<"Hotspot20_OSU_Providers">>) -> + #'Hotspot20_OSU_Providers'{}; +default_record (<<"Hotspot20_Config">>) -> + #'Hotspot20_Config'{}; +default_record (<<"Wifi_Stats_Config">>) -> + #'Wifi_Stats_Config'{}; +default_record (<<"Wifi_Radio_State">>) -> + #'Wifi_Radio_State'{}; +default_record (<<"Wifi_Inet_State">>) -> + #'Wifi_Inet_State'{}; +default_record (<<"AWLAN_Node">>) -> + #'AWLAN_Node'{}. + +-spec default_values(RecordName::binary()) -> Values :: [term()]. +default_values(T) -> + [_|R] = tuple_to_list(default_record(T)), + R. + diff --git a/src/user_default.erl b/src/user_default.erl index 888c291..ba84127 100644 --- a/src/user_default.erl +++ b/src/user_default.erl @@ -329,6 +329,17 @@ c1()-> nodes = ['simnode1@debfarm1-node-c.arilia.com'] }, simengine:create(Simulation). +t1_key_h() -> + _ = import_ca("sim1","mypassword","tip2-cakey.pem","tip2-cacert.pem"), + Simulation = #simulation{ name = <<"sim1">>, + ca = <<"sim1">>, + num_devices = 10, + opensync_server_port = 6643, + opensync_server_name = <<"10.20.0.118">>, + nodes = ['simnode1@hypatia.syramo.com'] }, + simengine:create(Simulation). + + r1(X)-> w(X), _ = push_simulation("sim1"),