Previous Topic: BFC Hostname and External IP Change ScriptNext Topic: check_passphrase


change_host_properties

#!/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).