The network of Atlas probes managed by the
Two warnings before you read further away: the UDM are not
available for the general public. You need an account at RIPE-NCC and
some
First, let's create in
url = "https://atlas.ripe.net/api/v1/measurement/?key=%s" % key
request = urllib2.Request(url)
request.add_header("Content-Type", "application/json")
request.add_header("Accept", "application/json")
The two
data = { "definitions": [
{ "target": "www.bortzmeyer.org", "description": "Ping my blog",
"type": "ping", "af": 6, "is_oneoff": True} ],
"probes": [
{ "requested": 5, "type": "area", "value": "WW" } ] }
And let's change it at each iteration:
for area in ["WW", "West", "North-East", "South-East", "North-Central", "South-Central"]:
data["probes"][0]["value"] = area
And start the measurement with an HTTP
conn = urllib2.urlopen(request, json.dumps(data))
Atlas will send us back a JSON object giving, not the actual results,
but the ID of this measurement. We will have to retrieve it later,
through the Web interface or via the API, as explained in the next
paragraphs. But, for the time being, let's just display the
measurement ID. This requires parsing the JSON code:
results = json.load(conn)
print("%s: measurement #%s" % (area, results["measurements"]))
And that's all. The program will display:
% python ping.py
WW: measurement #[1007970]
West: measurement #[1007971]
North-East: measurement #[1007972]
South-East: measurement #[1007973]
North-Central: measurement #[1007974]
South-Central: measurement #[1007976]
There are two things I did not explain here: the error handling and
the API key. To create a measurement, you need an API key, which you
get from the Web interface. In my case, I store it in
The above script did only half of the work. It creates a
measurement but does not retrieve and parse it. Let's now do
that. Measurements can take a long time (in the previous example,
because of the parameter
0: Specified
1: Scheduled
2: Ongoing
4: Stopped
5: Forced to stop
6: No suitable probes
7: Failed
8: Archived
Now, Let's poll:
over = False
while not over:
request = urllib2.Request("%s/%i/?key=%s" % (url, measure, key))
request.add_header("Accept", "application/json")
conn = urllib2.urlopen(request)
results = json.load(conn)
status = results["status"]["name"]
if status == "Ongoing" or status == "Specified":
print("Not yet ready, sleeping...")
time.sleep(60)
elif status == "Stopped":
over = True
else:
print("Unknown status \"%s\"\n" % status)
time.sleep(120)
So, for measurement 1007970, we retrieve
request = urllib2.Request("%s/%i/result/?key=%s" % (url, measure, key))
request.add_header("Accept", "application/json")
conn = urllib2.urlopen(request)
results = json.load(conn)
total_rtt = 0
num_rtt = 0
num_error = 0
for result in results:
...
Here, we will do only a trivial computation, finding the average
for result in results:
for test in result["result"]:
if test.has_key("rtt"):
total_rtt += int(test["rtt"])
num_rtt += 1
elif test.has_key("error"):
num_error += 1
else:
raise Exception("Result has no field rtt and not field error")
...
print("%i successful tests, %i errors, average RTT: %i" % (num_rtt, num_error, total_rtt/num_rtt))
And that's all, we have a result:
Measurement #1007980, please wait the result (it may be long)
...
12 successful tests, 0 errors, average RTT: 66
The entire script is
Now, let's try with a different type of measurements, on the
And its execution also:
response, err := client.Do(req)
body, err := ioutil.ReadAll(response.Body)
But I skipped one step: what is in the
DATA string = "{ \"definitions\": [ { \"target\":
\"d.nic.fr\", \"query_argument\": \"fr\",
\"query_class\": \"IN\", \"query_type\": \"SOA\",
\"description\": \"DNS AFNIC\", \"type\": \"dns\",
\"af\": 6, \"is_oneoff\": \"True\"} ], \"probes\":
[ { \"requested\": 5, \"type\": \"area\", \"value\": \"WW\" } ] }"
)
Now, we just have to parse the JSON content sent back with the
standard package encoding/json. Go is a typed
language and, by default, type is checked before the program is
executed. In the REST/JSON world, we do not always know the complete
structure of the JSON object. So we just declare the resulting object
as
In the code above, the type assertion is between parenthesis adter the
dot: we assert that the
status := mapObject["status"].(map[string]interface{})["name"].(string)
if status == "Ongoing" || status == "Specified" {
fmt.Printf("Not yet ready, be patient...\n")
time.Sleep(60 * time.Second)
} else if status == "Stopped" {
over = true
} else {
fmt.Printf("Unknown status %s\n", status)
time.Sleep(90 * time.Second)
}
There was two other type assertions above, one to say that
And it displays:
Measurement #1008096
...
4 successes, 0 failures, average RTT 53.564
And this is the (temporary) end. The Go program is
Thanks to Daniel Quinn and IƱigo Ortiz de Urbina for their help and tricks and patience. A good tutorial on running UDM and analyzing their results is Hands-on: RIPE Atlas by Nikolay Melnikov.