def newfunc(*args, **kwargs): result = None attempt = 0 # setup the caller object iKwargs = {"debug": debug, "fd": fd, "verbose": verbose, "defaults": defaults, "trap": trap} caller = _InspectorGadget(func, **iKwargs) while attempt <= maximum: try: # initial caller setup result = caller(*args, **kwargs) if isinstance(result, defer.Deferred): return _deferred(result, caller) break # reset data except SystemExit: result = Failure() break except KeyboardInterrupt: result = Failure() break except: attempt += 1 if attempt > maximum: result = Failure() break time.sleep(delay) caller.write(">>> Retry attempt %d" % attempt) if isinstance(result, Failure): result.raiseException() return result
def migrateOneHome(self, fileTxn, homeType, fileHome): """ Migrate an individual calendar or addressbook home. """ migrateFunc, destFunc = homeTypeLookup.get(homeType) uid = normalizeUUIDOrNot(fileHome.uid()) self.log.warn("Starting migration transaction %s UID %r" % (homeType, uid)) sqlTxn = self.sqlStore.newTransaction( label="UpgradeToDatabaseStep.migrateOneHome") homeGetter = destFunc(sqlTxn) sqlHome = yield homeGetter(uid, create=False) if sqlHome is not None and not self.merge: self.log.warn("%s home %r already existed not migrating" % (homeType, uid)) yield sqlTxn.abort() returnValue(None) try: if sqlHome is None: sqlHome = yield homeGetter(uid, create=True) yield migrateFunc(fileHome, sqlHome, merge=self.merge) except: f = Failure() yield sqlTxn.abort() f.raiseException() else: yield sqlTxn.commit() # Remove file home after migration. FIXME: instead, this should be a # public remove...HomeWithUID() API for de-provisioning. (If we had # this, this would simply be a store-to-store migrator rather than a # filesystem-to-database upgrade.) fileHome._path.remove()
def migrateOneHome(self, fileTxn, homeType, fileHome): """ Migrate an individual calendar or addressbook home. """ migrateFunc, destFunc = homeTypeLookup.get(homeType) uid = normalizeUUIDOrNot(fileHome.uid()) self.log.warn("Starting migration transaction %s UID %r" % (homeType, uid)) sqlTxn = self.sqlStore.newTransaction() homeGetter = destFunc(sqlTxn) sqlHome = yield homeGetter(uid, create=False) if sqlHome is not None and not self.merge: self.log.warn( "%s home %r already existed not migrating" % ( homeType, uid)) yield sqlTxn.abort() returnValue(None) try: if sqlHome is None: sqlHome = yield homeGetter(uid, create=True) yield migrateFunc(fileHome, sqlHome, merge=self.merge) except: f = Failure() yield sqlTxn.abort() f.raiseException() else: yield sqlTxn.commit() # Remove file home after migration. FIXME: instead, this should be a # public remove...HomeWithUID() API for de-provisioning. (If we had # this, this would simply be a store-to-store migrator rather than a # filesystem-to-database upgrade.) fileHome._path.remove()
def http_POST(self, request): """ The server-to-server POST method. """ # Need a transaction to work with txn = transactionFromRequest(request, self._newStore) # This is a server-to-server scheduling operation. scheduler = IScheduleScheduler(txn, None, podding=self._podding) originator = self.loadOriginatorFromRequestHeaders(request) recipients = self.loadRecipientsFromRequestHeaders(request) body = (yield allDataFromStream(request.stream)) # Do the POST processing treating this as a non-local schedule try: result = (yield scheduler.doSchedulingViaPOST(request.remoteAddr, request.headers, body, originator, recipients)) except Exception: ex = Failure() yield txn.abort() ex.raiseException() else: yield txn.commit() response = result.response() if not self._podding: response.headers.addRawHeader(ISCHEDULE_CAPABILITIES, str(config.Scheduling.iSchedule.SerialNumber)) returnValue(response)
def newfunc(*args, **kwargs): result = None attempt = 0 #setup the caller object iKwargs = { 'debug': debug, 'fd': fd, 'verbose': verbose, 'defaults': defaults, 'trap': trap } caller = _InspectorGadget(func, **iKwargs) while attempt <= maximum: try: #initial caller setup result = caller(*args, **kwargs) if isinstance(result, defer.Deferred): return _deferred(result, caller) break #reset data except SystemExit: result = Failure() break except KeyboardInterrupt: result = Failure() break except: attempt += 1 if attempt > maximum: result = Failure() break time.sleep(delay) caller.write('>>> Retry attempt %d' % attempt) if isinstance(result, Failure): result.raiseException() return result
def doToEachHomeNotAtVersion(store, homeSchema, version, doIt, logStr, filterOwnerUID=None, processExternal=False): """ Do something to each home whose version column indicates it is older than the specified version. Do this in batches as there may be a lot of work to do. Also, allow the GUID to be filtered to support a parallel mode of operation. """ txn = store.newTransaction("updateDataVersion") where = homeSchema.DATAVERSION < version if filterOwnerUID: where = where.And(homeSchema.OWNER_UID.StartsWith(filterOwnerUID)) total = (yield Select( [Count(homeSchema.RESOURCE_ID), ], From=homeSchema, Where=where, ).on(txn))[0][0] yield txn.commit() count = 0 while True: logUpgradeStatus(logStr, count, total) # Get the next home with an old version txn = store.newTransaction("updateDataVersion") try: rows = yield Select( [homeSchema.RESOURCE_ID, homeSchema.OWNER_UID, homeSchema.STATUS, ], From=homeSchema, Where=where, OrderBy=homeSchema.OWNER_UID, Limit=1, ).on(txn) if len(rows) == 0: yield txn.commit() logUpgradeStatus("End {}".format(logStr), count, total) returnValue(None) # Apply to the home if not external homeResourceID, _ignore_owner_uid, homeStatus = rows[0] if homeStatus != _HOME_STATUS_EXTERNAL or processExternal: yield doIt(txn, homeResourceID) # Update the home to the current version yield Update( {homeSchema.DATAVERSION: version}, Where=homeSchema.RESOURCE_ID == homeResourceID, ).on(txn) yield txn.commit() except RuntimeError, e: f = Failure() logUpgradeError( logStr, "Failed to upgrade {} to {}: {}".format(homeSchema, version, e) ) yield txn.abort() f.raiseException() count += 1
def moveCalendarTimezoneProperties(sqlStore): """ Need to move all the CalDAV:calendar-timezone properties in the RESOURCE_PROPERTY table to the new CALENDAR_BIND table columns, extracting the new value from the XML property. """ cb = schema.CALENDAR_BIND rp = schema.RESOURCE_PROPERTY try: calendars_for_id = {} while True: sqlTxn = sqlStore.newTransaction() rows = (yield rowsForProperty(sqlTxn, caldavxml.CalendarTimeZone, with_uid=True, batch=BATCH_SIZE)) if len(rows) == 0: yield sqlTxn.commit() break delete_ids = [] for calendar_rid, value, viewer in rows: delete_ids.append(calendar_rid) if calendar_rid not in calendars_for_id: ids = yield Select( [cb.CALENDAR_HOME_RESOURCE_ID, cb.BIND_MODE, ], From=cb, Where=cb.CALENDAR_RESOURCE_ID == calendar_rid, ).on(sqlTxn) calendars_for_id[calendar_rid] = ids if viewer: calendarHome = (yield sqlTxn.calendarHomeWithUID(viewer)) else: calendarHome = None for row in calendars_for_id[calendar_rid]: home_id, bind_mode = row if bind_mode == _BIND_MODE_OWN: calendarHome = (yield sqlTxn.calendarHomeWithResourceID(home_id)) break if calendarHome is not None: prop = WebDAVDocument.fromString(value).root_element calendar = (yield calendarHome.childWithID(calendar_rid)) if calendar is not None: yield calendar.setTimezone(prop.calendar()) # Always delete the rows so that batch processing works correctly yield Delete( From=rp, Where=(rp.RESOURCE_ID.In(Parameter("ids", len(delete_ids)))).And (rp.NAME == PropertyName.fromElement(caldavxml.CalendarTimeZone).toString()), ).on(sqlTxn, ids=delete_ids) yield sqlTxn.commit() yield cleanPropertyStore() except RuntimeError: f = Failure() yield sqlTxn.abort() f.raiseException()
def handle_errors(self, failure: Failure) -> None: cleaned_traceback = self.clean_traceback(failure) self.log.warn( f"Unhandled error during operator bonded check: {cleaned_traceback}" ) if failure.check([self.OperatorNoLongerBonded]): # this type of exception we want to propagate because we will shut down failure.raiseException()
def http_POST(self, request): """ The server-to-server POST method. """ # Need a transaction to work with txn = transactionFromRequest(request, self._newStore) # Log extended item if not hasattr(request, "extendedLogItems"): request.extendedLogItems = {} # This is a server-to-server scheduling operation. scheduler = IScheduleScheduler(txn, None, logItems=request.extendedLogItems, podding=self._podding) # Check content first contentType = request.headers.getHeader("content-type") format = self.determineType(contentType) if format is None: msg = "MIME type {} not allowed in iSchedule request".format( contentType, ) self.log.error(msg) raise HTTPError( scheduler.errorResponse( responsecode.FORBIDDEN, (ischedule_namespace, "invalid-calendar-data-type"), msg, )) originator = self.loadOriginatorFromRequestHeaders(request) recipients = self.loadRecipientsFromRequestHeaders(request) body = (yield allDataFromStream(request.stream)) calendar = Component.fromString(body, format=format) # Do the POST processing treating this as a non-local schedule try: result = (yield scheduler.doSchedulingViaPOST(request.remoteAddr, request.headers, body, calendar, originator, recipients)) except Exception: ex = Failure() yield txn.abort() ex.raiseException() else: yield txn.commit() response = result.response(format=format) if not self._podding: response.headers.addRawHeader( ISCHEDULE_CAPABILITIES, str(config.Scheduling.iSchedule.SerialNumber)) returnValue(response)
def getVersions(self): """ Extract the expected version from the database schema and get the actual version in the current database, along with the DB dialect. """ # Retrieve the version number from the schema file current_schema = self.schemaLocation.child("current.sql").getContent() found = re.search( "insert into CALENDARSERVER values \('%s', '(\d+)'\);" % (self.versionKey, ), current_schema) if found is None: msg = "Schema is missing required database key %s insert statement: %s" % ( self.versionKey, current_schema, ) self.log.error(msg) raise RuntimeError(msg) else: required_version = int(found.group(1)) self.log.warn("Required database key %s: %s." % ( self.versionKey, required_version, )) # Get the schema version in the current database sqlTxn = self.sqlStore.newTransaction( label="UpgradeDatabaseCoreStep.getVersions") dialect = sqlTxn.dialect try: actual_version = yield sqlTxn.calendarserverValue(self.versionKey) actual_version = int(actual_version) yield sqlTxn.commit() except (RuntimeError, ValueError): f = Failure() self.log.error("Database key %s cannot be determined." % (self.versionKey, )) yield sqlTxn.abort() if self.defaultKeyValue is None: f.raiseException() else: actual_version = self.defaultKeyValue self.log.warn("Actual database key %s: %s." % ( self.versionKey, actual_version, )) returnValue(( dialect, required_version, actual_version, ))
def moveCalendarAvailabilityProperties(sqlStore): """ Need to move all the CS:calendar-availability properties in the RESOURCE_PROPERTY table to the new CALENDAR_BIND table columns, extracting the new value from the XML property. """ cb = schema.CALENDAR_BIND rp = schema.RESOURCE_PROPERTY try: while True: sqlTxn = sqlStore.newTransaction() rows = (yield rowsForProperty(sqlTxn, customxml.CalendarAvailability, batch=BATCH_SIZE)) if len(rows) == 0: yield sqlTxn.commit() break # Map each calendar to a home id using a single query for efficiency calendar_ids = [row[0] for row in rows] home_map = yield Select( [cb.CALENDAR_RESOURCE_ID, cb.CALENDAR_HOME_RESOURCE_ID, ], From=cb, Where=(cb.CALENDAR_RESOURCE_ID.In(Parameter("ids", len(calendar_ids)))).And(cb.BIND_MODE == _BIND_MODE_OWN), ).on(sqlTxn, ids=calendar_ids) calendar_to_home = dict(home_map) # Move property to each home for calendar_rid, value in rows: if calendar_rid in calendar_to_home: calendarHome = (yield sqlTxn.calendarHomeWithResourceID(calendar_to_home[calendar_rid])) if calendarHome is not None: prop = WebDAVDocument.fromString(value).root_element yield calendarHome.setAvailability(prop.calendar()) # Always delete the rows so that batch processing works correctly yield Delete( From=rp, Where=(rp.RESOURCE_ID.In(Parameter("ids", len(calendar_ids)))).And (rp.NAME == PropertyName.fromElement(customxml.CalendarAvailability).toString()), ).on(sqlTxn, ids=calendar_ids) yield sqlTxn.commit() yield cleanPropertyStore() except RuntimeError: f = Failure() yield sqlTxn.abort() f.raiseException()
def applyUpgrade(self, fp): """ Apply the schema upgrade .sql file to the database. """ self.log.warn("Applying schema upgrade: %s" % (fp.basename(),)) sqlTxn = self.sqlStore.newTransaction() try: sql = fp.getContent() yield sqlTxn.execSQLBlock(sql) yield sqlTxn.commit() except RuntimeError: f = Failure() yield sqlTxn.abort() f.raiseException()
def applyUpgrade(self, fp): """ Apply the schema upgrade .sql file to the database. """ self.log.warn("Applying schema upgrade: %s" % (fp.basename(), )) sqlTxn = self.sqlStore.newTransaction( label="UpgradeDatabaseSchemaStep.applyUpgrade") try: sql = fp.getContent() yield sqlTxn.execSQLBlock(sql) yield sqlTxn.commit() except RuntimeError: f = Failure() yield sqlTxn.abort() f.raiseException()
def startService(self): """Start All AppManager Services""" if self.scanning.called: #need to pre-populate values self.scanning = defer.maybeDeferred(self._first_scan) self.first_run = True self._task = task.LoopingCall(self.scan_app_instances) #plugins will be created and loaded when needed for shortname in config.APPLICATIONS.keys(): manager = None try: applog = logWithContext(type=shortname, route=SERVICENAME) applog('Loading Application Plugin') applog('Creating Application Manager') manager = AppManager(shortname) manager.parentService = self #check and see if the model is bound if not AppManager(shortname).running: applog('Starting Application Manager') manager.start() except: failure = Failure() #bad plugin, not adaptable failures = (InvalidPlugin, TypeError) if failure.check(*failures) and manager: log('plugin for %s is invalid' % (manager.name, )) manager.action.__class__.delete(manager.action) try: pluginFactory.delete_plugin(manager.model) except: pass #silence AppManager.delete(manager) if not config.EXCESSIVE_LOGGING: continue #avoid extra logging try: failure.raiseException() except: crashReport('ApplicationLoader', self) Service.startService(self) Event('instance-started').subscribe(self.reset_tracking) #wire allapps action into the server drone.builtins.update({ 'allapps': self.allapps_action, 'applist': self.applist_action, }) #delay scanning by some interval config.reactor.callLater(SERVICECONFIG.initial_delay, self._start_all_tasks)
def http_POST(self, request): """ The server-to-server POST method. """ # Need a transaction to work with txn = transactionFromRequest(request, self._newStore) # This is a server-to-server scheduling operation. scheduler = IScheduleScheduler(txn, None, podding=self._podding) # Check content first contentType = request.headers.getHeader("content-type") format = self.determineType(contentType) if format is None: msg = "MIME type {} not allowed in iSchedule request".format(contentType,) self.log.error(msg) raise HTTPError(scheduler.errorResponse( responsecode.FORBIDDEN, (ischedule_namespace, "invalid-calendar-data-type"), msg, )) originator = self.loadOriginatorFromRequestHeaders(request) recipients = self.loadRecipientsFromRequestHeaders(request) body = (yield allDataFromStream(request.stream)) calendar = Component.fromString(body, format=format) # Do the POST processing treating this as a non-local schedule try: result = (yield scheduler.doSchedulingViaPOST(request.remoteAddr, request.headers, body, calendar, originator, recipients)) except Exception: ex = Failure() yield txn.abort() ex.raiseException() else: yield txn.commit() response = result.response(format=format) if not self._podding: response.headers.addRawHeader(ISCHEDULE_CAPABILITIES, str(config.Scheduling.iSchedule.SerialNumber)) returnValue(response)
def startService(self): """Start All AppManager Services""" if self.scanning.called: #need to pre-populate values self.scanning = defer.maybeDeferred(self._first_scan) self.first_run = True self._task = task.LoopingCall(self.scan_app_instances) #plugins will be created and loaded when needed for shortname in config.APPLICATIONS.keys(): manager = None try: applog = logWithContext(type=shortname,route=SERVICENAME) applog('Loading Application Plugin') applog('Creating Application Manager') manager = AppManager(shortname) manager.parentService = self #check and see if the model is bound if not AppManager(shortname).running: applog('Starting Application Manager') manager.start() except: failure = Failure() #bad plugin, not adaptable failures = (InvalidPlugin, TypeError) if failure.check(*failures) and manager: log('plugin for %s is invalid' % (manager.name,)) manager.action.__class__.delete(manager.action) try: pluginFactory.delete_plugin(manager.model) except: pass #silence AppManager.delete(manager) if not config.EXCESSIVE_LOGGING: continue #avoid extra logging try: failure.raiseException() except: crashReport('ApplicationLoader', self) Service.startService(self) Event('instance-started').subscribe(self.reset_tracking) #wire allapps action into the server drone.builtins.update({ 'allapps': self.allapps_action, 'applist': self.applist_action, }) #delay scanning by some interval config.reactor.callLater(SERVICECONFIG.initial_delay, self._start_all_tasks)
def applyUpgrade(self, fp): """ Apply the schema upgrade .sql file to the database. """ self.log.warn("Applying schema upgrade: {path}", path=fp.basename()) sqlTxn = self.sqlStore.newTransaction(label="UpgradeDatabaseSchemaStep.applyUpgrade") try: sql = fp.getContent() yield sqlTxn.execSQLBlock(sql) yield sqlTxn.commit() except (RuntimeError, StandardError) as e: if hasattr(e, "stmt"): self.log.error("Apply upgrade failed for '{basename}' on statement: {stmt}\n{err}".format( basename=fp.basename(), stmt=e.stmt, err=e, )) f = Failure() yield sqlTxn.abort() f.raiseException()
def migrateOneHome(self, fileTxn, homeType, fileHome): """ Migrate an individual calendar or addressbook home. """ migrateFunc, destFunc = homeTypeLookup.get(homeType) uid = normalizeUUIDOrNot(fileHome.uid()) self.log.warn("Starting migration transaction {type} UID {uid}", type=homeType, uid=uid) sqlTxn = self.sqlStore.newTransaction( label="UpgradeToDatabaseStep.migrateOneHome") homeGetter = destFunc(sqlTxn) sqlHome = yield homeGetter(uid, create=False) if sqlHome is not None and not self.merge: self.log.warn("{ty[e} home {uid} already existed not migrating", type=homeType, uid=uid) yield sqlTxn.abort() returnValue(None) try: if sqlHome is None: try: sqlHome = yield homeGetter(uid, create=True) except DirectoryRecordNotFoundError: # The directory record does not exist; skip this home self.log.warn( "Skipping migration of {uid} because it's missing from the directory service", uid=uid) returnValue(None) yield migrateFunc(fileHome, sqlHome, merge=self.merge) except: f = Failure() yield sqlTxn.abort() f.raiseException() else: yield sqlTxn.commit() # Remove file home after migration. FIXME: instead, this should be a # public remove...HomeWithUID() API for de-provisioning. (If we had # this, this would simply be a store-to-store migrator rather than a # filesystem-to-database upgrade.) fileHome._path.remove()
def getVersions(self): """ Extract the expected version from the database schema and get the actual version in the current database, along with the DB dialect. """ # Retrieve the version number from the schema file current_schema = self.schemaLocation.child("current.sql").getContent() found = re.search("insert into CALENDARSERVER values \('%s', '(\d+)'\);" % (self.versionKey,), current_schema) if found is None: msg = "Schema is missing required database key %s insert statement: %s" % (self.versionKey, current_schema,) self.log.error(msg) raise RuntimeError(msg) else: required_version = int(found.group(1)) self.log.warn("Required database key %s: %s." % (self.versionKey, required_version,)) # Get the schema version in the current database sqlTxn = self.sqlStore.newTransaction() dialect = sqlTxn.dialect try: actual_version = yield sqlTxn.calendarserverValue(self.versionKey) actual_version = int(actual_version) yield sqlTxn.commit() except (RuntimeError, ValueError): f = Failure() self.log.error("Database key %s cannot be determined." % (self.versionKey,)) yield sqlTxn.abort() if self.defaultKeyValue is None: f.raiseException() else: actual_version = self.defaultKeyValue self.log.warn("Actual database key %s: %s." % (self.versionKey, actual_version,)) returnValue((dialect, required_version, actual_version,))
def _processDefaultCalendarProperty(sqlStore, propname, colname): """ Move the specified property value to the matching CALENDAR_HOME_METADATA table column. Since the number of calendar homes may well be large, we need to do this in batches. """ cb = schema.CALENDAR_BIND rp = schema.RESOURCE_PROPERTY try: while True: sqlTxn = sqlStore.newTransaction() rows = (yield rowsForProperty(sqlTxn, propname, batch=BATCH_SIZE)) if len(rows) == 0: yield sqlTxn.commit() break delete_ids = [] for inbox_rid, value in rows: delete_ids.append(inbox_rid) ids = yield Select( [cb.CALENDAR_HOME_RESOURCE_ID, ], From=cb, Where=cb.CALENDAR_RESOURCE_ID == inbox_rid, ).on(sqlTxn) if len(ids) > 0: calendarHome = (yield sqlTxn.calendarHomeWithResourceID(ids[0][0])) if calendarHome is not None: prop = WebDAVDocument.fromString(value).root_element defaultCalendar = str(prop.children[0]) parts = defaultCalendar.split("/") if len(parts) == 5: calendarName = parts[-1] calendarHomeUID = parts[-2] expectedHome = (yield sqlTxn.calendarHomeWithUID(calendarHomeUID)) if expectedHome is not None and expectedHome.id() == calendarHome.id(): calendar = (yield calendarHome.calendarWithName(calendarName)) if calendar is not None: yield calendarHome.setDefaultCalendar( calendar, tasks=(propname == customxml.ScheduleDefaultTasksURL) ) # Always delete the rows so that batch processing works correctly yield Delete( From=rp, Where=(rp.RESOURCE_ID.In(Parameter("ids", len(delete_ids)))).And (rp.NAME == PropertyName.fromElement(propname).toString()), ).on(sqlTxn, ids=delete_ids) yield sqlTxn.commit() yield cleanPropertyStore() except RuntimeError: f = Failure() yield sqlTxn.abort() f.raiseException()
def _processDefaultAlarmProperty(sqlStore, propname, vevent, timed): """ Move the specified property value to the matching CALENDAR_HOME_METADATA or CALENDAR_BIND table column. Since the number of properties may well be large, we need to do this in batches. """ hm = schema.CALENDAR_HOME_METADATA cb = schema.CALENDAR_BIND rp = schema.RESOURCE_PROPERTY try: calendars_for_id = {} while True: sqlTxn = sqlStore.newTransaction() rows = (yield rowsForProperty(sqlTxn, propname, with_uid=True, batch=BATCH_SIZE)) if len(rows) == 0: yield sqlTxn.commit() break delete_ids = [] for rid, value, viewer in rows: delete_ids.append(rid) prop = WebDAVDocument.fromString(value).root_element alarm = str(prop.children[0]) if prop.children else None # First check if the rid is a home - this is the most common case ids = yield Select( [hm.RESOURCE_ID, ], From=hm, Where=hm.RESOURCE_ID == rid, ).on(sqlTxn) if len(ids) > 0: # Home object calendarHome = (yield sqlTxn.calendarHomeWithResourceID(ids[0][0])) if calendarHome is not None: yield calendarHome.setDefaultAlarm(alarm, vevent, timed) else: # rid is a calendar - we need to find the per-user calendar for the resource viewer if rid not in calendars_for_id: ids = yield Select( [cb.CALENDAR_HOME_RESOURCE_ID, cb.BIND_MODE, ], From=cb, Where=cb.CALENDAR_RESOURCE_ID == rid, ).on(sqlTxn) calendars_for_id[rid] = ids if viewer: calendarHome = (yield sqlTxn.calendarHomeWithUID(viewer)) else: calendarHome = None for row in calendars_for_id[rid]: home_id, bind_mode = row if bind_mode == _BIND_MODE_OWN: calendarHome = (yield sqlTxn.calendarHomeWithResourceID(home_id)) break if calendarHome is not None: calendar = yield calendarHome.childWithID(rid) if calendar is not None: yield calendar.setDefaultAlarm(alarm, vevent, timed) # Always delete the rows so that batch processing works correctly yield Delete( From=rp, Where=(rp.RESOURCE_ID.In(Parameter("ids", len(delete_ids)))).And (rp.NAME == PropertyName.fromElement(propname).toString()), ).on(sqlTxn, ids=delete_ids) yield sqlTxn.commit() yield cleanPropertyStore() except RuntimeError: f = Failure() yield sqlTxn.abort() f.raiseException()