#!/usr/bin/env escript
%% -*- erlang -*-
%%! -sname change_ext_ip -noinput
%% -module(change_ext_ip).
%% -export([main/1]).
%% -include_lib("kernel/include/inet.hrl").
-define(CONTEXT_TABLE, 'component_context').
-define(INFO_TABLE, 'component_info').
-define(INSTALL_DATA, 'core/install_data').
-define(CONFIG_COMPONENT, 'core/system_config').
-define(DHCP_MGR, 'core/boot.dhcp_manager_singleton').
-define(DHCP_CFG, 'core/boot.dhcp_config_singleton').
-define(LDAP_HUB, 'core/ldap_eldap_hub').
-define(DHCP_CFILE_PARMS, 'core/dhcp_configfile_params').
-define(QUOTE, 34). %% To use instead of $" which screws up emacs editing
-define(SEMI, 59). %% same reason
-define(VALID_ARG_KEYS, ["ip", "hostname", "uname", "restore"]).
-define(NOT_SET, '$__NOT_SET__$').
-define(MNESIA_BACKUP_FILE, filename:join(["", "tmp", "mnesia.backup"])).
-record(attr_changed, {ip_changed=false,
hostname_changed=false,
uname_changed=false}).
-record(hostent, {h_name, h_aliases, h_addrtype, h_length, h_addr_list}).
main([]) ->
print_error_and_exit(usage());
main(Args) ->
ArgList = process_args(Args),
%% Update HostInfo for each replica
Replicas = find_replicas(),
case Replicas == [] andalso
lists:keymember("hostname", 1, ArgList) of
true ->
print_error_and_exit("In order to change either the hostname "
" you need to have a BFC replica configured");
false ->
ok
end,
IPNameUpdated =
lists:foldl(
fun({"ip", IPList}, Acc) ->
%% validate the IP and then set it in the database
IP = hd(IPList),
IpChanged = validate_ip(IP),
update_fcconfig(IP),
update_ext_ip(IP),
Acc#attr_changed{ip_changed=IpChanged};
({"hostname", V}, Acc) ->
ShortName = fqdn_to_shortname(V),
NewNode = list_to_atom(
lists:flatten(
string:join(["bfc", ShortName], "@"))),
case bfcnode() of
NewNode ->
Acc;
_ ->
io:format(
"~nUpdating hostname within the BFC App~n"),
Acc#attr_changed{hostname_changed=true}
end;
({"uname", V}, Acc) ->
OldUname = cfg_shortname(),
case OldUname of
V -> Acc;
_ ->
update_ldap_queue(OldUname, V),
update_dhcp_mgt_cache(OldUname, V),
update_dhcp_config(OldUname, V),
Acc#attr_changed{uname_changed=true}
end;
({"restore", BackupLoc}, Acc) ->
io:format("~nRestoring database to pickup name changes~n"),
install_fallback(BackupLoc),
io:format("~nRestore finished. Please restart the bfc "
"application (server bfc restart)~n"),
Acc;
(_, _) ->
print_error_and_exit(usage())
end,
#attr_changed{}, ArgList),
lists:foreach(
fun(Dir) ->
validate_replica(Dir),
update_hostinfo(Dir, ArgList)
end,
Replicas),
#attr_changed{ip_changed=IPChanged,
hostname_changed=HostnameChanged,
uname_changed=UnameChanged} = IPNameUpdated,
case IPChanged of
true ->
io:format("~nYou have the BFC name associated with the old IP "
"address (in /etc/hosts or DNS), you should stop the "
"BFC service, make changes to /etc/hosts and/or DNS, "
"and then restart the BFC service~n");
false ->
ok
end,
case HostnameChanged of
true ->
ReplNames = [ "replica" ++ integer_to_list(N) ||
N <- lists:seq(1, length(Replicas)) ],
backup_mnesia(),
OldSnames = ["bfc"|ReplNames],
lists:foreach(
fun(Nm) ->
[_, HN] = string:tokens(atom_to_list(bfcnode()), "@"),
OldNodeName = lists:flatten(string:join([Nm, HN], "@")),
NewNodeName =
lists:flatten(
string:join([Nm, hostname_from_args(ArgList)], "@")),
change_nodename(OldNodeName, NewNodeName)
end,
OldSnames),
io:format("~nUpdating the hostname of the BFC server requires "
"a restore from a backup in order to update the "
"database. We have created a backup file: ~s."
"~nIn order to perform the restore from the backup, "
"you will need to uninstall the BFC application "
"(~s/bin/uninstall -f), delete your current replica "
"(or move it aside and make an empty directory "
"at the previous directory location). Then, (after "
"changing the hostname of the system), perform a fresh "
"install of the BFC application using the same inputs "
"as the previous install (and using the same install "
"iso). Finally, re-run this script with the "
"--restore=~p option only.~n",
[?MNESIA_BACKUP_FILE, installroot(), ?MNESIA_BACKUP_FILE]),
io:format("~n Don't forget to update the hostname in files "
"that effect the hostname on reboot "
"(/etc/sysconfig/netork, /etc/hosts, DNS servers, etc)~n"),
case UnameChanged of
false ->
io:format("~nIf you wish to retain the current "
"hostname, you may re-run this script and "
"provide the original hostname.~n");
_ -> ok
end;
_ -> ok
end,
case UnameChanged of
true ->
io:format("~nUpdating the node name (uname) of the BFC "
"server requires a recovery install of the BFC. "
"This should be done after the procedure for "
"updating the hostname if that changed as well.~n"
"Please run ~s/bin/uninstall -f. When that "
"finishes, update the uname of the system "
"(generally using hostname <newname> and then updating "
"/etc/sysconfig/network with the new name). If you wish "
"to maintain a hostname that is separate from the "
"uname, add the hostname you desire as the first "
"name for either the loopback or external IP in the "
"/etc/hosts file~n. After updating the hostname, "
"re-install the BFC application, select yes at the "
"question where you are asked whether you want to "
"recover from a replica, and then provide the "
"location of your replica directory~n.", [installroot()]),
io:format("~n~nIf you wish to retain the current name, you may "
"re-run this script and provide the original hostname~n");
_ -> ok
end,
init:stop(0).
usage() ->
"change_host_properties [--ip=<new IP address>] [--hostname=<new hostname>]"
"[--uname=<new uname>] [--restore]".
process_args([]) ->
print_error_and_exit(usage());
process_args(Args) ->
%% io:format("Args: ~p~n", [Args]),
ValidArg =
fun(X) ->
Tokens = string:tokens(X, "="),
Key = string:strip(hd(Tokens), left, $-),
case lists:member(Key, ?VALID_ARG_KEYS) of
true ->
Value = case length(Tokens) of
1 -> ?NOT_SET;
2 -> string:strip(tl(Tokens));
_ -> print_error_and_exit(usage())
end,
{Key, Value};
_ -> print_error_and_exit("Invalid argument(s) ~p", [Args])
end
end,
SortFun = fun({A,_},{B, _}) ->
case "ip" of
A -> true;
B -> false;
_ -> A=<B
end
end,
lists:sort(SortFun, [ValidArg(Arg) || Arg <- Args]).
hostname_from_args(ArgList) ->
[HostTuple] =
lists:filter(
fun({K, _}) -> K == "hostname" end,
ArgList),
element(2, HostTuple).
backup_mnesia() ->
%% io:format("backup_mnesia~n"),
case filelib:is_regular(?MNESIA_BACKUP_FILE) of
true -> file:delete(?MNESIA_BACKUP_FILE);
false -> ok
end,
rpc_call(mnesia, backup, [?MNESIA_BACKUP_FILE]).
change_nodename(From, To) ->
%% io:format("Changing node name: ~p to ~p.~n", [From, To]),
NewMnesiaBackupFile = filename:join(["", "tmp", "new.mnesia.backup"]),
case change_node_name(mnesia_backup, list_to_atom(From), list_to_atom(To),
?MNESIA_BACKUP_FILE, NewMnesiaBackupFile) of
{ok, _} -> ok;
R -> throw(R)
end,
case file:rename(NewMnesiaBackupFile, ?MNESIA_BACKUP_FILE) of
ok -> ok;
V -> throw(V)
end.
change_node_name(Mod, From, To, Source, Target) ->
Switch =
fun(Node) when Node == From -> To;
(Node) when Node == To -> throw({error, already_exists});
(Node) -> Node
end,
Convert =
fun({schema, db_nodes, Nodes}, Acc) ->
NewDBNodes = lists:map(Switch, Nodes),
%% io:format("New db_nodes ~p.~n", [NewDBNodes]),
{[{schema, db_nodes, NewDBNodes}], Acc};
({schema, version, Version}, Acc) ->
%% io:format("Version: ~p.~n", [Version]),
{[{schema, version, Version}], Acc};
({schema, cookie, Cookie}, Acc) ->
%% io:format("Cookie: ~p.~n", [Cookie]),
{[{schema, cookie, Cookie}], Acc};
({schema, Tab, CreateList}, Acc) ->
%% io:format("CreateList: ~p.~n", [CreateList]),
Keys = [ram_copies, disc_copies, disc_only_copies],
OptSwitch =
fun({Key, Val}) ->
case lists:member(Key, Keys) of
true ->
NewCopyNodes = lists:map(Switch, Val),
{Key, NewCopyNodes};
false->
update_val(Key, Val, Switch)
end
end,
NewCreateList = lists:map(OptSwitch, CreateList),
%% io:format("NewCreateList: ~p.~n", [NewCreateList]),
{[{schema, Tab, NewCreateList}], Acc};
(Other, Acc) ->
%% io:format("Other: ~p.~n", [Other]),
{[Other], Acc}
end,
mnesia:traverse_backup(Source, Mod, Target, Mod, Convert, switched).
update_val(version, Value, SwitchFun) ->
{Vers, VersVal} = Value,
Res =
case VersVal of
V when is_tuple(V) ->
{NN, N} = V,
NNN = SwitchFun(NN),
{version, {Vers, {NNN, N}}};
V when is_list(V) ->
VV = lists:map(SwitchFun, V),
{version, {Vers, VV}};
_ -> {version, Value}
end,
Res;
update_val(cookie, Value, SwitchFun) ->
{C, NN} = Value,
NNN = SwitchFun(NN),
Res = {cookie, {C, NNN}},
Res;
update_val(Key, Value, _) ->
{Key, Value}.
install_fallback(BackupLoc) ->
rpc_call(mnesia, install_fallback, [BackupLoc]).
installroot() ->
os:cmd("rpm -q --queryformat \"%{INSTALLPREFIX}\" bfc").
bfcnode() ->
case get(nodename) of
undefined ->
NodeStr = lists:flatten(string:join(["bfc", shortname()], "@")),
Node = list_to_atom(NodeStr),
N = case rpc:call(Node, application, which_applications, []) of
{badrpc, _} ->
CfgStr = lists:flatten(
string:join(["bfc", cfg_shortname()], "@")),
list_to_atom(CfgStr);
_ -> Node
end,
put(nodename, N),
N;
N -> N
end.
%% uname() ->
%% string:strip(os:cmd("uname -n"), right, $\n).
shortname() ->
fqdn_to_shortname(hostname()).
hostname() ->
string:strip(os:cmd("hostname -s"), right, $\n).
cfg_shortname() ->
fqdn_to_shortname(cfg_hostname()).
cfg_hostname() ->
%% read from dhcpd.conf as if we're here, we can't reach node with current
%% hostname
DhcpConfFile = filename:join(["", "etc", "dhcpd.conf"]),
{ok, Bin} = file:read_file(DhcpConfFile),
Lines = string:tokens(binary_to_list(Bin), "\n"),
[Hn] =
lists:foldl(
fun(L, Acc) ->
case L of
"ldap-base-dn " ++ BaseDn ->
[base_dn_to_fqdn(BaseDn)|Acc];
_ -> Acc
end
end,
[], Lines),
Hn.
base_dn_to_fqdn(DN) ->
D = string:strip(string:strip(DN, right, ?SEMI), both, ?QUOTE),
HostComponents =
lists:foldr(
fun(Rdn, Acc) ->
case Rdn of
"dc=" ++ HostPart ->
[HostPart|Acc];
_ -> Acc
end
end,
[], string:tokens(D, ",")),
string:join(HostComponents, ".").
fqdn_to_shortname([]) ->
"";
fqdn_to_shortname(Fqdn) ->
hd(string:tokens(Fqdn, ".")).
validate_ip(IP) ->
io:format("Validating IP ~p~n", [IP]),
{ok, LoopbackIP} = inet_parse:address("127.0.0.1"),
{ok, NewIP} = inet_parse:address(IP),
{ok, #hostent{h_addr_list=Addrs}} = inet:gethostbyname(hostname()),
lists:foldl(
fun(FoundIP, Acc) ->
case FoundIP of
LoopbackIP -> Acc or false;
NewIP -> Acc or false;
_ -> true
end
end,
false, Addrs).
update_ext_ip(IP) ->
%% io:format("update_ext_ip(~p)~n", [IP]),
OldIP = rpc_call(component_impl, direct_singleton_call,
[?INSTALL_DATA, getValue, ["BFC", "externalip"]]),
case IP of
OldIP ->
ok;
_ ->
io:format("Updating the external IP within the BFC app~n"),
rpc_call(component_impl, direct_singleton_call,
[?INSTALL_DATA, setValue, ["BFC", "externalip", IP]]),
io:format("Updated external IP~n")
end.
find_replicas() ->
%% io:format("find_replicas~n"),
rpc_call(component_app, get_replica_paths, []).
update_fcconfig(IP) ->
FName = "fcconfig.cfg",
ConfDir = filename:join(["", "etc", "bfc", "conf"]),
ConfFile = filename:join(ConfDir, FName),
TmpDir = filename:join("", "tmp"),
TmpFile = filename:join(TmpDir, FName),
{ok, Bin} = file:read_file(ConfFile),
Lines = string:tokens(binary_to_list(Bin), "\n"),
NewLines =
lists:foldr(
fun(L, Acc) ->
case starts_with(L, "externalip") of
true -> ["externalip = " ++ IP|Acc];
_ -> [L|Acc]
end
end,
[], Lines),
NewBin = string:join(NewLines, "\n") ++ "\n",
file:write_file(TmpFile, NewBin),
file:copy(TmpFile, ConfFile),
file:delete(TmpFile).
starts_with(Line, Str) ->
string:str(string:strip(Line, left), Str) =:= 1.
validate_replica(ReplicaDir) ->
case filelib:is_dir(ReplicaDir) of
false ->
print_error_and_exit("Replica directory ~s does not "
"exist.", [ReplicaDir]);
_ -> ok
end.
update_hostinfo(ReplicaDir, ArgList) ->
%% io:format("update_hostinfo(~p, ~p)~n", [ReplicaDir, ArgList]),
%% This call updates ext ip in file but uses current hostname/uname
rpc_call(component_impl, direct_singleton_call,
[?CONFIG_COMPONENT, saveHostInfoInReplica, [ReplicaDir]]),
%% Update hostname/uname if needed
NameUpdateMap =
[{"cfg_" ++ K ++ " = \\S+","cfg_" ++ K ++ " = " ++ V} ||
{K, V} <- ArgList, (K == "hostname" orelse K == "uname")],
case NameUpdateMap of
[] ->
ok;
_ ->
HIFile = filename:join(ReplicaDir, "HOSTINFO"),
{ok, HIBin} = file:read_file(HIFile),
NewHI = replace_with(binary_to_list(HIBin), NameUpdateMap),
file:write_file(HIFile, NewHI)
end.
get_singleton(Name) ->
%% io:format("get_singleton(~p)~n", [Name]),
%% io:format("escript arguments: ~p~n", [init:get_arguments()]),
%% io:format("escript scriptname: ~p.~n", [escript:script_name()]),
rpc_call(code, add_path, [filename:dirname(escript:script_name())]),
{Id, _} = rpc_call(component_impl, fetch_singleton, [Name]),
Ret = rpc_call(mnesia, transaction,
[fun(X) ->
[{component_info, _, _, Driver, _, _}] =
mnesia:read(component_info, X),
case is_atom(Driver) of
true -> X;
_ -> Driver
end
end,
[Id]]),
%% io:format("Ret: ~p.~n", [Ret]),
{atomic, DriverId} = Ret,
DriverId.
get_field(FieldName, Id) ->
%% io:format("get_field(~p, ~p)~n", [FieldName, Id]),
GetFields =
fun() ->
[{component_context, _, Flds, _}] =
mnesia:read(?CONTEXT_TABLE, Id),
Flds
end,
{atomic, Fields} = rpc_call(mnesia, transaction, [GetFields]),
gb_trees:get(FieldName, Fields).
set_field(FieldName, Value, Id) ->
%% io:format("set_field(~p, ~p, ~p)~n", [FieldName, Value, Id]),
UpdateField =
fun() ->
[{component_context, Id, Dict, FsmDict}] =
mnesia:read(?CONTEXT_TABLE, Id),
mnesia:write({component_context, Id,
gb_trees:enter(FieldName, Value, Dict), FsmDict})
end,
rpc_call(mnesia, transaction, [UpdateField]),
Pid = rpc_call(component_impl, fetch_pid, [Id]),
rpc_call(erlang, exit, [Pid, kill]),
timer:sleep(1000).
update_ldap_queue(OldName, NewName) ->
LdapId = get_singleton(?LDAP_HUB),
EncOpQueue = get_field(operations, LdapId),
Ops = [update_ldap_op(EncOp, OldName, NewName) ||
EncOp <- queue:to_list(EncOpQueue)],
set_field(operations, queue:from_list(Ops), LdapId).
update_ldap_op(EncOp, Old, New) ->
OpBin = rpc_call(encrypted_data_driver, decrypt, [EncOp]),
Op = binary_to_term(OpBin),
%% io:format("O LDAP Op: ~p.~n", [Op]),
NewOp =
case Op of
{close, Args} ->
{close, Args};
{F, Args} when is_pid(hd(Args)) ->
[Conn, Dn|Rest] = Args,
{F, [Conn, re:replace(Dn, Old, New, [{return, list}])|Rest]};
{F, Args} -> {F, Args}
end,
NewOpBin = term_to_binary(NewOp),
NewEncOp = rpc_call(encrypted_data_driver, encrypt, [NewOpBin]),
case NewEncOp of
EncOp -> ok;
_ ->
%%io:format("Op~n~p~nhas been updated to~n~p~n", [Op, NewOp])
ok
end,
NewEncOp.
update_dhcp_mgt_cache(OldName, NewName) ->
DhcpId = get_singleton(?DHCP_MGR),
Cache = get_field(dhcp_config_cache, DhcpId),
NewCache = [update_type_cache(TC, OldName, NewName) || TC <- Cache],
set_field(dhcp_config_cache, NewCache, DhcpId).
update_type_cache(TC, Old, New) ->
{CacheType, Entries} = TC,
NewEntries = [update_entry(E, Old, New) || E <- Entries],
{CacheType, NewEntries}.
update_entry(E, Old, New) ->
{EntryId, {ConfigId, EntryRec}} = E,
{EntryId, {replace_cfg_name(ConfigId, Old, New), EntryRec}}.
replace_cfg_name(ConfIdStr, Old, New) ->
re:replace(ConfIdStr, cfg_name(Old), cfg_name(New), [{return, list}]).
cfg_name(Name) ->
string:join(["dc=" ++ N || N <- string:tokens(Name, ".")], ",").
update_dhcp_config(OldName, NewName) ->
DhcpCfgId = get_singleton(?DHCP_CFG),
CfgFileParms = get_field(?DHCP_CFILE_PARMS, DhcpCfgId),
UpdateFun =
fun({K, V}) ->
{K, re:replace(V, OldName, NewName, [{return, list}])}
end,
NewCfgFileParms = [UpdateFun({K, V}) || {K, V} <- CfgFileParms],
set_field(?DHCP_CFILE_PARMS, NewCfgFileParms, DhcpCfgId).
rpc_call(Mod, Op, Args) ->
%% io:format("rpc_call(~p, ~p, ~p)~n", [Mod, Op, Args]),
%% Node = bfcnode(),
%% io:format("bfcnode: ~p.~n", [Node]),
case rpc:call(bfcnode(), Mod, Op, Args) of
{badrpc, nodedown} ->
print_error_and_exit("BFC service is not running");
{badrpc, Msg} ->
print_error_and_exit("Call failed, reason: ~p", [Msg]);
Ret -> Ret
end.
replace_with(S, []) ->
S;
replace_with(S, [{O,N}|T]) ->
replace_with(re:replace(S, O, N, [global, {return, list}]), T).
print_error_and_exit(Msg) ->
print_error_and_exit(Msg, []).
print_error_and_exit(Msg, Args) ->
io:format(Msg ++ "~n", Args),
io:format("Exiting.~n"),
halt(1).
|
Copyright © 2013 CA Technologies.
All rights reserved.
|
|