1
# -*- coding: utf-8 -*-
4
# author: Hasanat Kazmi <hasanatkazmi@gmail.com>
5
# This script requires jsonrpc, install it from json-rpc.org/wiki/python-json-rpc
6
# written using python 2.5, should work on 2.6
10
import logging, logging.handlers
11
from ConfigParser import RawConfigParser
14
# Change this if application name changes!
15
#import applications.sahana.modules.Zeroconf
21
from jsonrpc import ServiceProxy, JSONRPCException
23
print 'JSONRPC module not available within the running Python - this needs installing for AutoSync!'
25
class LogManager(object):
26
""" Manages error logs """
27
def __init__(self, logLevel = logging.DEBUG):
28
self.logfolder = "synclogs"
29
self.logfilename = "log.out"
32
scriptfolder = sys.path[0]
33
if not os.path.isdir(os.path.join(scriptfolder, self.logfolder)):
34
os.mkdir(os.path.join(scriptfolder, self.logfolder)) # mkdir is only available for Windows & Unix
36
handler = logging.handlers.RotatingFileHandler(os.path.join(scriptfolder, self.logfolder, self.logfilename), maxBytes=128*1024*8)#128kb
38
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
39
handler.setFormatter(formatter)
41
self.handler = handler
43
def log(self, caller, severity, message):
45
caller is name of the class which is logging
46
severity is type of log entry like warning or error
47
message is log message
49
if severity not in [logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL]:
50
raise Exception("severity must be one of these: logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL")
51
my_logger = logging.getLogger(str(caller))
52
my_logger.setLevel(self.level)
53
my_logger.addHandler(self.handler)
54
my_logger.log(severity, message)
57
""" (not to confuse with zeroConf) It saves configurations for the system """
60
items['localhost'] = "127.0.0.1" # this should be dealt with different approach
61
items['localhost-port'] = 8000 # this too
62
items['servername'] = 'sahana'
65
def getConfFromServer(self, server):
66
"gets configurations by calling server (localhost)"
67
if not isinstance(server, RpcManager):
69
# get config from server
71
settings = server.execute('getconf')
73
serveritems['uuid'] = settings['uuid']
74
serveritems['webservice_port'] = settings['webservice_port']
76
zeroconfproperties = {}
77
zeroconfproperties['description'] = settings['zeroconf_description']
78
zeroconfproperties['uuid'] = settings['uuid']
79
zeroconfproperties['serverport'] = self.items['localhost-port']
80
zeroconfproperties['zeroconfig_port'] = settings['webservice_port']
81
serveritems['zeroconfproperties'] = zeroconfproperties
83
self.items.update(serveritems)
85
print "DEBUG: seems, server is down or you are not authenticated? are you being recognized as 127.0.0.1"
86
raise Exception("invalid settings called from server, or server may be down")
88
class DumpManager(object):
89
""" All data dump related operations are performed by this """
91
note that date can not be processed by pickle, so web services should not export date as date object (which it isnt right at the time to writting this code)
94
def __init__(self, logger=None):
96
default_dump_folder = "dumps"
97
self.default_dump_folder_path = os.path.join(sys.path[0], default_dump_folder)
99
if not os.path.isdir(self.default_dump_folder_path):
100
os.mkdir(self.default_dump_folder_path)
102
def __next(self): # this is enumerate function, but meeting our unique requirement
110
dirs = os.listdir(self.default_dump_folder_path)
111
dirs = [int(i) for i in dirs if os.path.isdir(os.path.join(self.default_dump_folder_path, i)) if isintfolder(i) ]
113
for i in range(len(dirs)+1):
119
def createdump(self, *data):
121
This function creates dumps and returns reference id
122
data are any python objects
124
folderref = self.__next()
125
folder = os.path.join(self.default_dump_folder_path, str(folderref))
127
for i in range(len(data)):
128
fileobj = file(os.path.join(folder, str(i)), "w")
129
pickle.dump(data[i], fileobj)
131
if not self.logger == None:
132
self.logger.log('DumpManager', logging.INFO, "created Dump in " + os.path.join(folder, str(i)))
136
def createobj(self, referenceid, deletedump = True):
137
"when referenceid is passed, all object related to that reference id are returned as list"
138
folder = os.path.join(self.default_dump_folder_path, str(referenceid))
147
files = [i for i in os.listdir(folder) if os.path.isfile(os.path.join(folder, i))]
152
raise Exception("referenceid must be an integer")
153
raise Exception("Invalid reference id, no such file")
155
toreturn = [pickle.load(file(os.path.join(folder, i), "r")) for i in files]
156
if not self.logger == None:
157
self.logger.log('DumpManager', logging.INFO, "read Dumps from " + folder)
160
shutil.rmtree(folder)
161
if not self.logger == None:
162
self.logger.log('DumpManager', logging.INFO, "deleted Dump folder: " + folder)
167
shutil.rmtree(self.default_dump_folder_path)
168
os.mkdir(self.default_dump_folder_path)
169
if not self.logger == None:
170
self.logger.log('DumpManager', logging.INFO, "cleared all dump: " + self.default_dump_folder_path)
172
class ZeroConfExpose(object):
173
""" exposes the service for zeroConf """
174
def __init__(self, conf, logger = None):
178
server = Zeroconf.Zeroconf()
179
# Get local IP address
180
local_ip = socket.gethostbyname(socket.gethostname())
181
local_ip = socket.inet_aton(local_ip)
183
svc1 = Zeroconf.ServiceInfo('_sahana._tcp.local.',
184
self.conf.items['uuid'] + '._sahana._tcp.local.',
186
port = self.conf.items['webservice_port'],
189
properties = self.conf.items['zeroconfproperties']
191
if not self.logger == None:
192
self.logger.log('ZeroConfExpose', logging.INFO, "exposed ZeroConf service")
193
server.registerService(svc1)
196
class ZeroConfSearch(object):
197
""" searches for other servers, if a server is found, gets its information, and puts in Server object. It passes server object to SyncManager """
199
class MyListener(object):
200
def __init__(self, conf, servermanager, localrpc, logger=None):
202
self.servermanager = servermanager
203
self.localrpc = localrpc
206
def removeService(self, server, type, name):
207
if not self.logger == None:
208
self.logger.log('ZeroConfSearch', logging.INFO, "Service "+ repr(name)+ " removed")
210
def addService(self, server, type, name):
211
if not self.logger == None:
212
self.logger.log('ZeroConfSearch', logging.INFO, "Service "+ repr(name)+ " added")
213
# Request more information about the service
214
info = server.getServiceInfo(type, name)
215
print "addService called"
217
"printing for debugging purposes"
218
# IP address is as unsigned short, network byte order
219
r = unpack('4c', info.getAddress())
220
r = [ord(i) for i in r]
221
r = reduce(lambda x, y: str(x)+"."+str(y) , r)
222
print "Found a server:"
224
print "port:", info.getPort()
225
print "properties: ", info.getProperties()
226
self.servermanager.addServer(r, info.getPort(), info.getProperties())
228
def __init__(self, conf, servermanager, localrpc, logger = None):
230
self.servermanager = servermanager
231
self.localrpc = localrpc
234
server = Zeroconf.Zeroconf()
235
listener = self.MyListener(self.conf, self.servermanager, self.localrpc, logger = self.logger)
236
browser = Zeroconf.ServiceBrowser(server, "_sahana._tcp.local.", listener)
238
class ServerManager(object):
239
""" gets server object as parameter, it calls local server json rpc service through RpcManager. When it gets data from rpcManager, it calls local server for data, if it gets error, it logs that in error log using errorLogManager or if it gets successful, it calls another dumpManager's instance and stores data and gets reference id, then foreign server's postdata through rpcManager """
240
def __init__(self, conf, dumpmanager, localrpc, logger=None):
242
self.localrpc = localrpc
244
self.dumpmanager = dumpmanager
245
self.starttime = None
248
def addServer(self, adress, port, properties):
249
"""server is URI of the server to be called"""
250
if adress is "127.0.0.1": #local server shouldn't be added
252
node.append( adress )
254
node.append( properties )
255
self.cue.append(node)
256
print "DEBUG: server " + adress + " added"
257
print "CUE" + str(self.cue)
261
""" it does all json transactions between servers """
263
# For each element in self.cue, call that server and exchange data with local server
264
self.starttime = int(time.time())
267
url = "http://"+i[0]+":" + str(i[1]) + i[2]['rpc_service_url']
268
foreignrpc = RpcManager(self.conf, self.logger, server = url)
269
foreignrpc.open_server()
271
cred = self.localrpc.execute("getAuthCred", i[2]['uuid'])
272
dataforeign = foreignrpc.execute("getdata", self.conf.uuid, cred[0], cred[1])
273
ref1 = self.dumpmanager.createdump(dataforeign)
274
self.localrpc.execute("putdata", "","", dataforeign)
276
datalocal = self.localrpc.execute("getdata", i[2]['uuid'], "", "")
277
ref2 = self.dumpmanager.createdump(datalocal)
278
foreignrpc.execute("putdata", cred[0], cred[1], datalocal)
279
self.dumpmanager.cleardump()
281
if not self.logger == None:
282
self.logger.log('ServerManager', logging.ERROR, "error occured while processing: " + str(e) )
284
#pausetime = 30 * 60 # 30 mins
285
pausetime = 1 * 60 # for debugging set to 1 mins
286
if int(time.time()) - self.starttime < pausetime:
287
time.sleep(pausetime - time.time() + self.starttime)
290
class RpcManager(object):
291
""" all RPC related activities are performed through this """
292
def __init__(self, conf, logger=None, server = None):
293
if not isinstance(conf, Config):
301
def open_server(self):
302
if self.server == None:
304
url = "http://" + self.conf.items['localhost'] + ":" + str(self.conf.items['localhost-port']) + "/" + self.conf.items['servername'] + "/sync/call/jsonrpc"
306
raise Exception("Configuration file seems to be corrupt. Delete it, system will regenerate new for you, you can edit it later on")
308
self.server = ServiceProxy(url)
310
raise Exception("Server proxy couldn't be resolved: " + url)
312
def execute(self, function, *args):
314
toreturn = eval('self.server.' + str(function))(*args)
315
message = "function " + str(function) + " sucessful with arguments: " + str(*args)
316
if not self.logger == None:
317
self.logger.log('RpcManager', logging.INFO, message)
319
message = "function " + str(function)+" failed with arguments: " + str(*args)
320
if not self.logger == None:
321
self.logger.log('RpcManager', logging.ERROR, message)
322
raise Exception(message)
327
""" Manages execution """
329
"if dumps folder has files in it, it means there were were partial sync attempts, that need to be solved"
330
logger = LogManager()
332
r = RpcManager(c, logger=logger) #this instance deals with local server
335
c.getConfFromServer(r)
338
print "Couldn't call local server. " + str(e)
339
# Try it after 10 sec
341
#print "Printing config settings from server"
343
d = DumpManager(logger = logger)
344
#ref = d.createdump(2,3,[1,2,3], "23")
346
sm = ServerManager(c, d, r, logger=logger)
348
SyncByIp(r, sm, logger, c)
349
#ze = ZeroConfExpose(c, logger=logger)
350
zs = ZeroConfSearch(c, sm, r, logger=logger)
351
sm.process() # this is non returning
353
class SyncByIp(object):
354
""" The class sync with IPs stored in database """
355
def __init__(self, localserver, serverManager, logger = LogManager(), c = Config()):
356
#r = RpcManager(c, logger=logger) #this instance deals with local server
357
content = localserver.execute('getAllServers')
360
if not i['ip'] is None and not i['ip'] is '':
362
ip = i['ip'][:i['ip'].find(':')]
363
port = i['ip'][i['ip'].find(':')+1:]
364
properties={'discription':'This IP was manually entered by user'}
365
serverManager.addServer(ip, port, properties)
367
if __name__ == '__main__':
b'\\ No newline at end of file'