Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure Unique Config Plans during migrations #884

Merged
merged 4 commits into from
Feb 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/781.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed UniqueViolation error when applying migration 0029 with multiple config plans sharing same device, date and plan_type.
Original file line number Diff line number Diff line change
@@ -1,6 +1,31 @@
# Generated by Django 3.2.20 on 2023-09-16 17:31

from django.db import migrations
import secrets
from datetime import timedelta

from django.db import migrations, models


def ensure_config_plan_created_timestamps_are_unique(apps, schema_editor):
ConfigPlan = apps.get_model("nautobot_golden_config", "ConfigPlan")
natural_key_fields = ["plan_type", "device", "created"]

# We append some random milliseconds of time to avoid duplicate timestamps for Config Plan objects created field

duplicate_records = (
ConfigPlan.objects.values(*natural_key_fields)
.order_by()
.annotate(
count=models.Count("pk"),
)
.filter(count__gt=1)
)

for duplicate_record in duplicate_records:
duplicate_record.pop("count")
for record in ConfigPlan.objects.filter(**duplicate_record):
record.created += timedelta(milliseconds=secrets.randbelow(1000))
record.save()


class Migration(migrations.Migration):
Expand All @@ -10,6 +35,13 @@ class Migration(migrations.Migration):
]

operations = [
# I know that data migrations are not recommended in the same migration as schema changes,
# but since there are already released migrations with users that had successful migrations
# doing this is the only way to ensure that we don't break their data, but are also able to fix the issue.
migrations.RunPython(
code=ensure_config_plan_created_timestamps_are_unique,
reverse_code=migrations.RunPython.noop,
),
migrations.AlterUniqueTogether(
name="configplan",
unique_together={("plan_type", "device", "created")},
Expand Down
6 changes: 4 additions & 2 deletions nautobot_golden_config/utilities/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
from nautobot.core.utils.data import render_jinja2
from nautobot.dcim.filters import DeviceFilterSet
from nautobot.dcim.models import Device
from nautobot.extras.models import Job
from nautobot.extras.choices import DynamicGroupTypeChoices
from nautobot.extras.models import Job
from nornir_nautobot.exceptions import NornirNautobotException

from nautobot_golden_config import config as app_config
Expand Down Expand Up @@ -71,7 +71,9 @@ def get_job_filter(data=None):

raw_qs = Q()
# If scope is set to {} do not loop as all devices are in scope.
if not models.GoldenConfigSetting.objects.filter(dynamic_group__filter__iexact="{}", dynamic_group__group_type=DynamicGroupTypeChoices.TYPE_DYNAMIC_FILTER).exists():
if not models.GoldenConfigSetting.objects.filter(
dynamic_group__filter__iexact="{}", dynamic_group__group_type=DynamicGroupTypeChoices.TYPE_DYNAMIC_FILTER
).exists():
for obj in models.GoldenConfigSetting.objects.all():
raw_qs = raw_qs | obj.dynamic_group.generate_query()

Expand Down