aboutsummaryrefslogtreecommitdiff
path: root/apps/dreki/src/dreki_world_dns.erl
blob: 058c9db045a28375351dcdcc52b1a40f6e562871 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
-module(dreki_world_dns).

-export([start/0, graph/0, node_ips/1, node/0, parents/1, nodes/0, regions/0, vertex/1, node_params/1, node_param/2, as_map/0]).
-export([get_path/2, in_neighbours/1, out_neighbours/1]).

get_path(V1, V2) ->
    digraph:get_path(graph(), V1, V2).

in_neighbours(V) ->
    digraph:in_neighbours(graph(), V).

out_neighbours(V) ->
    digraph:out_neighbours(graph(), V).

as_map() ->
  Vertices = lists:foldr(fun (V = {Type, Key}, Acc) ->
    {_, Label} = digraph:vertex(graph(), V),
    BType = atom_to_binary(Type),
    Node = <<BType/binary, ":", Key/binary>>,
    [#{node => Node, type => Type, name => Key, data => Label} | Acc]
  end, [], digraph:vertices(graph())),
  Edges = lists:foldr(fun (E, Acc) ->
    {_, {Ft, Fn}, {Tt, Tn}, Label} = digraph:edge(graph(), E),
    BFt = atom_to_binary(Ft),
    BTt = atom_to_binary(Tt),
    Fk = <<BFt/binary, ":", Fn/binary>>,
    Tk = <<BTt/binary, ":", Tn/binary>>,
    [#{from => Fk, to => Tk, data => Label} | Acc]
  end, [], digraph:edges(graph())),
  #{vertices => Vertices, edges => Edges}.

vertex(V) ->
  digraph:vertex(graph(), V).

nodes() ->
  [N || V = {node, N} <- digraph_utils:topsort(graph())].

regions() ->
  vertices(region).

vertices(Type) ->
  [V || V = {Type, _} <- digraph_utils:topsort(graph())].

node() ->
  {node, dreki_world:domain()}.

node_params(N) ->
  case digraph:vertex(graph(), {node, N}) of
    {_, Params} -> {ok, Params};
    _ -> {error, no_such_node}
  end.

node_param(N, Key) ->
  case node_params(N) of
    {ok, P} -> {ok, maps:get(Key, P)};
    Err -> Err
  end.

parents(V) ->
  digraph:in_neighbours(graph(), V).

node_ips(N) ->
  case digraph:vertex(graph(), {node, N}) of
    {{node, N}, #{srvs := SRVs}} ->
      IPs = [ {I,Port} || #{name := Name, port := Port} <- SRVs,
                          T <- [a, aaaa],
                          {ok, {_,_,_,_,_,Ip}} <- [inet_res:getbyname(binary_to_list(Name), T)],
                          I <- Ip],
      {ok, lists:flatten(IPs)};
    Err ->
      {error, {no_such_node, N, Err}}
  end.

start() ->
  {ok, Graph, Errs} = build(),
  persistent_term:put({?MODULE, graph}, Graph),
  {ok, Errs}.

graph() ->
  persistent_term:get({?MODULE, graph}).

build() ->
  Root = dreki_world:internal_domain(),
  Host = sd_dns(Root),
  case inet_res:getbyname(Host, srv) of
    {ok, {hostent, _Host, [], srv, _, SRVs}} ->
      Graph = digraph:new([acyclic]),
      {Name, NameErrs} = read_txt(name_dns(Root), Root),
      V = {root, Root},
      digraph:add_vertex(Graph, V, #{display_name => Name}),
      Errs = collect_sd_srvs(SRVs, V, Graph, NameErrs),
      {ok, Graph, lists:flatten(Errs)};
    {error, DNSErr} when is_atom(DNSErr) ->
      {error, #{error => "world_dns_error", dns_error => DNSErr, host => Host}}
  end.

expand_sd_srv(Host, Parent, Graph) ->
  NodeHost = node_dns(Host),
  {Vn, Acc0} = case inet_res:getbyname(NodeHost, srv) of
    {ok, {hostent, _, _, srv, _, NSRVs}} ->
      Targets = lists:foldr(fun ({Priority, Weight, Port, Name}, Acc) ->
        [#{name => list_to_binary(Name), port => Port, priority => Priority, weight => Weight} | Acc]
      end, [], NSRVs),
      {NName, NameErrs} = read_txt(name_dns(Host), name(Host)),
      {NodeName, NameErrs2} = read_txt(node_name_dns(Host), <<"dreki@", Host/binary>>),
      Nv = {node, Host},
      digraph:add_vertex(Graph, Nv, #{display_name => NName, srvs => Targets, node_name => binary_to_atom(NodeName)}),
      digraph:add_edge(Graph, Parent, Nv, #{}),
      {Nv, [] ++ NameErrs ++ NameErrs2};
    {error, nxdomain} -> {undefined, []};
    {error, VDNSErr} when is_atom(VDNSErr) ->
      logger:log(error, #{dns_error => VDNSErr, host => NodeHost}),
      {undefined, [{error, #{error => "world_dns_error", dns_error => VDNSErr, host => NodeHost}}]}
  end,
  SdHost = sd_dns(Host),
  Acc1 = case inet_res:getbyname(SdHost, srv) of
    {ok, {hostent, _SdHost, [], srv, _, SSRVs}} ->
      {RName, RNameErrs} = read_txt(name_dns(Host), name(Host)),
      V = {region, Host},
      digraph:add_vertex(Graph, V, #{display_name => RName}),
      case Vn of
        undefined -> digraph:add_edge(Graph, Parent, V, #{});
        Vn -> digraph:add_edge(Graph, Vn, V, #{})
      end,
      collect_sd_srvs(SSRVs, V, Graph, RNameErrs);
    {error, nxdomain} -> [];
    {error, DNSErr} when is_atom(DNSErr) ->
      logger:log(error, #{dns_error => DNSErr, host => SdHost}),
      [{error, #{error => "world_dns_error", dns_error => DNSErr, host => SdHost}}]
  end,
  [Acc0, Acc1].

collect_sd_srvs([], _, _Graph, Acc) -> Acc;
collect_sd_srvs([{0, 0, 1337, Entry} | Rest], Parent, Graph, Acc) ->
  collect_sd_srvs(Rest, Parent, Graph, [expand_sd_srv(list_to_binary(Entry), Parent, Graph) | Acc]).

read_txt(Host, Default) ->
  case inet_res:getbyname(Host, txt) of
    {error, nxdomain} -> {Default, []};
    {ok,{hostent, _,  _, _, _, Lines}} -> {list_to_binary(Lines), []};
    {error, DNSErr} when is_atom(DNSErr) ->
      logger:log(error, #{dns_error => DNSErr, host => Host}),
      {Default, [#{error => "world_dns_error", dns_error => DNSErr, host => Host}]}
  end.

sd_dns(Domain) -> dnsname(<<"_dreki">>, Domain).
node_dns(Domain) -> dnsname(<<"_node._dreki">>, Domain).
name_dns(Domain) -> dnsname(<<"_name._dreki">>, Domain).
node_name_dns(Domain) -> dnsname(<<"_name._node._dreki">>, Domain).

dnsname(Prefix, Domain) when is_list(Prefix) ->
  dnsname(list_to_binary(Prefix), Domain);
dnsname(Prefix, Domain) when is_list(Domain) ->
  dnsname(Prefix, list_to_binary(Domain));
dnsname(Prefix, Domain) ->
  Full = <<Prefix/binary, ".", Domain/binary>>,
  binary:bin_to_list(Full).

name(Host) when is_list(Host) ->
  name(list_to_binary(Host));
name(Host) ->
  hd(binary:split(Host, <<".">>)).