blob: 3a31cb64d180d10ed09dcd921583e32c6b0231d7 [file] [log] [blame]
Sapan Bhatia26d40bc2014-05-12 15:28:02 -04001import os
2import base64
3from datetime import datetime
Tony Mack49475672015-02-24 14:19:07 -05004
5from django.db.models import F, Q
6
Scott Baker76a840e2015-02-11 21:38:09 -08007from xos.config import Config
Scott Bakerf154cc22016-01-14 16:07:32 -08008from xos.logger import Logger, logging
Sapan Bhatia003e84c2016-01-15 11:05:52 -05009from synchronizers.base.steps import *
Sapan Bhatia26d40bc2014-05-12 15:28:02 -040010
11logger = Logger(level=logging.INFO)
12
13class FailedDependency(Exception):
14 pass
15
16class SyncStep:
Scott Baker286a78f2015-02-18 16:13:48 -080017 """ A XOS Sync step.
Sapan Bhatia26d40bc2014-05-12 15:28:02 -040018
19 Attributes:
20 psmodel Model name the step synchronizes
21 dependencies list of names of models that must be synchronized first if the current model depends on them
22 """
23 slow=False
Tony Mackfbdae1b2015-02-24 14:16:43 -050024 def get_prop(self, prop):
Sapan Bhatia26d40bc2014-05-12 15:28:02 -040025 try:
26 sync_config_dir = Config().sync_config_dir
27 except:
Scott Bakerb8059c92015-02-19 22:25:49 -080028 sync_config_dir = '/etc/xos/sync' # XXX TODO: update path
Sapan Bhatia26d40bc2014-05-12 15:28:02 -040029 prop_config_path = '/'.join(sync_config_dir,self.name,prop)
30 return open(prop_config_path).read().rstrip()
31
32 def __init__(self, **args):
33 """Initialize a sync step
34 Keyword arguments:
35 name -- Name of the step
Scott Baker286a78f2015-02-18 16:13:48 -080036 provides -- XOS models sync'd by this step
Sapan Bhatia26d40bc2014-05-12 15:28:02 -040037 """
38 dependencies = []
39 self.driver = args.get('driver')
40 self.error_map = args.get('error_map')
41
42 try:
43 self.soft_deadline = int(self.get_prop('soft_deadline_seconds'))
44 except:
45 self.soft_deadline = 5 # 5 seconds
46
47 return
48
49 def fetch_pending(self, deletion=False):
Sapan Bhatia3e328352014-07-23 10:03:50 -040050 # This is the most common implementation of fetch_pending
51 # Steps should override it if they have their own logic
52 # for figuring out what objects are outstanding.
53 main_obj = self.provides[0]
Tony Mackfbdae1b2015-02-24 14:16:43 -050054 if (not deletion):
Sapan Bhatia3e328352014-07-23 10:03:50 -040055 objs = main_obj.objects.filter(Q(enacted__lt=F('updated')) | Q(enacted=None))
56 else:
57 objs = main_obj.deleted_objects.all()
58
59 return objs
Tony Mack3de59e32015-08-19 11:58:18 -040060 #return Instance.objects.filter(ip=None)
Sapan Bhatia26d40bc2014-05-12 15:28:02 -040061
62 def check_dependencies(self, obj, failed):
63 for dep in self.dependencies:
Sapan Bhatia3e328352014-07-23 10:03:50 -040064 peer_name = dep[0].lower() + dep[1:] # django names are camelCased with the first letter lower
65 peer_object = getattr(obj, peer_name)
Sapan Bhatia95470622014-08-04 10:48:28 -040066
67 # peer_object can be None, and if so there
68 # is no object-level dependency
69 if (peer_object and peer_object.pk==failed.pk):
Sapan Bhatia26d40bc2014-05-12 15:28:02 -040070 raise FailedDependency
71
72 def call(self, failed=[], deletion=False):
73 pending = self.fetch_pending(deletion)
74 for o in pending:
75 try:
76 for f in failed:
77 self.check_dependencies(o,f) # Raises exception if failed
78 if (deletion):
79 self.delete_record(o)
80 o.delete(purge=True)
81 else:
82 self.sync_record(o)
83 o.enacted = datetime.now() # Is this the same timezone? XXX
84 o.backend_status = "OK"
85 o.save(update_fields=['enacted'])
86 except Exception,e:
87 try:
88 o.backend_status = self.error_map.map(str(e))
89 except:
90 o.backend_status = str(e)
91
Sapan Bhatiaf1d3d272014-08-18 02:24:22 -040092 if (o.pk):
93 o.save(update_fields=['backend_status'])
Sapan Bhatia26d40bc2014-05-12 15:28:02 -040094
Sapan Bhatia8dddf662016-04-06 19:04:05 +020095 logger.log_exc("sync step failed!",extra=o.tologdict())
Sapan Bhatia26d40bc2014-05-12 15:28:02 -040096 failed.append(o)
97
98 return failed
99
100 def __call__(self, **args):
101 return self.call(**args)