Thank you again for the response, you are correct it’s an SMTP issue. The nignix got timed out on sending emails. It’s sending emails correctly on all other notifications, but on user creation, it was throwing an SSL error.
For an interim fix, I have disabled the email notification. Now, adding members is working.
Here is the stacktrace
OSError at /api/v1/memberships/bulk_create
[Errno 101] Network is unreachable
Request Method: POST
Request URL: https://taiga.q-sols.com:1025/api/v1/memberships/bulk_create
Django Version: 3.2.18
Exception Type: OSError
Exception Value:
[Errno 101] Network is unreachable
Exception Location: /usr/lib/python3.9/socket.py, line 831, in create_connection
Python Executable: /home/taiga/taiga-back/.venv/bin/python3
Python Version: 3.9.5
Python Path:
['/home/taiga/taiga-back',
'/home/taiga/taiga-back',
'/home/taiga/taiga-back/.venv/bin',
'/usr/lib/python39.zip',
'/usr/lib/python3.9',
'/usr/lib/python3.9/lib-dynload',
'/home/taiga/taiga-back/.venv/lib/python3.9/site-packages']
Server time: Thu, 02 Nov 2023 08:52:18 +0000
Traceback Switch to copy-and-paste view
/home/taiga/taiga-back/.venv/lib/python3.9/site-packages/django/core/handlers/exception.py, line 47, in inner
response = await sync_to_async(response_for_exception, thread_sensitive=False)(request, exc)
return response
return inner
else:
@wraps(get_response)
def inner(request):
try:
response = get_response(request) …
except Exception as exc:
response = response_for_exception(request, exc)
return response
return inner
▶ Local vars
Variable Value
exc
OSError(101, 'Network is unreachable')
get_response
<bound method BaseHandler._get_response of <django.core.handlers.wsgi.WSGIHandler object at 0x7f2057a79760>>
request
<WSGIRequest: POST '/api/v1/memberships/bulk_create'>
/home/taiga/taiga-back/.venv/lib/python3.9/site-packages/django/core/handlers/base.py, line 181, in _get_response
if response is None:
wrapped_callback = self.make_view_atomic(callback)
# If it is an asynchronous view, run it in a subthread.
if asyncio.iscoroutinefunction(wrapped_callback):
wrapped_callback = async_to_sync(wrapped_callback)
try:
response = wrapped_callback(request, *callback_args, **callback_kwargs) …
except Exception as e:
response = self.process_exception_by_middleware(e, request)
if response is None:
raise
# Complain if the view returned None (a common error).
▶ Local vars
Variable Value
callback
<function MembershipViewSet at 0x7f2057c20c10>
callback_args
()
callback_kwargs
{}
middleware_method
<bound method SentryMiddleware.process_view of <raven.contrib.django.middleware.SentryMiddleware object at 0x7f2057a9d700>>
request
<WSGIRequest: POST '/api/v1/memberships/bulk_create'>
response
None
self
<django.core.handlers.wsgi.WSGIHandler object at 0x7f2057a79760>
wrapped_callback
<function MembershipViewSet at 0x7f2057c20c10>
/home/taiga/taiga-back/taiga/base/api/viewsets.py, line 95, in view
setattr(self, method, handler)
# Patch this in as it's otherwise only present from 1.5 onwards
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
# And continue as usual
return self.dispatch(request, *args, **kwargs) …
# take name and docstring from class
update_wrapper(view, cls, updated=())
# and possible attributes set by decorators
# like csrf_exempt from dispatch
▶ Local vars
Variable Value
action
'bulk_create'
actions
{'post': 'bulk_create'}
args
()
cls
<class 'taiga.projects.api.MembershipViewSet'>
handler
<bound method MembershipViewSet.bulk_create of <taiga.projects.api.MembershipViewSet object at 0x7f20578f5b20>>
initkwargs
{}
kwargs
{}
method
'post'
request
<WSGIRequest: POST '/api/v1/memberships/bulk_create'>
self
<taiga.projects.api.MembershipViewSet object at 0x7f20578f5b20>
/home/taiga/taiga-back/.venv/lib/python3.9/site-packages/django/views/decorators/csrf.py, line 54, in wrapped_view
def csrf_exempt(view_func):
"""Mark a view function as being exempt from the CSRF view protection."""
# view_func.csrf_exempt = True would also work, but decorators are nicer
# if they don't have side effects, so return a new function.
def wrapped_view(*args, **kwargs):
return view_func(*args, **kwargs) …
wrapped_view.csrf_exempt = True
return wraps(view_func)(wrapped_view)
▶ Local vars
Variable Value
args
(<taiga.projects.api.MembershipViewSet object at 0x7f20578f5b20>,
<WSGIRequest: POST '/api/v1/memberships/bulk_create'>)
kwargs
{}
view_func
<function APIView.dispatch at 0x7f205de524c0>
/home/taiga/taiga-back/taiga/base/api/views.py, line 449, in dispatch
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
response = handler(request, *args, **kwargs)
except Exception as exc:
response = self.handle_exception(exc) …
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
def options(self, request, *args, **kwargs):
"""
▶ Local vars
Variable Value
args
()
handler
<bound method MembershipViewSet.bulk_create of <taiga.projects.api.MembershipViewSet object at 0x7f20578f5b20>>
kwargs
{}
request
<taiga.base.api.request.Request object at 0x7f20578f58b0>
self
<taiga.projects.api.MembershipViewSet object at 0x7f20578f5b20>
/home/taiga/taiga-back/taiga/base/api/views.py, line 447, in dispatch
# Get the appropriate handler method
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
response = handler(request, *args, **kwargs) …
except Exception as exc:
response = self.handle_exception(exc)
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
▶ Local vars
Variable Value
args
()
handler
<bound method MembershipViewSet.bulk_create of <taiga.projects.api.MembershipViewSet object at 0x7f20578f5b20>>
kwargs
{}
request
<taiga.base.api.request.Request object at 0x7f20578f58b0>
self
<taiga.projects.api.MembershipViewSet object at 0x7f20578f5b20>
/home/taiga/taiga-back/taiga/projects/api.py, line 1066, in bulk_create
try:
with advisory_lock("membership-creation-{}".format(project.id)):
members = services.get_members_from_bulk(bulk_memberships,
project=project,
invited_by=request.user,
invitation_extra_text=invitation_extra_text)
self._check_if_new_members_can_be_created(project, members)
services.create_members_in_bulk(members, callback=self.post_save) …
signal_members_added.send(sender=self.__class__,
user=self.request.user,
project=project,
new_members=members)
except exc.ValidationError as err:
return response.BadRequest(err.message_dict)
▶ Local vars
Variable Value
bulk_memberships
[OrderedDictWithMetadata([('username', 'ahsandevops'), ('role_id', 664)])]
context
{'request': <taiga.base.api.request.Request object at 0x7f20578f58b0>}
data
OrderedDictWithMetadata([('project_id', 102),
('bulk_memberships',
[OrderedDictWithMetadata([('username', 'ahsandevops'),
('role_id', 664)])]),
('invitation_extra_text', None)])
invitation_extra_text
None
kwargs
{}
members
[<Membership: Membership object (1041)>]
project
<Project 102>
request
<taiga.base.api.request.Request object at 0x7f20578f58b0>
self
<taiga.projects.api.MembershipViewSet object at 0x7f20578f5b20>
validator
<taiga.projects.validators.MembersBulkValidator object at 0x7f20578f5c40>
/home/taiga/taiga-back/taiga/projects/services/members.py, line 66, in create_members_in_bulk
"""Create members from `bulk_data`.
:param members: List of dicts `{"project_id": <>, "role_id": <>, "username": <>}`.
:param callback: Callback to execute after each task save.
:param additional_fields: Additional fields when instantiating each task.
"""
db.save_in_bulk(members, callback, precall) …
def remove_user_from_project(user, project):
models.Membership.objects.get(project=project, user=user).delete()
▶ Local vars
Variable Value
callback
<bound method MembershipViewSet.post_save of <taiga.projects.api.MembershipViewSet object at 0x7f20578f5b20>>
members
[<Membership: Membership object (1041)>]
precall
None
/usr/lib/python3.9/contextlib.py, line 79, in inner
"""
return self
def __call__(self, func):
@wraps(func)
def inner(*args, **kwds):
with self._recreate_cm():
return func(*args, **kwds) …
return inner
class _GeneratorContextManagerBase:
"""Shared functionality for @contextmanager and @asynccontextmanager."""
▶ Local vars
Variable Value
args
([<Membership: Membership object (1041)>],
<bound method MembershipViewSet.post_save of <taiga.projects.api.MembershipViewSet object at 0x7f20578f5b20>>,
None)
func
<function save_in_bulk at 0x7f205d42e430>
kwds
{}
self
<django.db.transaction.Atomic object at 0x7f205d430310>
/home/taiga/taiga-back/taiga/base/utils/db.py, line 88, in save_in_bulk
for instance in instances:
created = False
if instance.pk is None:
created = True
precall(instance)
instance.save(**save_options)
callback(instance, created=created) …
return ret
@transaction.atomic
def update_in_bulk(instances, list_of_new_values, callback=None, precall=None):
▶ Local vars
Variable Value
callback
<bound method MembershipViewSet.post_save of <taiga.projects.api.MembershipViewSet object at 0x7f20578f5b20>>
created
True
instance
<Membership: Membership object (1041)>
instances
[<Membership: Membership object (1041)>]
precall
<function noop at 0x7f205d42e1f0>
ret
[]
save_options
{}
/home/taiga/taiga-back/taiga/projects/api.py, line 1158, in post_save
def post_save(self, object, created=False):
super().post_save(object, created=created)
if not created:
return
# Send email only if a new membership is created
services.send_invitation(invitation=object) …
class InvitationViewSet(ModelListViewSet):
"""
Only used by front for get invitation by it token.
"""
▶ Local vars
Variable Value
__class__
<class 'taiga.projects.api.MembershipViewSet'>
created
True
object
<Membership: Membership object (1041)>
self
<taiga.projects.api.MembershipViewSet object at 0x7f20578f5b20>
/home/taiga/taiga-back/taiga/projects/services/invitations.py, line 29, in send_invitation
if invitation.user:
template = mail_builder.membership_notification
email = template(invitation.user, {"membership": invitation})
else:
template = mail_builder.membership_invitation
email = template(invitation.email, {"membership": invitation})
email.send() …
def find_invited_user(email, default=None):
"""Check if the invited user is already a registered.
:param email: some user email
▶ Local vars
Variable Value
email
<django.core.mail.message.EmailMultiAlternatives object at 0x7f2057961e80>
invitation
<Membership: Membership object (1041)>
template
<function MagicMailBuilder.__getattr__.<locals>._dynamic_email_generator at 0x7f2057953f70>
/home/taiga/taiga-back/.venv/lib/python3.9/site-packages/django/core/mail/message.py, line 284, in send
def send(self, fail_silently=False):
"""Send the email message."""
if not self.recipients():
# Don't bother creating the network connection if there's nobody to
# send to.
return 0
return self.get_connection(fail_silently).send_messages([self]) …
def attach(self, filename=None, content=None, mimetype=None):
"""
Attach a file with the given filename and content. The filename can
be omitted and the mimetype is guessed, if not provided.
▶ Local vars
Variable Value
fail_silently
False
self
<django.core.mail.message.EmailMultiAlternatives object at 0x7f2057961e80>
/home/taiga/taiga-back/.venv/lib/python3.9/site-packages/django/core/mail/backends/smtp.py, line 102, in send_messages
"""
Send one or more EmailMessage objects and return the number of email
messages sent.
"""
if not email_messages:
return 0
with self._lock:
new_conn_created = self.open() …
if not self.connection or new_conn_created is None:
# We failed silently on open().
# Trying to send would be pointless.
return 0
num_sent = 0
for message in email_messages:
▶ Local vars
Variable Value
email_messages
[<django.core.mail.message.EmailMultiAlternatives object at 0x7f2057961e80>]
self
<django.core.mail.backends.smtp.EmailBackend object at 0x7f2057961df0>
/home/taiga/taiga-back/.venv/lib/python3.9/site-packages/django/core/mail/backends/smtp.py, line 62, in open
connection_params['timeout'] = self.timeout
if self.use_ssl:
connection_params.update({
'keyfile': self.ssl_keyfile,
'certfile': self.ssl_certfile,
})
try:
self.connection = self.connection_class(self.host, self.port, **connection_params) …
# TLS/SSL are mutually exclusive, so only attempt TLS over
# non-secure connections.
if not self.use_ssl and self.use_tls:
self.connection.starttls(keyfile=self.ssl_keyfile, certfile=self.ssl_certfile)
if self.username and self.password:
▶ Local vars
Variable Value
connection_params
{'local_hostname': 'ubuntu-server'}
self
<django.core.mail.backends.smtp.EmailBackend object at 0x7f2057961df0>
/usr/lib/python3.9/smtplib.py, line 255, in __init__
self.timeout = timeout
self.esmtp_features = {}
self.command_encoding = 'ascii'
self.source_address = source_address
self._auth_challenge_count = 0
if host:
(code, msg) = self.connect(host, port) …
if code != 220:
self.close()
raise SMTPConnectError(code, msg)
if local_hostname is not None:
self.local_hostname = local_hostname
else:
▶ Local vars
Variable Value
host
'smtp.gmail.com'
local_hostname
'ubuntu-server'
port
587
self
<smtplib.SMTP object at 0x7f205771a8e0>
source_address
None
timeout
<object object at 0x7f2060c5e8a0>
/usr/lib/python3.9/smtplib.py, line 341, in connect
try:
port = int(port)
except ValueError:
raise OSError("nonnumeric port")
if not port:
port = self.default_port
sys.audit("smtplib.connect", self, host, port)
self.sock = self._get_socket(host, port, self.timeout) …
self.file = None
(code, msg) = self.getreply()
if self.debuglevel > 0:
self._print_debug('connect:', repr(msg))
return (code, msg)
▶ Local vars
Variable Value
host
'smtp.gmail.com'
port
587
self
<smtplib.SMTP object at 0x7f205771a8e0>
source_address
None
/usr/lib/python3.9/smtplib.py, line 312, in _get_socket
def _get_socket(self, host, port, timeout):
# This makes it simpler for SMTP_SSL to use the SMTP connect code
# and just alter the socket connection bit.
if timeout is not None and not timeout:
raise ValueError('Non-blocking socket (timeout=0) is not supported')
if self.debuglevel > 0:
self._print_debug('connect: to', (host, port), self.source_address)
return socket.create_connection((host, port), timeout, …
self.source_address)
def connect(self, host='localhost', port=0, source_address=None):
"""Connect to a host on a given port.
If the hostname ends with a colon (`:') followed by a number, and
▶ Local vars
Variable Value
host
'smtp.gmail.com'
port
587
self
<smtplib.SMTP object at 0x7f205771a8e0>
timeout
<object object at 0x7f2060c5e8a0>
/usr/lib/python3.9/socket.py, line 843, in create_connection
except error as _:
err = _
if sock is not None:
sock.close()
if err is not None:
try:
raise err …
finally:
# Break explicitly a reference cycle
err = None
else:
raise error("getaddrinfo returns an empty list")
▶ Local vars
Variable Value
address
('smtp.gmail.com', 587)
af
<AddressFamily.AF_INET6: 10>
canonname
''
err
None
host
'smtp.gmail.com'
port
587
proto
6
res
(<AddressFamily.AF_INET6: 10>,
<SocketKind.SOCK_STREAM: 1>,
6,
'',
('2a00:1450:400c:c00::6d', 587, 0, 0))
sa
('2a00:1450:400c:c00::6d', 587, 0, 0)
sock
<socket.socket [closed] fd=-1, family=AddressFamily.AF_INET6, type=SocketKind.SOCK_STREAM, proto=6>
socktype
<SocketKind.SOCK_STREAM: 1>
source_address
None
timeout
<object object at 0x7f2060c5e8a0>
/usr/lib/python3.9/socket.py, line 831, in create_connection
sock = None
try:
sock = socket(af, socktype, proto)
if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
sock.settimeout(timeout)
if source_address:
sock.bind(source_address)
sock.connect(sa) …
# Break explicitly a reference cycle
err = None
return sock
except error as _:
err = _
▶ Local vars
Variable Value
address
('smtp.gmail.com', 587)
af
<AddressFamily.AF_INET6: 10>
canonname
''
err
None
host
'smtp.gmail.com'
port
587
proto
6
res
(<AddressFamily.AF_INET6: 10>,
<SocketKind.SOCK_STREAM: 1>,
6,
'',
('2a00:1450:400c:c00::6d', 587, 0, 0))
sa
('2a00:1450:400c:c00::6d', 587, 0, 0)
sock
<socket.socket [closed] fd=-1, family=AddressFamily.AF_INET6, type=SocketKind.SOCK_STREAM, proto=6>
socktype
<SocketKind.SOCK_STREAM: 1>
source_address
None
timeout
<object object at 0x7f2060c5e8a0>
Environment:
Request Method: POST
Request URL: https://taiga.q-sols.com:1025/api/v1/memberships/bulk_create
Django Version: 3.2.18
Python Version: 3.9.5