import repository from arizona
[raven.git] / apps / nestproxy / stork_nest_proxy.py
1 #! /usr/bin/env python
2 """
3 <Program Name>
4    stork_nest_proxy.py
5
6 <Author>
7    Programmed by Scott Baker
8
9 <Purpose>
10 """
11
12 #           [option, long option,     variable,      action,        data,     default,                           metavar,     description]
13 """arizonaconfig
14    options=[
15             ["-C",   "--configfile",    "configfile",     "store",       "string", "/usr/local/stork/etc/stork.conf", "FILE",      "use a different config file (/usr/local/stork/etc/stork.conf is the default)"],
16             ["",     "--nestproxyhostname", "nestproxyhostname", "store", "string", "localhost",                      "HOSTNAME",  "bind to this host name (default localhost)"],
17             ["",     "--nestproxyport", "nestproxyport",  "store",       "int",    6648,                              "PORT",      "bind to this port (default 648)"],
18             ["",     "--retrievedir",   "retrievedir",    "store",       "string", "/usr/local/stork/var/proxy",      "DIRECTORY", "location to put retrieved files (default /usr/local/stork/var/packages)"],
19             ["",     "--not-daemon",    "daemon",         "store_false",  None,    True,                              None,        "specify that program should not attempt to detach from the terminal"]]
20    includes=[]
21 """
22
23 from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
24 from SocketServer import ForkingMixIn
25
26
27 import arizonaconfig
28 import arizonareport
29 import arizonacomm
30 import arizonacrypt
31 import arizonageneral
32 import arizonatransfer
33 import os
34 import stat
35 import glob
36 import sys
37 import tempfile
38 import traceback
39 import storkpackage
40 import socket
41 import storkerror
42 import time
43 import urlparse
44 import ravenlib.stats
45
46 glo_daemonized = False
47 glo_metafile_name="metafile"
48
49 def split_query_string(s):
50    if not s:
51       return {}
52
53    dict = {}
54    parts = s.strip().split("&")
55    for part in parts:
56       (name, val) = part.split("=",1)
57       dict[name] = val
58    return dict
59
60
61 def get_local_filename(host, filename, hash):
62    dest_dir = arizonaconfig.get_option("retrievedir")
63
64    arizonageneral.makedirs_existok(dest_dir)
65
66    if hash:
67        # If a hash is specified, then name the local file after the hash.
68        local_filename = os.path.join(dest_dir, hash)
69    else:
70        # If no hash is specified, then name the local file after the remote
71        # host and filename.
72
73        tmp = host + "/" + filename
74        tmp = tmp.replace("/", "_")
75        tmp = tmp.replace(":", "_")
76
77        # TODO: what happens if tmp > 255 characters long ?
78
79        local_filename = os.path.join(dest_dir, tmp)
80
81    return local_filename
82
83
84
85 def retrieve_file(dest_file, host, filename, hash):
86    file_dict = {}
87    file_dict["filename"] = filename
88    file_dict["hash"] = hash
89    file_dict['hashfuncs'] = [storkpackage.get_package_metadata_hash, arizonatransfer.default_hashfunc]
90
91    temp_dest_dir = tempfile.mkdtemp()
92    temp_dest_file = os.path.join(temp_dest_dir, filename)
93
94    print "getfiles:", host, file_dict, temp_dest_dir
95    (result, file_list) = arizonatransfer.getfiles1(host, (file_dict,), temp_dest_dir)
96    if result:
97       os.rename(temp_dest_file, dest_file)
98
99    if os.path.exists(temp_dest_file):
100       os.remove(temp_dest_file)
101    if os.path.exists(temp_dest_dir):
102       os.rmdir(temp_dest_dir)
103
104    return os.path.exists(dest_file)
105
106
107
108 def is_too_old(filename, max_age):
109    if not max_age:
110        return False
111
112    # checks the mtime of the file on disk against the current file
113
114    mtime = os.stat(filename)[stat.ST_MTIME]
115
116    age = time.time() - mtime
117    print filename, "age:", age, "max_age:", max_age
118
119    if age > int(max_age):
120        return True
121
122    return False
123
124
125 class Handler(BaseHTTPRequestHandler):
126
127     def do_GET(self):
128         print "GET:", self.path
129
130         ravenlib.stats.update("nestproxy_request")
131
132         parsed_path = urlparse.urlparse(self.path)
133
134         pathpart = parsed_path[2]
135         querypart = parsed_path[4]
136
137         attrs = split_query_string(querypart)
138         hash = attrs.get("hash", None)
139         max_age = attrs.get("max_age", None)
140
141         if (not hash) and (not max_age):
142             print "GET: no hash or max_age"
143             self.send_error(404, "no hash or max_age specified")
144             return
145
146         host = None
147         filename = None
148         urlparts = pathpart.lstrip("/").rsplit("/",1)
149         if len(urlparts) > 1:
150            host = urlparts[0]
151            filename = urlparts[1]
152
153         host = attrs.get("host", host)
154         filename = attrs.get("filename", filename)
155
156         print "GET: host:", host, "filename:", filename, "hash:", hash, "max_age:", max_age
157
158         local_filename = get_local_filename(host, filename, hash)
159
160         if os.path.exists(local_filename) and is_too_old(local_filename, max_age):
161            os.remove(local_filename)
162
163         if os.path.exists(local_filename):
164            ravenlib.stats.update("nestproxy_hit")
165         else:
166            ravenlib.stats.update("nestproxy_miss")
167            print "GET: retrieving file"
168            result = retrieve_file(local_filename, host, filename, hash)
169            if not result:
170               self.send_error(404, "failed to retrieve file")
171               return;
172
173         self.send_response(200)
174         self.send_header('Content-type', 'application/octet-stream')
175         self.end_headers()
176
177         print "GET: sending response"
178
179         f = open(local_filename)
180         while True:
181            data = f.read(8192)
182            if len(data) <= 0:
183               break
184            self.wfile.write(data)
185         f.close()
186
187         return
188
189 class ForkedHTTPServer(ForkingMixIn, HTTPServer):
190     """Handle requests in a separate process."""
191
192 ########################### MAIN ###############################
193 def main():
194    global glo_daemonized
195
196    # use error reporting tool
197    #storkerror.init_error_reporting("stork_nest_proxy.py")
198
199    # process command line and initialize variables
200    args = arizonaconfig.init_options('stork_nest_proxy.py', configfile_optvar='configfile') #, version=stork_nest_version.VERREL)
201
202    # if there are any transfer methods for nest or nestproxy, remove them
203    # this prevents recursion
204    methods = arizonaconfig.get_option("transfermethod")
205    if not methods:
206       arizonareport.send_error(0, "No arizonatransfer methods")
207       sys.exit(1)
208    if "nest" in methods:
209       methods.remove("nest")
210    if "nestproxy" in methods:
211       methods.remove("nestproxy")
212    arizonaconfig.set_option("transfermethod", methods)
213
214    # there should not be any leftover options
215    if args:
216       arizonareport.send_error(0, "Arguments not understood: " + str(args))
217       sys.exit(1)
218
219    # run as a daemon
220    if arizonaconfig.get_option("daemon"):
221       glo_daemonized = True
222       arizonageneral.make_daemon("stork_nest_proxy.py")
223
224    hostname = arizonaconfig.get_option("nestproxyhostname")
225    if hostname=="gethostname":
226        # if "gethostname" is specified as the hostname, then assume the user
227        # wants us to use the machines host name.
228        hostname = socket.gethostbyname(socket.gethostname())
229
230    port = arizonaconfig.get_option("nestproxyport")
231
232    # display ready message
233    if not glo_daemonized:
234       print "Ready for connections on", str(hostname) + ":" +str(port), "..."
235
236
237    server = ForkedHTTPServer((hostname, port), Handler)
238    server.serve_forever()
239
240
241
242
243
244
245 #-------------------------------------------------------------------------
246 # Start main
247 #-------------------------------------------------------------------------
248 if __name__ == "__main__":
249    main()