/
node.py
165 lines (139 loc) · 5.43 KB
/
node.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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import time #strftime()
import os #getcwd()
import socket #gethostname()
import sys
import logging
try:
from cloghandler import ConcurrentRotatingFileHandler
except:
pass
from xmlrpclib import ServerProxy, Fault
from os.path import join, isfile, abspath
from SimpleXMLRPCServer import SimpleXMLRPCServer
from urlparse import urlparse
MAX_HISTORY_LENGTH = 6
OK = 0
UNHANDLED = 100
ACCESS_DENIED = 200
SimpleXMLRPCServer.allow_reuse_address = 1
class UnhandledQuery(Fault):
def __init__(self, message="Couldn't handle the query"):
Fault.__init__(self, UNHANDLED, message)
class AccessDenied(Fault):
def __init__(self, message="Access denied"):
Fault.__init__(self, ACCESS_DENIED, message)
def inside(dir, name):
"""
Check whether a given file name lies within a given directory
Prevent case such as dir="/Shared/Owned/", name="../secret.file"
"""
dir = abspath(dir)
name = abspath(name)
return name.startswith(join(dir,''))
def getPort(url):
name = urlparse(url)[1]
parts = name.split(':')
return int(parts[-1])
class Node:
def __init__(self, url, dirname, secret):
log_level = logging.DEBUG
log_dir = os.getcwd()
log_max_size = 78 * 1024 * 1024
log_max_rotate = 9
LOG_LONG_FORMAT = '[%(module)s][%(funcName)s][%(lineno)d][%(levelname)s][%(message)s]'
LOG_RECORD_TIME = '[%(asctime)s]'
LOG_ROOT_NAME = 'server-node.log'
log = logging.getLogger()
log_format = LOG_RECORD_TIME + \
'[' + time.strftime("%Z", time.localtime()) + ']' + \
'[%(process)d]' + \
LOG_LONG_FORMAT
log_name = LOG_ROOT_NAME + '.' + str(socket.gethostname())
log_file = os.path.join(log_dir, log_name)
try:
rotate_handler = ConcurrentRotatingFileHandler(log_file, mode="a", maxBytes=log_max_size, backupCount=log_max_rotate)
log.addHandler(rotate_handler)
rotate_handler.setFormatter(formatter)
except:
logging.basicConfig(filename=log_file, level=logging.DEBUG, format='[%(asctime)s ][%(levelname)s][%(message)s]', datefmt='%m/%d/%Y %I:%M:%S %p')
pass
formatter = logging.Formatter(log_format)
log.setLevel(log_level)
logging.info('Logging level has been set to DEBUG mode')
logging.info('New node started with url <{}> serving directory <{}> with secret<{}>'.format(url, dirname, secret))
#log_file = "server-node.log"
#logging.basicConfig(filename=log_file, level=logging.DEBUG, format='[%(asctime)s ][%(levelname)s][%(message)s]', datefmt='%m/%d/%Y %I:%M:%S %p')
self.url = url
self.dirname = dirname
self.secret = secret
self.known = set()
def query(self, query, history=[]):
'''
Look for a file by asking neighbours, and return it as a string
'''
try:
logging.debug("Current query in function query() is {} with history {}".format(query, history))
return self._handle(query)
except UnhandledQuery:
history = history = [self.url]
if len(history) >= MAX_HISTORY_LENGTH:
raise
return self._broadcast(query, history)
def fetch(self, query, secret):
'''
If the secret is correct, perform a regular query and store
the file, a.k.a, make the Node find the file and download it.
'''
if secret != self.secret:
raise AccessDenied
result = self.query(query)
f = open(join(self.dirname, query), 'w')
f.write(result)
f.close()
return OK
def hello(self, other):
'''
Add the other Node as known peers
'''
self.known.add(other)
return OK
def _start(self):
s = SimpleXMLRPCServer(("", getPort(self.url)), logRequests=False)
s.register_instance(self)
s.serve_forever()
def _handle(self, query):
dir = self.dirname
name = join(dir, query)
logging.debug("Current dir is <{}> and final resolved name is <{}>".format(dir, name))
if not isfile(name):
logging.warning("<{}> is not a file".format(name))
raise UnhandledQuery
if not inside(dir, name):
logging.warning("<{}> is not inside <{}>".foramt(name, dir))
raise AccessDenied
return open(name).read()
def _broadcast(self, query, history):
for other in self.known.copy():
if other in history:
continue
try:
s = ServerProxy(other)
return s.query(query, history)
except Fault as f:
if f.faultCode == UNHANDLED:
pass
else:
self.known.remove(other)
except:
self.known.remove(other)
raise UnhandledQuery
def unittest():
n = Node('http://localhost:4242', 'test/peer2', 'secret1')
n.fetch('test.txt', 'secret1')
def main():
url, directory, secret = sys.argv[1:]
n = Node(url, directory, secret)
n._start()
if __name__ == '__main__':
#unittest()
main()