def raise_error(self, status, stdout, stderr): """This method may be over-ridden by sub-classes if you need to control how the relay error is generated. By default, the error raised is a :class:`~slimta.relay.TransientRelayError` unless the process output begins with a ``5.X.X`` enhanced status code. This behavior attempts to mimic the postfix pipe_ daemon. This method is only called if the subprocess returns a non-zero exit status. :param status: The non-zero exit status of the subprocess. :param stdout: The subprocess's standard output, as received by :py:meth:`~subprocess.Popen.communicate`. :type stdout: string :param stderr: The subprocess's standard error output, as received by :py:meth:`~subprocess.Popen.communicate`. :type stderr: string :raises: :class:`~slimta.relay.TransientRelayError`, :class:`~slimta.relay.PermanentRelayError` """ error_msg = stdout.rstrip() or stderr.rstrip() or 'Delivery failed' if self._permanent_error_pattern.match(error_msg): reply = Reply('550', error_msg) raise PermanentRelayError(error_msg, reply) else: reply = Reply('450', error_msg) raise TransientRelayError(error_msg, reply)
def test_enqueue_wait_partial_relay(self): env = Envelope( '*****@*****.**', ['*****@*****.**', '*****@*****.**', '*****@*****.**']) self.store.write(env, IsA(float)).AndReturn('1234') self.relay._attempt(env, 0).AndReturn({ '*****@*****.**': None, '*****@*****.**': TransientRelayError('transient', Reply('450', 'transient')), '*****@*****.**': PermanentRelayError('permanent', Reply('550', 'permanent')) }) self.store.increment_attempts('1234') self.store.set_timestamp('1234', IsA(float)) self.store.set_recipients_delivered('1234', set([0, 2])) self.mox.ReplayAll() def backoff(envelope, attempts): return 0 def no_bounce(envelope, reply): return None queue = Queue(self.store, self.relay, backoff=backoff, bounce_factory=no_bounce, relay_pool=5) queue.enqueue(env) queue.relay_pool.join()
def raise_error(self, status, stdout, stderr): error_msg = stdout.rstrip() or stderr.rstrip() or 'LDA delivery failed' if status == self.EX_TEMPFAIL: reply = Reply('450', error_msg) raise TransientRelayError(error_msg, reply) else: reply = Reply('550', error_msg) raise PermanentRelayError(error_msg, reply)
def test_enqueue_wait_partial_relay_expired(self): env = Envelope('*****@*****.**', ['*****@*****.**', '*****@*****.**', '*****@*****.**']) bounce_mock = self.mox.CreateMockAnything() bounce_mock(IsA(Envelope), IsA(Reply)).AndReturn(None) bounce_mock(IsA(Envelope), IsA(Reply)).AndReturn(None) self.store.write(env, IsA(float)).AndReturn('1234') self.relay._attempt(env, 0).AndReturn([TransientRelayError('transient', Reply('450', 'transient 1')), TransientRelayError('transient', Reply('450', 'transient 1')), TransientRelayError('transient', Reply('450', 'transient 2'))]) self.store.increment_attempts('1234') self.store.remove('1234') self.mox.ReplayAll() queue = Queue(self.store, self.relay, bounce_factory=bounce_mock, relay_pool=5) queue.enqueue(env) queue.relay_pool.join()
def _try_pipe(self, envelope): try: status, stdout, stderr = self._exec_process(envelope) except Timeout: msg = 'Delivery timed out' reply = Reply('450', msg) raise TransientRelayError(msg, reply) if status != 0: self.raise_error(status, stdout, stderr)
def test_enqueue_wait_transientfail_noretry(self): self.store.write(self.env, IsA(float)).AndReturn('1234') self.relay._attempt(self.env, 0).AndRaise(TransientRelayError('transient', Reply('450', 'transient'))) self.store.increment_attempts('1234') self.store.remove('1234') self.mox.ReplayAll() def no_bounce(envelope, reply): return None queue = Queue(self.store, self.relay, bounce_factory=no_bounce, relay_pool=5) queue.enqueue(self.env) queue.relay_pool.join()
def test_enqueue_wait_transientfail(self): self.store.write(self.env, IsA(float)).AndReturn('1234') self.relay._attempt(self.env, 0).AndRaise(TransientRelayError('transient', Reply('450', 'transient'))) self.store.increment_attempts('1234') self.store.set_timestamp('1234', IsA(float)) self.mox.ReplayAll() def backoff(envelope, attempts): return 0 queue = Queue(self.store, self.relay, backoff=backoff, relay_pool=5) queue.enqueue(self.env) queue.relay_pool.join()
def raise_error(self, status, stdout, stderr): error_msg = 'Delivery failed' if stdout.startswith('maildrop: '): error_msg = stdout[10:].rstrip() elif stderr.startswith('maildrop: '): error_msg = stderr[10:].rstrip() if status == self.EX_TEMPFAIL: reply = Reply('450', error_msg) raise TransientRelayError(error_msg, reply) else: reply = Reply('550', error_msg) raise PermanentRelayError(error_msg, reply)
def _try_pipe_one_rcpt(self, envelope): header_data, message_data = envelope.flatten() stdin = b''.join((header_data, message_data)) rcpt = envelope.recipients[0] try: with Timeout(self.timeout): args = self._process_args(envelope, rcpt) return self._exec_process(args, stdin) except Timeout: msg = 'Delivery timed out' reply = Reply('450', '4.4.2 ' + msg) raise TransientRelayError(msg, reply)
def _try_pipe_all_rcpts(self, envelope): header_data, message_data = envelope.flatten() stdin = b''.join((header_data, message_data)) results = {} try: with Timeout(self.timeout): for rcpt in envelope.recipients: args = self._process_args(envelope, rcpt) results[rcpt] = self._exec_process(args, stdin) except Timeout: for rcpt in envelope.recipients: if rcpt not in results: msg = 'Delivery timed out' reply = Reply('450', '4.4.2 ' + msg) results[rcpt] = TransientRelayError(msg, reply) return results
def test_attempt_delivery_transientrelayerror(self): task = self.mox.CreateMockAnything() subtask = self.mox.CreateMockAnything() result = self.mox.CreateMockAnything() result.id = '12345' self.relay.attempt(self.env, 0).AndRaise(TransientRelayError('transient', Reply('450', 'transient error'))) self.celery.task(IgnoreArg()).AndReturn(task) task.s(self.env, 1).AndReturn(subtask) subtask.set(countdown=60) subtask.apply_async().AndReturn(result) self.mox.ReplayAll() def backoff(envelope, attempts): self.assertEqual(self.env, envelope) self.assertEqual(1, attempts) return 60 queue = CeleryQueue(self.celery, self.relay, backoff=backoff) queue.attempt_delivery(self.env, 0)
def test_attempt_delivery_transientrelayerror_no_retry(self): task = self.mox.CreateMockAnything() subtask = self.mox.CreateMockAnything() result = self.mox.CreateMockAnything() result.id = '12345' self.relay.attempt(self.env, 0).AndRaise(TransientRelayError('transient', Reply('450', 'transient error'))) self.celery.task(IgnoreArg()).AndReturn(task) task.s(self.bounce, 0).AndReturn(subtask) subtask.apply_async().AndReturn(result) self.mox.ReplayAll() def return_bounce(envelope, reply): self.assertEqual(self.env, envelope) return self.bounce def no_retry(envelope, attempts): self.assertEqual(self.env, envelope) self.assertEqual(1, attempts) return None queue = CeleryQueue(self.celery, self.relay, backoff=no_retry, bounce_factory=return_bounce) queue.attempt_delivery(self.env, 0)
def test_default_replies(self): perm = PermanentRelayError('test msg') transient = TransientRelayError('test msg') self.assertEqual('550 5.0.0 test msg', str(perm.reply)) self.assertEqual('450 4.0.0 test msg', str(transient.reply))