From 1df2534cf359371d267b9261e5ccb4cc5e0458f9 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Thu, 5 Nov 2015 20:54:21 +0000 Subject: [PATCH] Add DNS models This commit add model classes closely matching the tables defined in PowerDNS' schema as described at https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/. The commit includes the model definitions a schema migration including PostgreSQL specific CHECK constraints and the registration in the Django admin interface. addresses #17 --- gnuviechadmin/domains/admin.py | 16 +- .../migrations/0003_auto_20151105_2133.py | 159 ++++++++++++ gnuviechadmin/domains/models.py | 235 +++++++++++++++++- 3 files changed, 408 insertions(+), 2 deletions(-) create mode 100644 gnuviechadmin/domains/migrations/0003_auto_20151105_2133.py diff --git a/gnuviechadmin/domains/admin.py b/gnuviechadmin/domains/admin.py index 997b49d..87be497 100644 --- a/gnuviechadmin/domains/admin.py +++ b/gnuviechadmin/domains/admin.py @@ -6,9 +6,23 @@ with the django admin site. from django.contrib import admin from .models import ( - MailDomain, + DNSComment, + DNSCryptoKey, + DNSDomain, + DNSDomainMetadata, + DNSRecord, + DNSSupermaster, + DNSTSIGKey, HostingDomain, + MailDomain, ) admin.site.register(MailDomain) admin.site.register(HostingDomain) +admin.site.register(DNSComment) +admin.site.register(DNSCryptoKey) +admin.site.register(DNSDomain) +admin.site.register(DNSDomainMetadata) +admin.site.register(DNSRecord) +admin.site.register(DNSSupermaster) +admin.site.register(DNSTSIGKey) diff --git a/gnuviechadmin/domains/migrations/0003_auto_20151105_2133.py b/gnuviechadmin/domains/migrations/0003_auto_20151105_2133.py new file mode 100644 index 0000000..caf2e72 --- /dev/null +++ b/gnuviechadmin/domains/migrations/0003_auto_20151105_2133.py @@ -0,0 +1,159 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import django.utils.timezone +from django.conf import settings +import model_utils.fields + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('domains', '0002_auto_20150124_1909'), + ] + + operations = [ + migrations.CreateModel( + name='DNSComment', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('name', models.CharField(max_length=255)), + ('commenttype', models.CharField(max_length=10, db_column='type')), + ('modified_at', models.IntegerField()), + ('comment', models.CharField(max_length=65535)), + ('customer', models.ForeignKey(verbose_name='customer', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.RunSQL( + '''ALTER TABLE domains_dnscomment ADD CONSTRAINT c_lowercase_name + CHECK (((name)::TEXT = LOWER((name)::TEXT)))''' + ), + migrations.CreateModel( + name='DNSCryptoKey', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('flags', models.IntegerField()), + ('active', models.BooleanField(default=True)), + ('content', models.TextField()), + ], + ), + migrations.CreateModel( + name='DNSDomain', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), + ('domain', models.CharField(unique=True, max_length=255, verbose_name='domain name')), + ('master', models.CharField(max_length=128, null=True, blank=True)), + ('last_check', models.IntegerField(null=True)), + ('domaintype', models.CharField(max_length=6, db_column='type', choices=[('MASTER', 'Master'), ('SLAVE', 'Slave'), ('NATIVE', 'Native')])), + ('notified_serial', models.IntegerField(null=True)), + ('customer', models.ForeignKey(verbose_name='customer', blank=True, to=settings.AUTH_USER_MODEL, null=True)), + ], + options={ + 'verbose_name': 'DNS domain', + 'verbose_name_plural': 'DNS domains', + }, + ), + migrations.RunSQL( + '''ALTER TABLE domains_dnsdomain ADD CONSTRAINT c_lowercase_name + CHECK (((domain)::TEXT = LOWER((domain)::TEXT)))''' + ), + migrations.CreateModel( + name='DNSDomainMetadata', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('kind', models.CharField(max_length=32)), + ('content', models.TextField()), + ('domain', models.ForeignKey(to='domains.DNSDomain')), + ], + ), + migrations.CreateModel( + name='DNSRecord', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('name', models.CharField(db_index=True, max_length=255, null=True, blank=True)), + ('recordtype', models.CharField(max_length=10, null=True, db_column='type', blank=True)), + ('content', models.CharField(max_length=65535, null=True, blank=True)), + ('ttl', models.IntegerField(null=True)), + ('prio', models.IntegerField(null=True)), + ('change_date', models.IntegerField(null=True)), + ('disabled', models.BooleanField(default=False)), + ('ordername', models.CharField(max_length=255)), + ('auth', models.BooleanField(default=True)), + ('domain', models.ForeignKey(to='domains.DNSDomain')), + ], + options={ + 'verbose_name': 'DNS record', + 'verbose_name_plural': 'DNS records', + }, + ), + migrations.RunSQL( + '''ALTER TABLE domains_dnsrecord ADD CONSTRAINT c_lowercase_name + CHECK (((name)::TEXT = LOWER((name)::TEXT)))''' + ), + migrations.RunSQL( + '''CREATE INDEX recordorder ON domains_dnsrecord (domain_id, + ordername text_pattern_ops)''' + ), + migrations.CreateModel( + name='DNSSupermaster', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('ip', models.GenericIPAddressField()), + ('nameserver', models.CharField(max_length=255)), + ('customer', models.ForeignKey(verbose_name='customer', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='DNSTSIGKey', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('name', models.CharField(max_length=255)), + ('algorithm', models.CharField(max_length=50)), + ('secret', models.CharField(max_length=255)), + ], + ), + migrations.RunSQL( + '''ALTER TABLE domains_dnstsigkey ADD CONSTRAINT c_lowercase_name + CHECK (((name)::TEXT = LOWER((name)::TEXT)))''' + ), + migrations.AlterField( + model_name='hostingdomain', + name='domain', + field=models.CharField(unique=True, max_length=255, verbose_name='domain name'), + ), + migrations.AlterField( + model_name='maildomain', + name='domain', + field=models.CharField(unique=True, max_length=255, verbose_name='domain name'), + ), + migrations.AddField( + model_name='dnscryptokey', + name='domain', + field=models.ForeignKey(to='domains.DNSDomain'), + ), + migrations.AddField( + model_name='dnscomment', + name='domain', + field=models.ForeignKey(to='domains.DNSDomain'), + ), + migrations.AlterUniqueTogether( + name='dnssupermaster', + unique_together=set([('ip', 'nameserver')]), + ), + migrations.AlterUniqueTogether( + name='dnstsigkey', + unique_together=set([('name', 'algorithm')]), + ), + migrations.AlterIndexTogether( + name='dnsrecord', + index_together=set([('name', 'recordtype')]), + ), + migrations.AlterIndexTogether( + name='dnscomment', + index_together=set([('name', 'commenttype'), ('domain', 'modified_at')]), + ), + ] diff --git a/gnuviechadmin/domains/models.py b/gnuviechadmin/domains/models.py index d545964..d94cb3c 100644 --- a/gnuviechadmin/domains/models.py +++ b/gnuviechadmin/domains/models.py @@ -10,6 +10,14 @@ from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext as _ from model_utils.models import TimeStampedModel +from model_utils import Choices + + +DNS_DOMAIN_TYPES = Choices( + ('MASTER', _('Master')), + ('SLAVE', _('Slave')), + ('NATIVE', _('Native')), +) @python_2_unicode_compatible @@ -18,7 +26,7 @@ class DomainBase(TimeStampedModel): This is the base model for domains. """ - domain = models.CharField(_('domain name'), max_length=128, unique=True) + domain = models.CharField(_('domain name'), max_length=255, unique=True) customer = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name=_('customer'), blank=True, null=True) @@ -96,3 +104,228 @@ class HostingDomain(DomainBase): def __str__(self): return self.domain + + +@python_2_unicode_compatible +class DNSDomain(DomainBase): + """ + This model represents a DNS zone. The model is similar to the domain table + in the PowerDNS schema specified in + https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/. + + CREATE TABLE domains ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + master VARCHAR(128) DEFAULT NULL, + last_check INT DEFAULT NULL, + type VARCHAR(6) NOT NULL, + notified_serial INT DEFAULT NULL, + account VARCHAR(40) DEFAULT NULL, + CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT))) + ); + + CREATE UNIQUE INDEX name_index ON domains(name); + + """ + # name is represented by domain + master = models.CharField(max_length=128, blank=True, null=True) + last_check = models.IntegerField(null=True) + domaintype = models.CharField(max_length=6, choices=DNS_DOMAIN_TYPES, + db_column='type') + notified_serial = models.IntegerField(null=True) + # account is represented by customer_id + # check constraint is added via RunSQL in migration + + class Meta: + verbose_name = _('DNS domain') + verbose_name_plural = _('DNS domains') + + def __str__(self): + return self.domain + + +class DNSRecord(models.Model): + """ + This model represents a DNS record. The model is similar to the record + table in the PowerDNS schema specified in + https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/. + + CREATE TABLE records ( + id SERIAL PRIMARY KEY, + domain_id INT DEFAULT NULL, + name VARCHAR(255) DEFAULT NULL, + type VARCHAR(10) DEFAULT NULL, + content VARCHAR(65535) DEFAULT NULL, + ttl INT DEFAULT NULL, + prio INT DEFAULT NULL, + change_date INT DEFAULT NULL, + disabled BOOL DEFAULT 'f', + ordername VARCHAR(255), + auth BOOL DEFAULT 't', + CONSTRAINT domain_exists + FOREIGN KEY(domain_id) REFERENCES domains(id) + ON DELETE CASCADE, + CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT))) + ); + + CREATE INDEX rec_name_index ON records(name); + CREATE INDEX nametype_index ON records(name,type); + CREATE INDEX domain_id ON records(domain_id); + CREATE INDEX recordorder ON records (domain_id, ordername text_pattern_ops); + + """ + domain = models.ForeignKey('DNSDomain') + name = models.CharField(max_length=255, blank=True, null=True, + db_index=True) + recordtype = models.CharField(max_length=10, blank=True, null=True, + db_column='type') + content = models.CharField(max_length=65535, blank=True, null=True) + ttl = models.IntegerField(null=True) + prio = models.IntegerField(null=True) + change_date = models.IntegerField(null=True) + disabled = models.BooleanField(default=False) + ordername = models.CharField(max_length=255) + auth = models.BooleanField(default=True) + # check constraint and index recordorder are added via RunSQL in migration + + class Meta: + verbose_name = _('DNS record') + verbose_name_plural = _('DNS records') + index_together = [ + ['name', 'recordtype'] + ] + + +class DNSSupermaster(models.Model): + """ + This model represents the supermasters table in the PowerDNS schema + specified in + https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/. + + CREATE TABLE supermasters ( + ip INET NOT NULL, + nameserver VARCHAR(255) NOT NULL, + account VARCHAR(40) NOT NULL, + PRIMARY KEY(ip, nameserver) + ); + + """ + ip = models.GenericIPAddressField() + nameserver = models.CharField(max_length=255) + # account is replaced by customer + customer = models.ForeignKey( + settings.AUTH_USER_MODEL, verbose_name=_('customer')) + + class Meta: + unique_together = ( + ('ip', 'nameserver') + ) + + +class DNSComment(models.Model): + """ + This model represents the comments table in the PowerDNS schema specified + in https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/. + + CREATE TABLE comments ( + id SERIAL PRIMARY KEY, + domain_id INT NOT NULL, + name VARCHAR(255) NOT NULL, + type VARCHAR(10) NOT NULL, + modified_at INT NOT NULL, + account VARCHAR(40) DEFAULT NULL, + comment VARCHAR(65535) NOT NULL, + CONSTRAINT domain_exists + FOREIGN KEY(domain_id) REFERENCES domains(id) + ON DELETE CASCADE, + CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT))) + ); + + CREATE INDEX comments_domain_id_idx ON comments (domain_id); + CREATE INDEX comments_name_type_idx ON comments (name, type); + CREATE INDEX comments_order_idx ON comments (domain_id, modified_at); + + """ + domain = models.ForeignKey('DNSDomain') + name = models.CharField(max_length=255) + commenttype = models.CharField(max_length=10, db_column='type') + modified_at = models.IntegerField() + # account is replaced by customer + customer = models.ForeignKey( + settings.AUTH_USER_MODEL, verbose_name=_('customer')) + comment = models.CharField(max_length=65535) + # check constraint is added via RunSQL in migration + + class Meta: + index_together = [ + ['name', 'commenttype'], + ['domain', 'modified_at'] + ] + + +class DNSDomainMetadata(models.Model): + """ + This model represents the domainmetadata table in the PowerDNS schema + specified in + https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/. + + CREATE TABLE domainmetadata ( + id SERIAL PRIMARY KEY, + domain_id INT REFERENCES domains(id) ON DELETE CASCADE, + kind VARCHAR(32), + content TEXT + ); + + CREATE INDEX domainidmetaindex ON domainmetadata(domain_id); + """ + domain = models.ForeignKey('DNSDomain') + kind = models.CharField(max_length=32) + content = models.TextField() + + +class DNSCryptoKey(models.Model): + """ + This model represents the cryptokeys table in the PowerDNS schema + specified in + https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/. + + CREATE TABLE cryptokeys ( + id SERIAL PRIMARY KEY, + domain_id INT REFERENCES domains(id) ON DELETE CASCADE, + flags INT NOT NULL, + active BOOL, + content TEXT + ); + + CREATE INDEX domainidindex ON cryptokeys(domain_id); + """ + domain = models.ForeignKey('DNSDomain') + flags = models.IntegerField() + active = models.BooleanField(default=True) + content = models.TextField() + + +class DNSTSIGKey(models.Model): + """ + This model represents the tsigkeys table in the PowerDNS schema specified + in https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/. + + CREATE TABLE tsigkeys ( + id SERIAL PRIMARY KEY, + name VARCHAR(255), + algorithm VARCHAR(50), + secret VARCHAR(255), + CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT))) + ); + + CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm); + """ + name = models.CharField(max_length=255) + algorithm = models.CharField(max_length=50) + secret = models.CharField(max_length=255) + # check constraint is added via RunSQL in migration + + class Meta: + unique_together = [ + ['name', 'algorithm'] + ]