-
Notifications
You must be signed in to change notification settings - Fork 290
/
ExternalConfigServer.py
138 lines (107 loc) · 4.61 KB
/
ExternalConfigServer.py
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
""" ExternalConfigServer is a replacement for WSGITileServer that uses external
configuration fetched via HTTP to service all config requests.
Example usage, with gunicorn (http://gunicorn.org):
gunicorn --bind localhost:8888 "TileStache.Goodies.ExternalConfigServer:WSGIServer(url)"
"""
import logging
try:
from json import load as json_load
except ImportError:
from simplejson import load as json_load
from TileStache.py3_compat import urlopen
import TileStache
class DynamicLayers:
def __init__(self, config, url_root, cache_responses, dirpath):
self.config = config
self.url_root = url_root
self.dirpath = dirpath
self.cache_responses = cache_responses;
self.seen_layers = {}
self.lookup_failures = set()
def keys(self):
return self.seen_layers.keys()
def items(self):
return self.seen_layers.items()
def parse_layer(self, layer_json):
layer_dict = json_load(layer_json)
return TileStache.Config._parseConfigLayer(layer_dict, self.config, self.dirpath)
def __contains__(self, key):
# If caching is enabled and we've seen a request for this layer before, return True unless
# the prior lookup failed to find this layer.
if self.cache_responses:
if key in self.seen_layers:
return True
elif key in self.lookup_failures:
return False
res = urlopen(self.url_root + "/layer/" + key)
if self.cache_responses:
if res.getcode() != 200:
# Cache a failed lookup
self.lookup_failures.add(key)
else :
# If lookup succeeded and we are caching, parse the layer now so that a subsequent
# call to __getitem__ doesn't require a call to the config server. If we aren't
# caching, we skip this step to avoid an unnecessary json parse.
try:
self.seen_layers[key] = self.parse_layer(res)
except ValueError:
# The JSON received by the config server was invalid. Treat this layer as a
# failure. We don't want to raise ValueError from here because other parts
# of TileStache are just expecting a boolean response from __contains__
logging.error("Invalid JSON response seen for %s", key)
self.lookup_failures.add(key)
return False
if res.getcode() != 200:
logging.info("Config response code %s for %s", res.getcode(), key)
return res.getcode() == 200
def __getitem__(self, key):
if self.cache_responses:
if key in self.seen_layers:
return self.seen_layers[key]
elif key in self.lookup_failures:
# If we are caching, raise KnownUnknown if we have previously failed to find this layer
raise TileStache.KnownUnknown("Layer %s previously not found", key)
logging.debug("Requesting layer %s", self.url_root + "/layer/" + key)
res = urlopen(self.url_root + "/layer/" + key)
if (res.getcode() != 200) :
logging.info("Config response code %s for %s", res.getcode(), key)
if (self.cache_responses) :
self.lookup_failures.add(key)
raise TileStache.KnownUnknown("Layer %s not found", key)
try :
layer = self.parse_layer(res)
self.seen_layers[key] = layer
return layer
except ValueError:
logging.error("Invalid JSON response seen for %s", key)
if (self.cache_responses) :
# If caching responses, cache this failure
self.lookup_failures.add(key)
# KnownUnknown seems like the appropriate thing to raise here since this is akin
# to a missing configuration.
raise TileStache.KnownUnknown("Failed to parse JSON configuration for %s", key)
class ExternalConfiguration:
def __init__(self, url_root, cache_dict, cache_responses, dirpath):
self.cache = TileStache.Config._parseConfigCache(cache_dict, dirpath)
self.dirpath = dirpath
self.layers = DynamicLayers(self, url_root, cache_responses, dirpath)
class WSGIServer (TileStache.WSGITileServer):
"""
Wrap WSGI application, passing it a custom configuration.
The WSGI application is an instance of TileStache:WSGITileServer.
This method is initiated with a url_root that contains the scheme, host, port
and path that must prefix the API calls on our local server. Any valid http
or https urls should work.
The cache_responses parameter tells TileStache to cache all responses from
the configuration server.
"""
def __init__(self, url_root, cache_responses=True, debug_level="DEBUG"):
logging.basicConfig(level=debug_level)
# Call API server at url to grab cache_dict
cache_dict = json_load(urlopen(url_root + "/cache"))
dirpath = '/tmp/stache'
config = ExternalConfiguration(url_root, cache_dict, cache_responses, dirpath)
TileStache.WSGITileServer.__init__(self, config, False)
def __call__(self, environ, start_response):
response = TileStache.WSGITileServer.__call__(self, environ, start_response)
return response