nautobot: Unable to stand up nautobot in a HA mode (one write instance & one read-only instance)
Environment
- Nautobot version (Docker tag too if applicable): 1.3.6
- Python version: 3.10
- Database platform, version: postgres
- Middleware(s):
Steps to Reproduce
- Standup nautobot using docker-compose but change the compose file so the database port is exposed
- Connect to the postgres database and create a read-only user and set transactions to read-only
CREATE USER nautobot_read WITH PASSWORD 'readonly';
GRANT CONNECT ON DATABASE nautobot TO nautobot_read;
GRANT USAGE ON SCHEMA public TO nautobot_read;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO nautobot_read;
GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO nautobot_read;
ALTER USER nautobot_read set default_transaction_read_only = on;
- Copy the docker-compose directory to another folder and change the compose file to use a different port for exposing the web interface and remove the db container and volume
- Change the local.env to update the DB settings so they use a DB host of the local IP on the machine and the database port that was exposed in step 1 and the DB account/password for the read user setup in Step 2.
Expected Behavior
I’m able to spin up a write instance and a separate read-only instance.
The idea being that nautobot can be hosted in two different sites, one site with write the other for read, the redis cluster is synced cross-site and the database has read-only replicas in the other site.
Observed Behavior
The read-only instance complains it doesn’t have the permissions to perform INSERT/UPDATE statements in different tables.
nauto_read-celery_beat-1 | Traceback (most recent call last):
nauto_read-celery_beat-1 | File "/usr/local/lib/python3.10/site-packages/django/db/backends/utils.py", line 84, in _execute
nauto_read-celery_beat-1 | return self.cursor.execute(sql, params)
nauto_read-celery_beat-1 | psycopg2.errors.ReadOnlySqlTransaction: cannot execute UPDATE in a read-only transaction
nauto_read-celery_beat-1 |
nauto_read-celery_beat-1 |
nauto_read-celery_beat-1 | The above exception was the direct cause of the following exception:
nauto_read-celery_beat-1 |
nauto_read-celery_beat-1 | Traceback (most recent call last):
nauto_read-celery_beat-1 | File "/usr/local/lib/python3.10/site-packages/django_celery_beat/schedulers.py", line 320, in update_from_dict
nauto_read-celery_beat-1 | entry = self.Entry.from_entry(name,
nauto_read-celery_beat-1 | File "/usr/local/lib/python3.10/site-packages/django_celery_beat/schedulers.py", line 180, in from_entry
nauto_read-celery_beat-1 | name=name, defaults=cls._unpack_fields(**entry),
nauto_read-celery_beat-1 | File "/usr/local/lib/python3.10/site-packages/django_celery_beat/schedulers.py", line 187, in _unpack_fields
nauto_read-celery_beat-1 | model_schedule, model_field = cls.to_model_schedule(schedule)
nauto_read-celery_beat-1 | File "/usr/local/lib/python3.10/site-packages/django_celery_beat/schedulers.py", line 172, in to_model_schedule
nauto_read-celery_beat-1 | model_schedule.save()
nauto_read-celery_beat-1 | File "/usr/local/lib/python3.10/site-packages/django/db/models/base.py", line 739, in save
nauto_read-celery_beat-1 | self.save_base(using=using, force_insert=force_insert,
nauto_read-celery_beat-1 | File "/usr/local/lib/python3.10/site-packages/django/db/models/base.py", line 776, in save_base
nauto_read-celery_beat-1 | updated = self._save_table(
nauto_read-celery_beat-1 | File "/usr/local/lib/python3.10/site-packages/django/db/models/base.py", line 858, in _save_table
nauto_read-celery_beat-1 | updated = self._do_update(base_qs, using, pk_val, values, update_fields,
nauto_read-celery_beat-1 | File "/usr/local/lib/python3.10/site-packages/django/db/models/base.py", line 912, in _do_update
nauto_read-celery_beat-1 | return filtered._update(values) > 0
nauto_read-celery_beat-1 | File "/usr/local/lib/python3.10/site-packages/django/db/models/query.py", line 802, in _update
nauto_read-celery_beat-1 | return query.get_compiler(self.db).execute_sql(CURSOR)
nauto_read-celery_beat-1 | File "/usr/local/lib/python3.10/site-packages/django/db/models/sql/compiler.py", line 1559, in execute_sql
nauto_read-celery_beat-1 | cursor = super().execute_sql(result_type)
nauto_read-celery_beat-1 | File "/usr/local/lib/python3.10/site-packages/django/db/models/sql/compiler.py", line 1175, in execute_sql
nauto_read-celery_beat-1 | cursor.execute(sql, params)
nauto_read-celery_beat-1 | File "/usr/local/lib/python3.10/site-packages/cacheops/transaction.py", line 97, in execute
nauto_read-celery_beat-1 | result = self._no_monkey.execute(self, sql, params)
nauto_read-celery_beat-1 | File "/usr/local/lib/python3.10/site-packages/django/db/backends/utils.py", line 66, in execute
nauto_read-celery_beat-1 | return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
nauto_read-celery_beat-1 | File "/usr/local/lib/python3.10/site-packages/django/db/backends/utils.py", line 75, in _execute_with_wrappers
nauto_read-celery_beat-1 | return executor(sql, params, many, context)
nauto_read-celery_beat-1 | File "/usr/local/lib/python3.10/site-packages/django/db/backends/utils.py", line 79, in _execute
nauto_read-celery_beat-1 | with self.db.wrap_database_errors:
nauto_read-celery_beat-1 | File "/usr/local/lib/python3.10/site-packages/django/db/utils.py", line 90, in __exit__
nauto_read-celery_beat-1 | raise dj_exc_value.with_traceback(traceback) from exc_value
nauto_read-celery_beat-1 | File "/usr/local/lib/python3.10/site-packages/django/db/backends/utils.py", line 84, in _execute
nauto_read-celery_beat-1 | return self.cursor.execute(sql, params)
nauto_read-celery_beat-1 | django.db.utils.InternalError: cannot execute UPDATE in a read-only transaction
nauto_read-celery_beat-1 |
nauto_read-nautobot-1 | Traceback (most recent call last):
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/django/db/models/query.py", line 581, in get_or_create
nauto_read-nautobot-1 | return self.get(**kwargs), False
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/cacheops/query.py", line 351, in get
nauto_read-nautobot-1 | return qs._no_monkey.get(qs, *args, **kwargs)
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/django/db/models/query.py", line 435, in get
nauto_read-nautobot-1 | raise self.model.DoesNotExist(
nauto_read-nautobot-1 | django.contrib.contenttypes.models.ContentType.DoesNotExist: ContentType matching query does not exist.
nauto_read-nautobot-1 |
nauto_read-nautobot-1 | During handling of the above exception, another exception occurred:
nauto_read-nautobot-1 |
nauto_read-nautobot-1 | Traceback (most recent call last):
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/django/db/backends/utils.py", line 84, in _execute
nauto_read-nautobot-1 | return self.cursor.execute(sql, params)
nauto_read-nautobot-1 | psycopg2.errors.ReadOnlySqlTransaction: cannot execute INSERT in a read-only transaction
nauto_read-nautobot-1 |
nauto_read-nautobot-1 |
nauto_read-nautobot-1 | The above exception was the direct cause of the following exception:
nauto_read-nautobot-1 |
nauto_read-nautobot-1 | Traceback (most recent call last):
nauto_read-nautobot-1 | File "/usr/local/bin/nautobot-server", line 8, in <module>
nauto_read-nautobot-1 | sys.exit(main())
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/nautobot/core/cli.py", line 54, in main
nauto_read-nautobot-1 | run_app(
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/nautobot/core/runner/runner.py", line 266, in run_app
nauto_read-nautobot-1 | management.execute_from_command_line([runner_name, command] + command_args)
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/django/core/management/__init__.py", line 419, in execute_from_command_line
nauto_read-nautobot-1 | utility.execute()
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/django/core/management/__init__.py", line 413, in execute
nauto_read-nautobot-1 | self.fetch_command(subcommand).run_from_argv(self.argv)
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/django/core/management/base.py", line 354, in run_from_argv
nauto_read-nautobot-1 | self.execute(*args, **cmd_options)
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/django/core/management/base.py", line 398, in execute
nauto_read-nautobot-1 | output = self.handle(*args, **options)
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/nautobot/core/management/commands/post_upgrade.py", line 78, in handle
nauto_read-nautobot-1 | call_command(
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/django/core/management/__init__.py", line 181, in call_command
nauto_read-nautobot-1 | return command.execute(*args, **defaults)
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/django/core/management/base.py", line 398, in execute
nauto_read-nautobot-1 | output = self.handle(*args, **options)
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/django/core/management/base.py", line 89, in wrapped
nauto_read-nautobot-1 | res = handle_func(*args, **kwargs)
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/django/core/management/commands/migrate.py", line 268, in handle
nauto_read-nautobot-1 | emit_post_migrate_signal(
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/django/core/management/sql.py", line 42, in emit_post_migrate_signal
nauto_read-nautobot-1 | models.signals.post_migrate.send(
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/django/dispatch/dispatcher.py", line 180, in send
nauto_read-nautobot-1 | return [
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/django/dispatch/dispatcher.py", line 181, in <listcomp>
nauto_read-nautobot-1 | (receiver, receiver(signal=self, sender=sender, **named))
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/constance/apps.py", line 28, in create_perm
nauto_read-nautobot-1 | content_type, created = ContentType.objects.using(using).get_or_create(
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/django/db/models/query.py", line 588, in get_or_create
nauto_read-nautobot-1 | return self.create(**params), True
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/django/db/models/query.py", line 453, in create
nauto_read-nautobot-1 | obj.save(force_insert=True, using=self.db)
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/django/db/models/base.py", line 739, in save
nauto_read-nautobot-1 | self.save_base(using=using, force_insert=force_insert,
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/django/db/models/base.py", line 776, in save_base
nauto_read-nautobot-1 | updated = self._save_table(
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/django/db/models/base.py", line 881, in _save_table
nauto_read-nautobot-1 | results = self._do_insert(cls._base_manager, using, fields, returning_fields, raw)
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/django/db/models/base.py", line 919, in _do_insert
nauto_read-nautobot-1 | return manager._insert(
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/django/db/models/manager.py", line 85, in manager_method
nauto_read-nautobot-1 | return getattr(self.get_queryset(), name)(*args, **kwargs)
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/django/db/models/query.py", line 1270, in _insert
nauto_read-nautobot-1 | return query.get_compiler(using=using).execute_sql(returning_fields)
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/django/db/models/sql/compiler.py", line 1416, in execute_sql
nauto_read-nautobot-1 | cursor.execute(sql, params)
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/cacheops/transaction.py", line 97, in execute
nauto_read-nautobot-1 | result = self._no_monkey.execute(self, sql, params)
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/django/db/backends/utils.py", line 66, in execute
nauto_read-nautobot-1 | return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/django/db/backends/utils.py", line 75, in _execute_with_wrappers
nauto_read-nautobot-1 | return executor(sql, params, many, context)
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/django/db/backends/utils.py", line 79, in _execute
nauto_read-nautobot-1 | with self.db.wrap_database_errors:
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/django/db/utils.py", line 90, in __exit__
nauto_read-nautobot-1 | raise dj_exc_value.with_traceback(traceback) from exc_value
nauto_read-nautobot-1 | File "/usr/local/lib/python3.10/site-packages/django/db/backends/utils.py", line 84, in _execute
nauto_read-nautobot-1 | return self.cursor.execute(sql, params)
nauto_read-nautobot-1 | django.db.utils.InternalError: cannot execute INSERT in a read-only transaction
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Comments: 16 (10 by maintainers)
Commits related to this issue
- Resolves #2477 - Standup read-only instance — committed to gneville-ot/nautobot by gneville-ot 2 years ago
- add changelog notes for fixing #2477 — committed to gneville-ot/nautobot by gneville-ot 2 years ago
- Fix last login time being updated during maintenance mode with remote user authentication (#2656) * Check for maintenance mode during the NautobotConfig ready signal and disconnect update_last_login ... — committed to nautobot/nautobot by joaopsys 2 years ago
When it comes to that authentiction error @gneville-ot and @jathanism, I believe Nautobot still tries to update the last login time under
MAINTENANCE_MODE
in some cases, for example when it comes to remote user authentication.The
MAINTENANCE_MODE
check exists inusers/views.py
- https://github.com/nautobot/nautobot/blob/21c85f25e6458ad97e62484bc89e5effa1328a80/nautobot/users/views.py#L67However remote user authentication doesn’t seem to pass that check, it goes straight to
core/middleware.py
and is passed to Django, right? https://github.com/nautobot/nautobot/blob/21c85f25e6458ad97e62484bc89e5effa1328a80/nautobot/core/middleware.py#L23I have fixed this by adding the following to
core/middleware.py
underprocess_request
underclass RemoteUserMiddleware(RemoteUserMiddleware_):
I have no clue if this is the right approach though, but it did fix it for me.