Ignore servers which are down when building a GeoDNS map
[dns.git] / bin / mkgeo
1 #!/usr/bin/perl
2
3 use strict;
4 use warnings;
5
6 use IO::File;
7 use Math::Trig qw(deg2rad pip2 great_circle_distance);
8 use JSON::XS;
9 use LWP::UserAgent;
10 use XML::Writer;
11 use XML::TreeBuilder;
12 use YAML;
13
14 my $source = shift @ARGV;
15 my $zone = shift @ARGV;
16 my $servers = YAML::LoadFile("src/${source}");
17
18 my $ua = LWP::UserAgent->new;
19
20 $ua->default_header("App-Key", "2cohi62u5haxvqmypk3ljqqrze1jufrh");
21 $ua->credentials("api.pingdom.com:443", "Pingdom API", $ENV{PINGDOM_USERNAME}, $ENV{PINGDOM_PASSWORD});
22
23 foreach my $server (values %$servers)
24 {
25     $server->{status} = "down";
26
27     if (my $checkid = $server->{pingdom})
28     {
29         my $response = $ua->get("https://api.pingdom.com/api/2.0/checks/${checkid}");
30
31         if ($response->is_success)
32         {
33             my $check = decode_json($response->content);
34
35             $server->{status} = $check->{check}->{status};
36         }
37     }
38 }
39
40 my %countries = ();
41
42 my $countries = XML::TreeBuilder->new;
43
44 $countries->parsefile("lib/countries.xml");
45
46 foreach my $country ($countries->look_down("_tag" => "country"))
47 {
48     my $code = $country->look_down("_tag" => "countryCode")->as_text;
49     my $name = $country->look_down("_tag" => "countryName")->as_text;
50     my $continent = $country->look_down("_tag" => "continent")->as_text;
51     my $west = $country->look_down("_tag" => "bBoxWest")->as_text;
52     my $north = $country->look_down("_tag" => "bBoxNorth")->as_text;
53     my $east = $country->look_down("_tag" => "bBoxEast")->as_text;
54     my $south = $country->look_down("_tag" => "bBoxSouth")->as_text;
55     my $lat = centre_lat( $south, $north );
56     my $lon = centre_lon( $west, $east );
57     my @servers;
58
59     foreach my $servername (keys %$servers)
60     {
61         my $server = $servers->{$servername};
62         my $match = match_country($server, $code, $continent);
63
64         if ($match eq "preferred" || $match eq "allowed")
65         {
66             my $priority = $match eq "preferred" ? 20 : 10;
67             my $distance = distance($lat, $lon, $server->{lat}, $server->{lon});
68
69             $priority = $priority * 10 if $server->{status} eq "up";
70
71 #            print STDERR "$servername is $match for $name with distance $distance\n";
72
73             push @servers, { name => $servername, priority => $priority, distance => $distance };
74         }
75     }
76
77     $countries{$code} = {
78         code => $code, name => $name, continent => $continent,
79         lat => $lat, lon => $lon, servers => \@servers
80     };
81 }
82
83 $countries->delete;
84
85 my $zonefile = IO::File->new("> data/${zone}") || die "$!";
86 my $kmlfile = IO::File->new("> kml/${zone}.kml") || die "$!";
87 my $kmlwriter = XML::Writer->new(OUTPUT => $kmlfile, ENCODING => 'utf-8');
88
89 $kmlwriter->xmlDecl();
90 $kmlwriter->startTag("kml", "xmlns" => "http://www.opengis.net/kml/2.2");
91 $kmlwriter->startTag("Document");
92
93 foreach my $country (values %countries)
94 {
95     my @servers = sort { $b->{priority} <=> $a->{priority} || $a->{distance} <=> $b->{distance} } @{$country->{servers}};
96     my $server = $servers->{$servers[0]->{name}};
97     my $clon = $country->{lon};
98     my $clat = $country->{lat};
99     my $slon = $server->{lon};
100     my $slat = $server->{lat};
101
102     if ($clon > 0 && $slon < 0 && 360 + $slon - $clon < $clon - $slon)
103     {
104         $clon = $clon - 360;
105     }
106
107     $zonefile->print("C\L$country->{code}\E.${zone}:$servers[0]->{name}.${zone}:600\n");
108
109     $kmlwriter->startTag("Placemark");
110     $kmlwriter->dataElement("name", $country->{name});
111     $kmlwriter->startTag("LineString");
112     $kmlwriter->dataElement("coordinates", "$clon,$clat $slon,$slat");
113     $kmlwriter->endTag("LineString");
114     $kmlwriter->endTag("Placemark");
115 }
116
117 foreach my $server (grep { $servers->{$_}->{default} } keys %$servers)
118 {
119     $zonefile->print("Cxx.${zone}:${server}.${zone}:600\n");
120 }
121
122 $kmlwriter->endTag("Document");
123 $kmlwriter->endTag("kml");
124 $kmlwriter->end();
125
126 $kmlfile->close();
127 $zonefile->close();
128
129 exit 0;
130
131 sub centre_lat
132 {
133     my $south = shift;
134     my $north = shift;
135
136     return ( $south + $north ) / 2;
137 }
138
139 sub centre_lon
140 {
141     my $west = shift;
142     my $east = shift;
143     my $lon;
144
145     if ($west < $east)
146     {
147         $lon = ( $west + $east ) / 2;
148     }
149     else
150     {
151         $lon = ( $west + $east + 360 ) / 2;
152     }
153
154     $lon = $lon - 360 if $lon > 180;
155
156     return $lon
157 }
158
159 sub match_country
160 {
161     my $server = shift;
162     my $country = shift;
163     my $continent = shift;
164     my $match;
165
166     if ($server->{preferred} &&
167         $server->{preferred}->{countries} &&
168         grep { $_ eq $country } @{$server->{preferred}->{countries}})
169     {
170         $match = "preferred";
171     }
172     elsif ($server->{preferred} &&
173            $server->{preferred}->{continents} &&
174            grep { $_ eq $continent } @{$server->{preferred}->{continents}})
175     {
176         $match = "preferred";
177     }
178     elsif ($server->{allowed} &&
179            $server->{allowed}->{countries} &&
180            grep { $_ eq $country } @{$server->{allowed}->{countries}})
181     {
182         $match = "allowed";
183     }
184     elsif ($server->{allowed} &&
185            $server->{allowed}->{continents} &&
186            grep { $_ eq $continent } @{$server->{allowed}->{continents}})
187     {
188         $match = "allowed";
189     }
190     elsif ($server->{allowed})
191     {
192         $match = "none";
193     }
194     else
195     {
196         $match = "allowed";
197     }
198
199     return $match;
200 }
201
202 sub distance
203 {
204     my $lat1 = deg2rad(shift);
205     my $lon1 = deg2rad(shift);
206     my $lat2 = deg2rad(shift);
207     my $lon2 = deg2rad(shift);
208
209     return great_circle_distance($lon1, pip2 - $lat1, $lon2, pip2 - $lat2);
210 }