Unable to stand up nautobot in a HA mode (one write instance & one read-only instance)
See original GitHub issueEnvironment
- 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
Issue Analytics
- State:
- Created a year ago
- Comments:16 (10 by maintainers)
Top Results From Across the Web
Optional Configuration Settings - Nautobot Documentation
The Docker container normally attempts to run migrations on startup; however, if the database is in a read-only state the Docker container will...
Read more >Developing Nautobot Plugins - Part 1 - NTC Blog
This first post will provide an overview of Nautobot plugins and will cover getting started, including setting up a development environment ...
Read more >Nautobot Solution Guide: Source of Truth for Network ...
For instance, configuration could be copied from one top-of-rack, or leaf switch ... vOps opens up the idea to look at network Infrastructure...
Read more >Transformation Services - Network to Code
Digital transformation has driven a sharp rise in application complexity. ... This all leads to one inevitable conclusion: data rules.
Read more >Getting Nautobot Up and Running in the Lab - NTC Blog
This all-in-one Docker container is Nautobot Lab. With Nautobot Lab, users are able to spin up the latest instance of Nautobot, ...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
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.
At a glance, looks like an issue with social_core/social_django
get_and_store_nonce()
, probably https://python-social-auth.readthedocs.io/en/latest/storage.html. It looks like it might be necessary to find or write an alternative toDjangoAssociationMixin
that understands Nautobot’sMAINTENANCE_MODE
setting and doesn’t try to write to the DB in that case.