summaryrefslogtreecommitdiff
path: root/src/rabbit_upgrade.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/rabbit_upgrade.erl')
-rw-r--r--src/rabbit_upgrade.erl120
1 files changed, 69 insertions, 51 deletions
diff --git a/src/rabbit_upgrade.erl b/src/rabbit_upgrade.erl
index 0071a08a..97a07514 100644
--- a/src/rabbit_upgrade.erl
+++ b/src/rabbit_upgrade.erl
@@ -32,11 +32,13 @@
-ifdef(use_specs).
+-type(step() :: atom()).
+-type(version() :: [step()]).
+
-spec(maybe_upgrade/0 :: () -> 'ok' | 'version_not_available').
--spec(read_version/0 ::
- () -> {'ok', [any()]} | rabbit_types:error(any())).
+-spec(read_version/0 :: () -> rabbit_types:ok_or_error2(version(), any())).
-spec(write_version/0 :: () -> 'ok').
--spec(desired_version/0 :: () -> [atom()]).
+-spec(desired_version/0 :: () -> version()).
-endif.
@@ -48,26 +50,25 @@
maybe_upgrade() ->
case read_version() of
{ok, CurrentHeads} ->
- G = load_graph(),
- case unknown_heads(CurrentHeads, G) of
- [] ->
- case upgrades_to_apply(CurrentHeads, G) of
- [] -> ok;
- Upgrades -> apply_upgrades(Upgrades)
- end;
- Unknown ->
- exit({future_upgrades_found, Unknown})
- end,
- true = digraph:delete(G),
- ok;
+ with_upgrade_graph(
+ fun (G) ->
+ case unknown_heads(CurrentHeads, G) of
+ [] -> case upgrades_to_apply(CurrentHeads, G) of
+ [] -> ok;
+ Upgrades -> apply_upgrades(Upgrades)
+ end;
+ Unknown -> throw({error,
+ {future_upgrades_found, Unknown}})
+ end
+ end);
{error, enoent} ->
version_not_available
end.
read_version() ->
case rabbit_misc:read_term_file(schema_filename()) of
- {ok, [Heads]} -> {ok, Heads};
- {error, E} -> {error, E}
+ {ok, [Heads]} -> {ok, Heads};
+ {error, _} = Err -> Err
end.
write_version() ->
@@ -75,17 +76,26 @@ write_version() ->
ok.
desired_version() ->
- G = load_graph(),
- Version = heads(G),
- true = digraph:delete(G),
- Version.
+ with_upgrade_graph(fun (G) -> heads(G) end).
%% -------------------------------------------------------------------
-load_graph() ->
- Upgrades = rabbit_misc:all_module_attributes(rabbit_upgrade),
- rabbit_misc:build_acyclic_graph(
- fun vertices/2, fun edges/2, fun graph_build_error/1, Upgrades).
+with_upgrade_graph(Fun) ->
+ case rabbit_misc:build_acyclic_graph(
+ fun vertices/2, fun edges/2,
+ rabbit_misc:all_module_attributes(rabbit_upgrade)) of
+ {ok, G} -> try
+ Fun(G)
+ after
+ true = digraph:delete(G)
+ end;
+ {error, {vertex, duplicate, StepName}} ->
+ throw({error, {duplicate_upgrade_step, StepName}});
+ {error, {edge, {bad_vertex, StepName}, _From, _To}} ->
+ throw({error, {dependency_on_unknown_upgrade_step, StepName}});
+ {error, {edge, {bad_edge, StepNames}, _From, _To}} ->
+ throw({error, {cycle_in_upgrade_steps, StepNames}})
+ end.
vertices(Module, Steps) ->
[{StepName, {Module, StepName}} || {StepName, _Reqs} <- Steps].
@@ -93,10 +103,6 @@ vertices(Module, Steps) ->
edges(_Module, Steps) ->
[{Require, StepName} || {StepName, Requires} <- Steps, Require <- Requires].
-graph_build_error({vertex, duplicate, StepName}) ->
- exit({duplicate_upgrade, StepName});
-graph_build_error({edge, E, From, To}) ->
- exit({E, From, To}).
unknown_heads(Heads, G) ->
[H || H <- Heads, digraph:vertex(G, H) =:= false].
@@ -111,8 +117,8 @@ upgrades_to_apply(Heads, G) ->
sets:from_list(digraph_utils:reaching(Heads, G)))),
%% Form a subgraph from that list and find a topological ordering
%% so we can invoke them in order.
- [element(2, digraph:vertex(G, StepName))
- || StepName <- digraph_utils:topsort(digraph_utils:subgraph(G, Unsorted))].
+ [element(2, digraph:vertex(G, StepName)) ||
+ StepName <- digraph_utils:topsort(digraph_utils:subgraph(G, Unsorted))].
heads(G) ->
lists:sort([V || V <- digraph:vertices(G), digraph:out_degree(G, V) =:= 0]).
@@ -120,19 +126,35 @@ heads(G) ->
%% -------------------------------------------------------------------
apply_upgrades(Upgrades) ->
- LockFile = lock_filename(),
- case file:open(LockFile, [write, exclusive]) of
- {ok, Lock} ->
- ok = file:close(Lock),
+ LockFile = lock_filename(dir()),
+ case rabbit_misc:lock_file(LockFile) of
+ ok ->
+ BackupDir = dir() ++ "-upgrade-backup",
info("Upgrades: ~w to apply~n", [length(Upgrades)]),
- [apply_upgrade(Upgrade) || Upgrade <- Upgrades],
- info("Upgrades: All applied~n", []),
- ok = write_version(),
- ok = file:delete(LockFile);
+ case rabbit_mnesia:copy_db(BackupDir) of
+ ok ->
+ %% We need to make the backup after creating the
+ %% lock file so that it protects us from trying to
+ %% overwrite the backup. Unfortunately this means
+ %% the lock file exists in the backup too, which
+ %% is not intuitive. Remove it.
+ ok = file:delete(lock_filename(BackupDir)),
+ info("Upgrades: Mnesia dir backed up to ~p~n", [BackupDir]),
+ [apply_upgrade(Upgrade) || Upgrade <- Upgrades],
+ info("Upgrades: All upgrades applied successfully~n", []),
+ ok = write_version(),
+ ok = rabbit_misc:recursive_delete([BackupDir]),
+ info("Upgrades: Mnesia backup removed~n", []),
+ ok = file:delete(LockFile);
+ {error, E} ->
+ %% If we can't backup, the upgrade hasn't started
+ %% hence we don't need the lockfile since the real
+ %% mnesia dir is the good one.
+ ok = file:delete(LockFile),
+ throw({could_not_back_up_mnesia_dir, E})
+ end;
{error, eexist} ->
- exit(previous_upgrade_failed);
- {error, _} = Error ->
- exit(Error)
+ throw({error, previous_upgrade_failed})
end.
apply_upgrade({M, F}) ->
@@ -141,16 +163,12 @@ apply_upgrade({M, F}) ->
%% -------------------------------------------------------------------
-schema_filename() ->
- filename:join(dir(), ?VERSION_FILENAME).
+dir() -> rabbit_mnesia:dir().
-lock_filename() ->
- filename:join(dir(), ?LOCK_FILENAME).
+schema_filename() -> filename:join(dir(), ?VERSION_FILENAME).
+
+lock_filename(Dir) -> filename:join(Dir, ?LOCK_FILENAME).
%% NB: we cannot use rabbit_log here since it may not have been
%% started yet
-info(Msg, Args) ->
- error_logger:info_msg(Msg, Args).
-
-dir() ->
- rabbit_mnesia:dir().
+info(Msg, Args) -> error_logger:info_msg(Msg, Args).