diff --git a/.gitignore b/.gitignore index 4096281b6b9d331d10100eec82e21285768ea479..e03263b62cde25275cc7c4c303b025a187aeb41e 100644 --- a/.gitignore +++ b/.gitignore @@ -20,7 +20,7 @@ _build *.crashdump # Generated code -priv/ +./priv/ # Excessive for iOS models apps/roster/priv/macbert/Model/PublishService.swift diff --git a/apps/nynja_oam/priv/extensions/db_install_fallback.sh b/apps/nynja_oam/priv/extensions/db_install_fallback.sh new file mode 100644 index 0000000000000000000000000000000000000000..9d3e75c9dc7ac077df13e8af83a652b40a3f4439 --- /dev/null +++ b/apps/nynja_oam/priv/extensions/db_install_fallback.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +case $1 in + help) + echo "bin/server db_install_fallback SNAPSHOT_FILE" + ;; + *) + ;; +esac + +code="noam_utils:install_fallback(\"$1\"),halt()" + +exec "$BINDIR/erlexec" $FOREGROUNDOPTIONS \ + -boot "$ROOTDIR/bin/start_clean" \ + -config "$RELX_CONFIG_PATH" \ + -boot_var ERTS_LIB_DIR "$ERTS_LIB_DIR" \ + -args_file "$VMARGS_PATH" \ + -eval "$code" diff --git a/apps/nynja_oam/priv/extensions/db_restore.sh b/apps/nynja_oam/priv/extensions/db_restore.sh new file mode 100644 index 0000000000000000000000000000000000000000..63c6628426a9480def9dc95470dfca7ab9e86f92 --- /dev/null +++ b/apps/nynja_oam/priv/extensions/db_restore.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +case $1 in + help) + echo "bin/server db_restore SNAPSHOT_FILE" + ;; + *) + ;; +esac + +code="noam_utils:restore_snapshot(\"$1\"),halt()" + +exec "$BINDIR/erlexec" $FOREGROUNDOPTIONS \ + -boot "$ROOTDIR/bin/start_clean" \ + -config "$RELX_CONFIG_PATH" \ + -boot_var ERTS_LIB_DIR "$ERTS_LIB_DIR" \ + -args_file "$VMARGS_PATH" \ + -eval "$code" diff --git a/apps/nynja_oam/priv/extensions/db_snapshot.sh b/apps/nynja_oam/priv/extensions/db_snapshot.sh new file mode 100644 index 0000000000000000000000000000000000000000..5a7ec55a2b58fb9b9a01a19d8b9775f04a69241c --- /dev/null +++ b/apps/nynja_oam/priv/extensions/db_snapshot.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +case $1 in + help) + echo "bin/server db_snapshot SNAPSHOT_FILE" + ;; + *) + ;; +esac + +code="noam_utils:db_snapshot(\"$1\"),halt()" + +exec "$BINDIR/erlexec" $FOREGROUNDOPTIONS \ + -boot "$ROOTDIR/bin/start_clean" \ + -config "$RELX_CONFIG_PATH" \ + -boot_var ERTS_LIB_DIR "$ERTS_LIB_DIR" \ + -args_file "$VMARGS_PATH" \ + -eval "$code" diff --git a/apps/nynja_oam/src/noam_utils.erl b/apps/nynja_oam/src/noam_utils.erl new file mode 100644 index 0000000000000000000000000000000000000000..36ebeaa6707e33b980085e5a9c92044ef3c0428e --- /dev/null +++ b/apps/nynja_oam/src/noam_utils.erl @@ -0,0 +1,115 @@ +-module(noam_utils). + +-export([ db_snapshot/1 + , restore_snapshot/1 + , install_fallback/1 ]). + +-define(LOG(Fmt, Args), io:fwrite(Fmt ++ "~n", Args)). + +db_snapshot(File) -> + ?LOG("File = ~p", [File]), + ok = ensure_mnesia_loaded_and_started(), + SchemaDir = mnesia:system_info(directory), + ?LOG("SchemaDir = ~p", [SchemaDir]), + ParentDir = filename:dirname(SchemaDir), + Base = filename:basename(SchemaDir), + dumped = mnesia:dump_log(), + ?LOG("Transaction log dumped", []), + application:stop(mnesia), + ?LOG("Mnesia stopped", []), + Cmd = [ "(cd ", ParentDir + , " && " + , "tar czf ", File, " ", Base + , ")" ], + ?LOG("Cmd = ~s", [Cmd]), + Res = os:cmd(Cmd), + ?LOG("Tar result = ~s", [Res]), + ok. + +restore_snapshot(File) -> + ?LOG("File = ~p", [File]), + no = mnesia:system_info(is_running), + ?LOG("Verified mnesia isn't running", []), + ok = ensure_app_loaded(mnesia), + SchemaDir = mnesia:system_info(directory), + ParentDir = filename:dirname(SchemaDir), + Base = filename:basename(SchemaDir), + ?LOG("Schema basename=~p", [Base]), + ok = verify_snapshot_base(File, SchemaDir), + ?LOG("Tar file seems to match schema dir", []), + Cmd = [ "(cd ", ParentDir + , " && " + , "rm -rf ", Base + , " && " + , "echo \"ls: `ls -l`\"" + , " && " + , "tar xzf ", File + , ")" ], + ?LOG("Cmd = ~s", [Cmd]), + Res = os:cmd(Cmd), + ?LOG("Res = ~s", [Res]), + ok. + +install_fallback(File) -> + ?LOG("File = ~p", [File]), + ok = ensure_mnesia_loaded_and_started(), + case mnesia:install_fallback(File) of + ok -> + ?LOG("Fallback installed, restarting mnesia ...", []), + application:stop(mnesia), + ensure_mnesia_loaded_and_started(), + init:stop(); + Error -> + ?LOG("ERROR: ~p", [Error]), + halt(1) + end. + +ensure_mnesia_loaded_and_started() -> + ok = application:ensure_started(mnesia), + ?LOG("mnesia started", []), + mnesia:info(), + Tabs = mnesia:system_info(tables) -- [schema], + ok = wait_for_tables(Tabs), + ?LOG("Tables loaded", []), + ok. + +wait_for_tables(Tabs) -> + wait_for_tables(Tabs, 30000). + +wait_for_tables(Tabs, Timeout) -> + case mnesia:wait_for_tables(Tabs, Timeout) of + {timeout, Remain} -> + ?LOG("wait_for_tables timeout, Remain = ~p", [Remain]), + ?LOG("Tabs loaded: ~p", [Tabs -- Remain]), + wait_for_tables(Remain, Timeout); + ok -> + ok + end. + +ensure_app_loaded(App) -> + case application:load(App) of + ok -> + ok; + {error,{already_loaded,_}} -> + ok; + Other -> + Other + end. + +verify_snapshot_base(TarF, SchemaDir) -> + Parent = filename:dirname(SchemaDir), + Base = filename:basename(SchemaDir), + Cmd = [ "(cd ", Parent + , " && " + , "tar tzf ", TarF, " )"], + ?LOG("Cmd = ~p", [Cmd]), + Res = os:cmd(Cmd), + FirstLine = hd(string:tokens(Res, [$\n])), + ?LOG("Tar tzf Res (line 1) = ~p", [FirstLine]), + case (Base ++ "/") == FirstLine of + true -> + ok; + false -> + ?LOG("ERROR: ~p doesn't match Mnesia base (~p)", [FirstLine, Base]), + {error, invalid_tar} + end. diff --git a/apps/nynja_oam/src/nynja_oam.app.src b/apps/nynja_oam/src/nynja_oam.app.src new file mode 100644 index 0000000000000000000000000000000000000000..91f39971d92e7731075d0aaaf1aef6343516b8a9 --- /dev/null +++ b/apps/nynja_oam/src/nynja_oam.app.src @@ -0,0 +1,9 @@ +%% -*- mode:erlang; erlang-indent-level:4; indent-tabs-mode:nil -*- + +{application, nynja_oam, + [{description, "Operation & Maintenance functions for Nynja server"}, + {vsn, git}, + {registered, []}, + {applications, [kernel, stdlib, mnesia, service, roster]}, + {env, []} + ]}. diff --git a/rebar.config b/rebar.config index 07073f3d1f62e02df55a0a5939ade7971de72f59..080cd58e7fdfddfdb04e2c35dc11095d78bcc73d 100644 --- a/rebar.config +++ b/rebar.config @@ -59,15 +59,29 @@ ssl_verify_fun,locus,emqttd,hackney,roster,service,active, cowboy,emq_dashboard,emqttc,enenra,envy,uuid,erlydtl,forms, gen_smtp,json_rec,jwt,mad,migresia,mini_s3,nitro,opencensus, - qdate,rest,rfc3339,sh,stacktrace_compat]}, + qdate,rest,rfc3339,sh,stacktrace_compat,nynja_oam]}, {sys_config, "./sys.config"}, {vm_args, "./vm.args"}, {dev_mode, true}, {include_erts, false}, {extended_start_script, true}, + {extended_start_script_extensions, + [{db_snapshot, "extensions/db_snapshot.sh"}, + {db_restore, "extensions/db_restore.sh"}, + {db_install_fallback, "extensions/db_install_fallback.sh"} + ]}, {overlay, [{link, "admin", "./admin"}, {link, "etc", "./etc"}, {link, "priv", "./priv"}, - {link, "asserts", "./asserts"} + {link, "asserts", "./asserts"}, + {link, + "apps/nynja_oam/priv/extensions/db_snapshot.sh", + "bin/extensions/db_snapshot.sh"}, + {link, + "apps/nynja_oam/priv/extensions/db_restore.sh", + "bin/extensions/db_restore.sh"}, + {link, + "apps/nynja_oam/priv/extensions/db_install_fallback.sh", + "bin/extensions/db_install_fallback.sh"} ]} ]}.