Skip to content

Commit 45bb3b5

Browse files
authored
fix: Split apache-libcloud versions for Python compatibility (#619)
* fix: Split apache-libcloud versions for Python compatibility - Server (Python 3.9): apache-libcloud==3.2.0 (stable Cloudflare DNS) - Client (Python 3.12): apache-libcloud==3.8.0 (Python 3.12 support) Client Dockerfile installs requirements-webapp.txt after requirements.txt, so libcloud 3.8.0 overrides 3.2.0 for the client container only. Fixes Cloudflare DNS issues: - libcloud 3.6.0: RecordAlreadyExistsError constructor bug - libcloud 3.8.0: KeyError 'zone_name' in Cloudflare driver - libcloud 3.2.0: Proven stable with Cloudflare, incompatible with Py3.12 Tested: - Server: Python 3.9 + libcloud 3.2.0 (all 8 DNS tests pass) - Client: Python 3.12 + libcloud 3.8.0 (imports work correctly) * fix: Remove [opwen_email_server] extra from client package install The client Dockerfile was installing the package with [opwen_email_server] extra, which pulled in server dependencies (requirements.txt with libcloud 3.2.0). This conflicted with the client's libcloud 3.8.0. Client only needs the base package dependencies (requirements-webapp.txt). * fix: Shorten comments to meet flake8 120 char limit and fix hadolint comment placement * fix: Make test queue durable for RabbitMQ 4.0+ compatibility RabbitMQ 4.0+ deprecated transient non-exclusive queues. The integration test was creating a queue without durable=True, causing CI failures with: InternalError: Queue.declare: (541) INTERNAL_ERROR - Feature `transient_nonexcl_queues` is deprecated. * Revert RabbitMQ fix - was incomplete * fix: Configure Celery for RabbitMQ 4.0+ compatibility RabbitMQ 4.0+ deprecated transient non-exclusive queues. This fix: 1. Explicitly defines all task queues as durable using Kombu Queue objects 2. Disables remote control (worker_enable_remote_control=False) to prevent creation of non-durable pidbox queues that trigger the deprecation error 3. Updates test queue to be durable Without these changes, Celery workers fail to start with: amqp.exceptions.InternalError: Queue.declare: (541) INTERNAL_ERROR Feature `transient_nonexcl_queues` is deprecated. Tested successfully with RabbitMQ 4.3.1 - all integration tests pass. * fix: Separate integration test dependencies into requirements-integration.txt - Created requirements-integration.txt with server dependencies needed by AzureIoc - Updated Dockerfile to install integration deps in both builder and runtime stages - Production client (client.env) uses default Ioc without server dependencies - Integration tests (webapp.env) use AzureIoc with full server dependencies - Integration tests now pass successfully with BUILD_TARGET=runtime * fix: RabbitMQ 4.0+ compatibility with explicit durable queues - Define all 7 task queues explicitly as durable Queue objects - Disable worker remote control to prevent transient pidbox queues - Set worker_prefetch_multiplier=1 to avoid deprecated global_qos - Fixes 'Feature transient_nonexcl_queues is deprecated' errors - Integration tests pass with RabbitMQ 4.3.1
1 parent f3ca8ff commit 45bb3b5

6 files changed

Lines changed: 63 additions & 6 deletions

File tree

docker/client/Dockerfile

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ RUN pip install --no-cache-dir -r requirements.txt
2828
COPY requirements-webapp.txt ./
2929
RUN pip install --no-cache-dir -r requirements-webapp.txt
3030

31+
# Integration test dependencies (only needed when using AzureIoc)
32+
COPY requirements-integration.txt ./
33+
RUN pip install --no-cache-dir -r requirements-integration.txt
34+
3135
ENV OPWEN_SESSION_KEY=changeme
3236
ENV OPWEN_SETTINGS=/app/docker/client/webapp.env
3337

@@ -56,9 +60,13 @@ RUN apt-get update \
5660

5761
# hadolint ignore=DL3010
5862
COPY --from=compiler /app/dist/pkg.tar.gz /app/dist/pkg.tar.gz
63+
COPY --from=compiler /app/requirements-integration.txt /app/requirements-integration.txt
5964

65+
# Install without [opwen_email_server] extra to avoid server dependencies (libcloud 3.2.0)
66+
# Client only needs base package with requirements-webapp.txt (libcloud 3.8.0)
6067
# hadolint ignore=DL3013
61-
RUN pip install --no-cache-dir "/app/dist/pkg.tar.gz[opwen_email_server]" \
68+
RUN pip install --no-cache-dir "/app/dist/pkg.tar.gz" \
69+
&& pip install --no-cache-dir -r /app/requirements-integration.txt \
6270
&& rm -rf /tmp/pip-ephem-wheel-cache*
6371

6472
COPY --from=compiler /app/docker/client/run-*.sh /app/docker/client/

opwen_email_server/integration/celery.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
from celery import Celery
2+
from kombu import Exchange
3+
from kombu import Queue
24

35
from opwen_email_server import config
46
from opwen_email_server.actions import IndexReceivedEmailForMailbox
@@ -121,6 +123,29 @@ def _fqn(task):
121123
return f'{__name__}.{task.__name__}'
122124

123125

126+
# RabbitMQ 4.0+ deprecated transient non-exclusive queues
127+
# Explicitly define all queues as durable
128+
default_exchange = Exchange('celery', type='direct', durable=True)
129+
130+
task_queues = (
131+
Queue(config.REGISTER_CLIENT_QUEUE,
132+
exchange=default_exchange,
133+
routing_key=config.REGISTER_CLIENT_QUEUE,
134+
durable=True),
135+
Queue(config.MAILBOX_RECEIVED_QUEUE,
136+
exchange=default_exchange,
137+
routing_key=config.MAILBOX_RECEIVED_QUEUE,
138+
durable=True),
139+
Queue(config.MAILBOX_SENT_QUEUE, exchange=default_exchange, routing_key=config.MAILBOX_SENT_QUEUE, durable=True),
140+
Queue(config.PROCESS_SERVICE_QUEUE,
141+
exchange=default_exchange,
142+
routing_key=config.PROCESS_SERVICE_QUEUE,
143+
durable=True),
144+
Queue(config.INBOUND_STORE_QUEUE, exchange=default_exchange, routing_key=config.INBOUND_STORE_QUEUE, durable=True),
145+
Queue(config.WRITTEN_STORE_QUEUE, exchange=default_exchange, routing_key=config.WRITTEN_STORE_QUEUE, durable=True),
146+
Queue(config.SEND_QUEUE, exchange=default_exchange, routing_key=config.SEND_QUEUE, durable=True),
147+
)
148+
124149
task_routes = {
125150
_fqn(register_client): {'queue': config.REGISTER_CLIENT_QUEUE},
126151
_fqn(index_received_email_for_mailbox): {'queue': config.MAILBOX_RECEIVED_QUEUE},
@@ -131,7 +156,16 @@ def _fqn(task):
131156
_fqn(send): {'queue': config.SEND_QUEUE}
132157
}
133158

134-
celery.conf.update(task_routes=task_routes)
159+
celery.conf.update(
160+
task_queues=task_queues,
161+
task_routes=task_routes,
162+
# RabbitMQ 4.0+ deprecated transient non-exclusive queues
163+
# Disable remote control to avoid creating non-durable pidbox queues
164+
worker_enable_remote_control=False,
165+
# RabbitMQ 4.0+ deprecated global_qos feature
166+
# Use per-consumer prefetch instead of global QoS
167+
worker_prefetch_multiplier=1,
168+
)
135169

136170
if __name__ == '__main__':
137171
celery.start()

requirements-integration.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Additional dependencies required for integration testing with AzureIoc
2+
# These are server-side dependencies imported by opwen_email_server.integration.webapp.AzureIoc
3+
# Only needed when LOKOLE_IOC=opwen_email_server.integration.webapp.AzureIoc (integration tests)
4+
# Production client uses opwen_email_client.webapp.ioc.Ioc which doesn't need these
5+
6+
applicationinsights==0.11.10 # Required by opwen_email_server.utils.log
7+
msgpack==1.0.4 # Required by opwen_email_server.utils.serialization
8+
python-http-client==3.3.7 # Required by sendgrid
9+
sendgrid==6.9.7 # Required by opwen_email_server.integration.celery.send_and_index_email
10+
pyzmail36==1.0.5 # Required by opwen_email_server email parsing
11+
azure-servicebus==7.8.0 # Required by opwen_email_server.services.queue
12+
connexion[swagger-ui]==2.14.0 # Required by opwen_email_server API
13+
jsonschema==4.23.0 # Required by connexion
14+
referencing==0.35.1 # Required by jsonschema
15+
wikipedia==1.4.0 # Required by opwen_email_server.services.index

requirements-webapp.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
setuptools>=65.5.0,<73.0.0 # Required for pkg_resources in Python 3.12+ (no longer included by default). Upper bound because setuptools 73+ removed pkg_resources
1+
setuptools>=65.5.0,<73.0.0 # Required for pkg_resources in Python 3.12+ (setuptools 73+ removed it)
22
Babel==2.14.0 # 2.14.0+ required for Python 3.13 (removed cgi module dependency)
33
Flask-BabelEx==0.9.4
44
pytz # Required by Flask-BabelEx (no longer a transitive dep of Babel 2.14+)
@@ -16,7 +16,7 @@ WTForms==3.0.1
1616
email-validator==1.2.1
1717
Werkzeug==2.2.1
1818
fasteners==0.17.3
19-
apache-libcloud==3.8.0
19+
apache-libcloud==3.8.0 # Client: Python 3.12 support. Overrides base 3.2.0. Azure Storage only.
2020
bcrypt==4.0.1
2121
beautifulsoup4==4.11.1
2222
cached-property==1.5.2

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ Flask==2.2.1
22
Flask-Cors==3.0.10
33
Pillow==10.4.0 # 10.1.0+ required for Python 3.13 support
44
setuptools>=65.5.0,<73.0.0 # Required for pkg_resources (gunicorn dependency). setuptools 73+ removed pkg_resources
5-
apache-libcloud==3.8.0
5+
apache-libcloud==3.2.0 # Server: Python 3.9, stable Cloudflare DNS. Client overrides to 3.8.0 (Python 3.12).
66
applicationinsights==0.11.10
77
beautifulsoup4==4.11.1
88
cached-property==1.5.2

tests/opwen_email_server/integration/test_celery.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def exchange(self) -> Exchange:
2525

2626
@cached_property
2727
def queue(self) -> Queue:
28-
return Queue(self.queue_name, exchange=self.exchange, routing_key=self.routing_key)
28+
return Queue(self.queue_name, exchange=self.exchange, routing_key=self.routing_key, durable=True)
2929

3030
@skipUnless(QUEUE_BROKER, 'no celery broker configured')
3131
def test_send_message(self):

0 commit comments

Comments
 (0)