Skip to content
Browse files

backporting stuff from inventory.

Conflicts:
	cyder/cydhcp/interface/static_intr/models.py
	cyder/cydns/address_record/models.py
	cyder/cydns/tests/test_views.py
	cyder/settings/base.py
  • Loading branch information...
1 parent 61c1ee8 commit c1dbb66ffb011de247d5a5b1402fb5977f59d8b1 @uberj uberj committed
Showing with 2,557 additions and 1,348 deletions.
  1. +2 −0 README.md
  2. 0 cyder/core/task/__init__.py
  3. +31 −0 cyder/core/task/models.py
  4. +51 −0 cyder/core/utils.py
  5. +57 −68 cyder/cydhcp/interface/static_intr/models.py
  6. +13 −16 cyder/cydhcp/lib/tests/free_ip.py
  7. +20 −23 cyder/cydhcp/lib/tests/intr_from_range.py
  8. +5 −5 cyder/cydhcp/network/models.py
  9. +19 −11 cyder/cydhcp/range/models.py
  10. +0 −4 cyder/cydhcp/site/models.py
  11. +2 −2 cyder/cydns/address_record/forms.py
  12. +59 −108 cyder/cydns/address_record/models.py
  13. +4 −3 cyder/cydns/cname/forms.py
  14. +36 −31 cyder/cydns/cname/models.py
  15. +25 −26 cyder/cydns/cname/tests/test_models.py
  16. +466 −201 cyder/cydns/cybind/builder.py
  17. +1 −0 cyder/cydns/cybind/test_models.py
  18. +107 −49 cyder/cydns/cybind/tests/build_tests.py
  19. +114 −80 cyder/cydns/cybind/tests/dirty_soa.py
  20. 0 cyder/cydns/cybind/tests/{all.py → test_models.py}
  21. +90 −85 cyder/cydns/cybind/zone_builder.py
  22. +64 −48 cyder/cydns/domain/models.py
  23. +16 −19 cyder/cydns/domain/tests/auto_delete.py
  24. +3 −8 cyder/cydns/domain/tests/auto_update.py
  25. +64 −35 cyder/cydns/domain/tests/full_name.py
  26. +81 −0 cyder/cydns/forms.py
  27. +40 −48 cyder/cydns/ip/models.py
  28. +1 −0 cyder/cydns/ip/tests/test_models.py
  29. +10 −8 cyder/cydns/ip/utils.py
  30. +139 −86 cyder/cydns/models.py
  31. +2 −2 cyder/cydns/mx/forms.py
  32. +8 −12 cyder/cydns/mx/models.py
  33. +3 −4 cyder/cydns/nameserver/forms.py
  34. +63 −61 cyder/cydns/nameserver/models.py
  35. +275 −0 cyder/cydns/nameserver/tests/test_models.py
  36. +5 −2 cyder/cydns/ptr/forms.py
  37. +38 −33 cyder/cydns/ptr/models.py
  38. +45 −25 cyder/cydns/soa/models.py
  39. +3 −3 cyder/cydns/srv/forms.py
  40. +20 −107 cyder/cydns/srv/models.py
  41. +27 −12 cyder/cydns/srv/tests/test_models.py
  42. +3 −4 cyder/cydns/sshfp/forms.py
  43. +23 −27 cyder/cydns/sshfp/models.py
  44. +13 −15 cyder/cydns/sshfp/tests/test_models.py
  45. +128 −30 cyder/cydns/tests/test_views.py
  46. +171 −0 cyder/cydns/tests/utils.py
  47. +3 −3 cyder/cydns/txt/forms.py
  48. +4 −21 cyder/cydns/txt/models.py
  49. +4 −12 cyder/cydns/txt/tests/test_models.py
  50. +6 −2 cyder/cydns/utils.py
  51. +1 −1 cyder/cydns/validation.py
  52. +2 −3 cyder/cydns/view/forms.py
  53. 0 cyder/scripts/__init__.py
  54. 0 cyder/scripts/dnsbuilds/__init__.py
  55. 0 cyder/{cydns/cybind → scripts}/dnsbuilds/main.py
  56. 0 cyder/scripts/dnsbuilds/tests/__init__.py
  57. +165 −0 cyder/scripts/dnsbuilds/tests/build_tests.py
  58. +5 −4 cyder/settings/base.py
  59. +20 −1 cyder/settings/dnsbuilds.py
View
2 README.md
@@ -24,6 +24,8 @@ Installation
Install dependencies. (virtualenv recommended)
+#TODO sudo yum install openldap-devel on fedora
+
```
sudo apt-get install python-dev libldap2-dev libsasl2-dev libssl-dev
git submodule update --init --recursive
View
0 cyder/core/task/__init__.py
No changes.
View
31 cyder/core/task/models.py
@@ -0,0 +1,31 @@
+from django.db import models
+
+
+class DNSManager(models.Manager):
+ def get_queryset(self):
+ return super(DNSManager, self).get_queryset().filter(ttype='dns')
+
+
+class Task(models.Model):
+ task = models.CharField(max_length=255, blank=False)
+ ttype = models.CharField(max_length=255, blank=False)
+
+ objects = models.Manager()
+ dns = DNSManager()
+
+ class Meta:
+ db_table = u'task'
+ ordering = ['task']
+
+ def __repr__(self):
+ return "<Task: {0}>".format(self)
+
+ def __str__(self):
+ return "{0} {1}".format(self.ttype, self.task)
+
+ def save(self):
+ super(Task, self).save()
+
+ @classmethod
+ def schedule_zone_rebuild(cls, soa):
+ Task(task=str(soa.pk), ttype='dns').save()
View
51 cyder/core/utils.py
@@ -0,0 +1,51 @@
+from email.mime.text import MIMEText
+import smtplib
+
+
+# Reference http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html
+# TODO, use this on all views touching DNS stuff
+def locked_function(lock_name, timeout=10):
+ """
+ This is a decorator that should be used around any view or function that
+ modifies, creates, or deletes a DNS model.
+ It's purpose is to prevent this case:
+
+ http://people.mozilla.com/~juber/public/t1_t2_scenario.txt
+
+ """
+ def decorator(f):
+ def new_function(*args, **kwargs):
+ from django.db import connection
+ cursor = connection.cursor()
+ cursor.execute(
+ "SELECT GET_LOCK('{lock_name}', {timeout});".format(
+ lock_name=lock_name, timeout=timeout
+ )
+ )
+ ret = f(*args, **kwargs)
+ cursor.execute(
+ "SELECT RELEASE_LOCK('{lock_name}');".format(
+ lock_name=lock_name
+ )
+ )
+ return ret
+ return new_function
+ return decorator
+
+# TODO, move this into a config file and decide on an email to send errors to.
+people_who_need_to_know_about_failures = ''
+inventorys_email = ''
+
+
+def fail_mail(content, subject='Inventory is having issues.',
+ to=people_who_need_to_know_about_failures,
+ from_=inventorys_email):
+ """Send email about a failure."""
+ return # TODO, enable this in production
+ msg = MIMEText(content)
+ msg['Subject'] = subject
+ msg['From'] = inventorys_email
+ # msg['To'] = to
+ s = smtplib.SMTP('localhost')
+ s.sendmail(from_, to, msg.as_string())
+ s.quit()
View
125 cyder/cydhcp/interface/static_intr/models.py
@@ -17,11 +17,8 @@
from cyder.cydns.address_record.models import AddressRecord, BaseAddressRecord
from cyder.cydns.ip.utils import ip_to_dns_form
-from cyder.cydns.view.models import View
from cyder.cydns.domain.models import Domain
-# import reversion
-
class StaticInterface(BaseAddressRecord, models.Model, ObjectUrlMixin):
"""The StaticInterface Class.
@@ -84,47 +81,55 @@ class StaticInterface(BaseAddressRecord, models.Model, ObjectUrlMixin):
"""
id = models.AutoField(primary_key=True)
mac = models.CharField(max_length=17, validators=[validate_mac],
- help_text="Mac address in format XX:XX:XX:XX:XX:XX")
+ help_text='Mac address in format XX:XX:XX:XX:XX:XX')
reverse_domain = models.ForeignKey(Domain, null=True, blank=True,
- related_name="staticintrdomain_set")
+ related_name='staticintrdomain_set')
system = models.ForeignKey(
System, null=True, blank=True,
- help_text="System to associate the interface with")
+ help_text='System to associate the interface with')
vrf = models.ForeignKey(Vrf, null=True, blank=True)
workgroup = models.ForeignKey(Workgroup, null=True, blank=True)
dhcp_enabled = models.BooleanField(
- default=True, help_text="Enable dhcp for this interface?")
+ default=True, help_text='Enable dhcp for this interface?')
dns_enabled = models.BooleanField(
- default=True, help_text="Enable dns for this interface?")
+ default=True, help_text='Enable dns for this interface?')
attrs = None
- search_fields = ("mac", "ip_str", "fqdn")
+ search_fields = ('mac', 'ip_str', 'fqdn')
+
+ class Meta:
+ db_table = 'static_interface'
+ unique_together = ('ip_upper', 'ip_lower', 'label', 'domain', 'mac')
+
+ def __repr__(self):
+ return '<StaticInterface: {0}>'.format(str(self))
+
+ def __str__(self):
+ #return 'IP:{0} Full Name:{1} Mac:{2}'.format(self.ip_str,
+ # self.fqdn, self.mac)
+ return self.fqdn
def update_attrs(self):
- self.attrs = AuxAttr(StaticIntrKeyValue, self, "intr")
+ self.attrs = AuxAttr(StaticIntrKeyValue, self, 'intr')
def details(self):
data = super(StaticInterface, self).details()
data['data'] = (
- ("Name", 'fqdn', self),
- ("IP", 'ip_str', str(self.ip_str)),
- ("MAC", 'mac', self.mac),
- ("Vrf", 'vrf', self.vrf),
- ("Workgroup", 'workgroup', self.workgroup),
- ("DHCP Enabled", "dhcp_enabled",
- "True" if self.dhcp_enabled else "False"),
- ("DNS Enabled", "dns_enabled",
- "True" if self.dns_enabled else "False"),
- ("DNS Type", '', "A/PTR"),
+ ('Name', 'fqdn', self),
+ ('IP', 'ip_str', str(self.ip_str)),
+ ('MAC', 'mac', self.mac),
+ ('Vrf', 'vrf', self.vrf),
+ ('Workgroup', 'workgroup', self.workgroup),
+ ('DHCP Enabled', 'dhcp_enabled',
+ 'True' if self.dhcp_enabled else 'False'),
+ ('DNS Enabled', 'dns_enabled',
+ 'True' if self.dns_enabled else 'False'),
+ ('DNS Type', '', 'A/PTR'),
)
return data
- class Meta:
- db_table = "static_interface"
- unique_together = ("ip_upper", "ip_lower", "label", "domain", "mac")
-
@classmethod
def get_api_fields(cls):
return super(StaticInterface, cls).get_api_fields() + \
@@ -144,25 +149,24 @@ def interface_name(self):
except AttributeError:
pass
if itype == '' or primary == '':
- return "None"
+ return 'None'
elif alias == '':
- return "{0}{1}".format(itype, primary)
+ return '{0}{1}'.format(itype, primary)
else:
- return "{0}{1}.{2}".format(itype, primary, alias)
-
+ return '{0}{1}.{2}'.format(itype, primary, alias)
def build_host(self):
- join_args = lambda x: "\n".join(map(lambda y: "\t\t{0};".format(y)))
- build_str = "\thost {0} {{\n".format(self.fqdn)
- build_str += "\t\thardware ethernet {0};\n".format(self.mac)
- build_str += "\t\tfixed-address {0};\n".format(self.ip_str)
+ join_args = lambda x: '\n'.join(map(lambda y: '\t\t{0};'.format(y)))
+ build_str = '\thost {0} {{\n'.format(self.fqdn)
+ build_str += '\t\thardware ethernet {0};\n'.format(self.mac)
+ build_str += '\t\tfixed-address {0};\n'.format(self.ip_str)
options = self.static_intr_key_value_set.filter(is_option=True)
statements = self.statc_intr_key_value_set.filter(is_statement=True)
- build_str += "\t\t# Host Options\n"
+ build_str += '\t\t# Host Options\n'
build_str += join_args(options)
- build_str += "\t\t# Host Statements\n"
+ build_str += '\t\t# Host Statements\n'
build_str += join_args(statements)
- build_str += "\t}\n\n"
+ build_str += '\t}\n\n'
return build_str
@@ -172,35 +176,28 @@ def build_subclass(self, contained_range, allowed):
def clean(self, *args, **kwargs):
- #if not isinstance(self.mac, basestring):
- # raise ValidationError("Mac Address not of valid type.")
- #self.mac = self.mac.lower()
+ self.mac = self.mac.lower()
+ if not self.system:
+ raise ValidationError(
+ "An interface means nothing without it's system."
+ )
+
from cyder.cydns.ptr.models import PTR
- if not self.system:
- raise ValidationError("An interface means nothing without it's "
- "system.")
if PTR.objects.filter(ip_str=self.ip_str, name=self.fqdn).exists():
- raise ValidationError("A PTR already uses this Name and IP")
- if AddressRecord.objects.filter(
- ip_str=self.ip_str, fqdn=self.fqdn).exists():
- raise ValidationError("An A record already uses this Name and IP")
+ raise ValidationError('A PTR already uses this Name and IP')
+ if AddressRecord.objects.filter(ip_str=self.ip_str, fqdn=self.fqdn
+ ).exists():
+ raise ValidationError('An A record already uses this Name and IP')
- if kwargs.pop("validate_glue", True):
+ if kwargs.pop('validate_glue', True):
self.check_glue_status()
+ self.update_reverse_domain()
+ self.check_no_ns_soa_condition(self.reverse_domain)
super(StaticInterface, self).clean(validate_glue=False,
- update_reverse_domain=True,
ignore_interface=True)
- if self.pk and self.ip_str.startswith("10."):
- p = View.objects.filter(name="private")
- if p:
- self.views.add(p[0])
- super(StaticInterface, self).clean(validate_glue=False,
- update_reverse_domain=True,
- ignore_interface=True)
-
def check_glue_status(self):
"""If this interface is a 'glue' record for a Nameserver instance,
do not allow modifications to this record. The Nameserver will
@@ -236,30 +233,22 @@ def bind_render_record(self, pk=False, **kwargs):
return super(StaticInterface, self).bind_render_record(pk=pk, **kwargs)
def obj_type(self):
- return "A/PTR"
+ return 'A/PTR'
def delete(self, *args, **kwargs):
- if kwargs.pop("validate_glue", True):
+ if kwargs.pop('validate_glue', True):
if self.intrnameserver_set.exists():
raise ValidationError("Cannot delete the record {0}. "
"It is a glue record.".format(
self.obj_type()))
- check_cname = kwargs.pop("check_cname", True)
+ check_cname = kwargs.pop('check_cname', True)
super(StaticInterface, self).delete(validate_glue=False,
check_cname=check_cname)
- def __repr__(self):
- return "<StaticInterface: {0}>".format(str(self))
-
- def __str__(self):
- #return "IP:{0} Full Name:{1} Mac:{2}".format(self.ip_str,
- # self.fqdn, self.mac)
- return self.fqdn
-
class StaticIntrKeyValue(CommonOption):
intr = models.ForeignKey(StaticInterface, null=False)
class Meta:
- db_table = "static_intr_key_value"
- unique_together = ("key", "value", "intr")
+ db_table = 'static_intr_key_value'
+ unique_together = ('key', 'value', 'intr')
View
29 cyder/cydhcp/lib/tests/free_ip.py
@@ -8,35 +8,32 @@
from cyder.cydhcp.lib.utils import calc_free_ips_str
from cyder.cydhcp.lib.utils import create_ipv4_intr_from_range
from cyder.cydns.domain.models import Domain
-from cyder.cydns.soa.models import SOA
from cyder.core.system.models import System
+from cyder.cydns.tests.utils import create_fake_zone
+
class LibTestsFreeIP(TestCase):
def setUp(self):
self.system = System()
- Domain.objects.get_or_create(name="com")
- d1, _ = Domain.objects.get_or_create(name="mozilla.com")
- soa, _ = SOA.objects.get_or_create(
- primary="fo.bar", contact="foo.bar.com",
- description="foo bar")
- self.s = soa
- d1.soa = soa
- d1.save()
+ self.system.save()
+
+ d1 = create_fake_zone("oregonstate.com", suffix="")
+ soa = d1.soa
v, _ = Vlan.objects.get_or_create(name="private", number=3)
s, _ = Site.objects.get_or_create(name="phx1")
s1, _ = Site.objects.get_or_create(name="corp", parent=s)
- d, _ = Domain.objects.get_or_create(name="phx1.mozilla.com")
+ d, _ = Domain.objects.get_or_create(name="phx1.oregonstate.com")
d.soa = soa
d.save()
- d1, _ = Domain.objects.get_or_create(name="corp.phx1.mozilla.com")
+ d1, _ = Domain.objects.get_or_create(name="corp.phx1.oregonstate.com")
d1.soa = soa
d1.save()
d2, _ = Domain.objects.get_or_create(
- name="private.corp.phx1.mozilla.com")
+ name="private.corp.phx1.oregonstate.com")
d2.soa = soa
d2.save()
@@ -62,7 +59,7 @@ def test1_free_ip_count(self):
count = calc_free_ips_str("15.0.0.200", "15.0.0.204")
self.assertEqual(count, 4)
intr, errors = create_ipv4_intr_from_range("foo",
- "private.corp.phx1.mozilla.com", self.system,
+ "private.corp.phx1.oregonstate.com", self.system,
"11:22:33:44:55:66", "15.0.0.200", "15.0.0.204")
intr.save()
self.assertEqual(errors, None)
@@ -72,7 +69,7 @@ def test1_free_ip_count(self):
self.assertEqual(count, 3)
intr, errors = create_ipv4_intr_from_range("foo",
- "private.corp.phx1.mozilla.com", self.system,
+ "private.corp.phx1.oregonstate.com", self.system,
"11:22:33:44:55:66", "15.0.0.200", "15.0.0.204")
intr.save()
self.assertEqual(errors, None)
@@ -82,7 +79,7 @@ def test1_free_ip_count(self):
self.assertEqual(count, 2)
intr, errors = create_ipv4_intr_from_range("foo",
- "private.corp.phx1.mozilla.com", self.system,
+ "private.corp.phx1.oregonstate.com", self.system,
"11:22:33:44:55:66", "15.0.0.200", "15.0.0.204")
intr.save()
self.assertEqual(errors, None)
@@ -92,7 +89,7 @@ def test1_free_ip_count(self):
self.assertEqual(count, 1)
intr, errors = create_ipv4_intr_from_range("foo",
- "private.corp.phx1.mozilla.com", self.system,
+ "private.corp.phx1.oregonstate.com", self.system,
"11:22:33:44:55:66", "15.0.0.200", "15.0.0.204")
intr.save()
self.assertEqual(errors, None)
View
43 cyder/cydhcp/lib/tests/intr_from_range.py
@@ -8,35 +8,32 @@
from cyder.cydhcp.lib.utils import create_ipv4_intr_from_range
from cyder.cydns.domain.models import Domain
-from cyder.cydns.soa.models import SOA
from cyder.core.system.models import System
+from cyder.cydns.tests.utils import create_fake_zone
+
class LibTestsRange(TestCase):
def setUp(self):
self.system = System()
- Domain.objects.get_or_create(name="com")
- d1, _ = Domain.objects.get_or_create(name="mozilla.com")
- soa, _ = SOA.objects.get_or_create(
- primary="fo.bar", contact="foo.bar.com",
- description="foo bar")
- self.s = soa
- d1.soa = soa
- d1.save()
+ self.system.save()
+ d1 = create_fake_zone("oregonstate.com", suffix="")
+ soa = d1.soa
+ self.soa = soa
v, _ = Vlan.objects.get_or_create(name="private", number=3)
s, _ = Site.objects.get_or_create(name="phx1")
s1, _ = Site.objects.get_or_create(name="corp", parent=s)
- d, _ = Domain.objects.get_or_create(name="phx1.mozilla.com")
+ d, _ = Domain.objects.get_or_create(name="phx1.oregonstate.com")
d.soa = soa
d.save()
- d1, _ = Domain.objects.get_or_create(name="corp.phx1.mozilla.com")
+ d1, _ = Domain.objects.get_or_create(name="corp.phx1.oregonstate.com")
d1.soa = soa
d1.save()
d2, _ = Domain.objects.get_or_create(
- name="private.corp.phx1.mozilla.com")
+ name="private.corp.phx1.oregonstate.com")
d2.soa = soa
d2.save()
@@ -56,7 +53,7 @@ def setUp(self):
def test1_create_ipv4_interface_from_range(self):
intr, errors = create_ipv4_intr_from_range(
- label="foo", domain_name="private.corp.phx1.mozilla.com",
+ label="foo", domain_name="private.corp.phx1.oregonstate.com",
system=self.system, mac="11:22:33:44:55:66",
range_start_str="15.0.0.1", range_end_str="15.0.0.3")
intr.save()
@@ -68,23 +65,23 @@ def test2_create_ipv4_interface_from_range(self):
# test soa inherit
intr, errors = create_ipv4_intr_from_range(
label="foo", system=self.system, mac="11:22:33:44:55:66",
- domain_name="superprivate.foo.corp.phx1.mozilla.com",
+ domain_name="superprivate.foo.corp.phx1.oregonstate.com",
range_start_str="15.0.0.20", range_end_str="15.0.0.22")
intr.save()
self.assertEqual(errors, None)
self.assertTrue(isinstance(intr, StaticInterface))
self.assertEqual(intr.ip_str, "15.0.0.20")
- self.assertEqual(intr.domain.soa, self.s)
+ self.assertEqual(intr.domain.soa, self.soa)
self.assertEqual(
- intr.domain.name, "superprivate.foo.corp.phx1.mozilla.com")
+ intr.domain.name, "superprivate.foo.corp.phx1.oregonstate.com")
self.assertEqual(
- intr.domain.master_domain.name, "foo.corp.phx1.mozilla.com")
- self.assertEqual(intr.domain.master_domain.soa, self.s)
+ intr.domain.master_domain.name, "foo.corp.phx1.oregonstate.com")
+ self.assertEqual(intr.domain.master_domain.soa, self.soa)
def test3_create_ipv4_interface_from_range(self):
# Test for an error when all the IP's are in use.
intr, errors = create_ipv4_intr_from_range(
- label="foo", domain_name="private.corp.phx1.mozilla.com",
+ label="foo", domain_name="private.corp.phx1.oregonstate.com",
system=self.system, mac="11:22:33:44:55:66",
range_start_str="15.0.0.2", range_end_str="15.0.0.5")
intr.save()
@@ -93,7 +90,7 @@ def test3_create_ipv4_interface_from_range(self):
self.assertEqual(intr.ip_str, "15.0.0.2")
intr, errors = create_ipv4_intr_from_range(
- label="foo", domain_name="private.corp.phx1.mozilla.com",
+ label="foo", domain_name="private.corp.phx1.oregonstate.com",
system=self.system, mac="11:22:33:44:55:66",
range_start_str="15.0.0.2", range_end_str="15.0.0.5")
intr.save()
@@ -102,7 +99,7 @@ def test3_create_ipv4_interface_from_range(self):
self.assertEqual(intr.ip_str, "15.0.0.3")
intr, errors = create_ipv4_intr_from_range(
- label="foo", domain_name="private.corp.phx1.mozilla.com",
+ label="foo", domain_name="private.corp.phx1.oregonstate.com",
system=self.system, mac="11:22:33:44:55:66",
range_start_str="15.0.0.2", range_end_str="15.0.0.5")
intr.save()
@@ -111,7 +108,7 @@ def test3_create_ipv4_interface_from_range(self):
self.assertEqual(intr.ip_str, "15.0.0.4")
intr, errors = create_ipv4_intr_from_range(
- label="foo", domain_name="private.corp.phx1.mozilla.com",
+ label="foo", domain_name="private.corp.phx1.oregonstate.com",
system=self.system, mac="11:22:33:44:55:66",
range_start_str="15.0.0.2", range_end_str="15.0.0.5")
intr.save()
@@ -120,7 +117,7 @@ def test3_create_ipv4_interface_from_range(self):
self.assertEqual(intr.ip_str, "15.0.0.5")
intr, errors = create_ipv4_intr_from_range(
- label="foo", domain_name="private.corp.phx1.mozilla.com",
+ label="foo", domain_name="private.corp.phx1.oregonstate.com",
system=self.system, mac="11:22:33:44:55:66",
range_start_str="15.0.0.2", range_end_str="15.0.0.5")
self.assertEqual(intr, None)
View
10 cyder/cydhcp/network/models.py
@@ -3,7 +3,7 @@
from django.db import models
from django.core.exceptions import ValidationError
-from cyder.base.constants import IP_TYPES
+from cyder.base.constants import IP_TYPES, IP_TYPE_4, IP_TYPE_6
from cyder.base.mixins import ObjectUrlMixin
from cyder.cydhcp.keyvalue.base_option import CommonOption
from cyder.cydhcp.utils import IPFilter
@@ -95,7 +95,7 @@ def save(self, *args, **kwargs):
super(Network, self).save(*args, **kwargs)
if add_routers:
- if self.ip_type == '4':
+ if self.ip_type == IP_TYPE_4:
router = str(ipaddr.IPv4Address(int(self.network.network) + 1))
else:
router = str(ipaddr.IPv6Address(int(self.network.network) + 1))
@@ -138,7 +138,7 @@ def check_valid_range(self):
fail = True
break
- if self.ip_type == '4':
+ if self.ip_type == IP_TYPE_4:
brdcst_upper, brdcst_lower = 0, int(self.network.broadcast)
else:
brdcst_upper, brdcst_lower = ipv6_to_longs(str(
@@ -233,9 +233,9 @@ def update_network(self):
if not isinstance(self.network_str, basestring):
raise ValidationError("ERROR: No network str.")
try:
- if self.ip_type == '4':
+ if self.ip_type == IP_TYPE_4:
self.network = ipaddr.IPv4Network(self.network_str)
- elif self.ip_type == '6':
+ elif self.ip_type == IP_TYPE_6:
self.network = ipaddr.IPv6Network(self.network_str)
else:
raise ValidationError("Could not determine IP type of network"
View
30 cyder/cydhcp/range/models.py
@@ -3,8 +3,10 @@
from django.http import HttpResponse
from cyder.base.mixins import ObjectUrlMixin
-from cyder.base.constants import IP_TYPES
-from cyder.cydhcp.constants import *
+from cyder.base.constants import IP_TYPES, IP_TYPE_4, IP_TYPE_6
+from cyder.cydhcp.constants import (
+ ALLOW_OPTIONS, DENY_OPTIONS, RANGE_TYPE, STATIC
+)
from cyder.cydhcp.interface.static_intr.models import StaticInterface
from cyder.cydhcp.network.models import Network
from cyder.cydhcp.utils import IPFilter, four_to_two
@@ -123,12 +125,12 @@ def clean(self):
"with a network and is not reserved".format(
self.start_str, self.end_str))
try:
- if self.ip_type == '4':
+ if self.ip_type == IP_TYPE_4:
self.start_upper, self.start_lower = 0, int(
ipaddr.IPv4Address(self.start_str))
self.end_upper, self.end_lower = 0, int(
ipaddr.IPv4Address(self.end_str))
- elif self.ip_type == '6':
+ elif self.ip_type == IP_TYPE_6:
self.start_upper, self.start_lower = ipv6_to_longs(
self.start_str)
self.end_upper, self.end_lower = ipv6_to_longs(self.end_str)
@@ -136,6 +138,7 @@ def clean(self):
raise ValidationError("ERROR: could not determine the ip type")
except ipaddr.AddressValueError, e:
raise ValidationError(str(e))
+
"""
Some notes:
start = s1 s2
@@ -164,13 +167,13 @@ def clean(self):
" or equal to the end of the range.")
if not self.is_reserved:
self.network.update_network()
- if self.network.ip_type == '4':
+ if self.network.ip_type == IP_TYPE_4:
IPClass = ipaddr.IPv4Address
else:
IPClass = ipaddr.IPv6Address
- if IPClass(self.start_str) < self.network.network.network or \
- IPClass(self.end_str) > self.network.network.broadcast:
+ if (IPClass(self.start_str) < self.network.network.network or
+ IPClass(self.end_str) > self.network.network.broadcast):
raise RangeOverflowError(
"Range {0} to {1} doesn't fit in {2}".format(
IPClass(self.start_lower),
@@ -191,11 +194,16 @@ def get_allowed_clients(self):
return allow
def check_for_overlaps(self):
- """This function will look at all the other ranges and make sure we
- don't overlap with any of them.
+ """
+ This function will look at all the other ranges and make sure we don't
+ overlap with any of them.
"""
self._range_ips()
- Ip = ipaddr.IPv4Address if self.ip_type == '4' else ipaddr.IPv6Address
+ if self.ip_type == IP_TYPE_4:
+ Ip = ipaddr.IPv4Address
+ else:
+ Ip = ipaddr.IPv6Address
+
for range in Range.objects.all():
if range.pk == self.pk:
continue
@@ -286,7 +294,7 @@ def find_free_ip(start, end, ip_type='4'):
:param ip_type: The type of IP you are looking for.
:type ip_type: str either '4' or '6'
"""
- if ip_type == '4':
+ if ip_type == IP_TYPE_4:
records = AddressRecord.objects.filter(ip_upper=0, ip_lower__gte=start,
ip_lower__lte=end)
ptrs = PTR.objects.filter(ip_upper=0, ip_lower__gte=start,
View
4 cyder/cydhcp/site/models.py
@@ -94,10 +94,6 @@ def compile_Q(self):
"""Compile a Django Q that will match any IP inside this site."""
return networks_to_Q(self.network_set.all())
- class Meta:
- db_table = 'site'
- unique_together = ('name', 'parent')
-
class SiteKeyValue(KeyValue):
site = models.ForeignKey(Site, null=False)
View
4 cyder/cydns/address_record/forms.py
@@ -1,9 +1,9 @@
-from django.forms import ModelForm
from django import forms
from cyder.cydns.address_record.models import AddressRecord
+from cyder.cydns.forms import DNSForm
-class AddressRecordForm(ModelForm):
+class AddressRecordForm(DNSForm):
class Meta:
model = AddressRecord
exclude = ('ip_upper', 'ip_lower', 'reverse_domain', 'fqdn')
View
167 cyder/cydns/address_record/models.py
@@ -1,49 +1,24 @@
from gettext import gettext as _
from django.db import models
-from django.core.exceptions import ValidationError, ObjectDoesNotExist
+from django.core.exceptions import ValidationError
import cydns
from cyder.cydns.cname.models import CNAME
-from cyder.cydns.view.models import View
from cyder.cydns.ip.models import Ip
-from cyder.cydns.models import set_fqdn, check_for_cname
-from cyder.cydns.models import check_TLD_condition
-from cyder.cydns.validation import (validate_first_label, validate_name,
- validate_ttl)
-from cyder.cydns.domain.models import Domain
-from cyder.base.mixins import ObjectUrlMixin, DisplayMixin
-from cyder.cydns.soa.utils import update_soa
+from cyder.cydns.models import CydnsRecord, LabelDomainMixin
+from cyder.base.constants import IP_TYPE_6, IP_TYPE_4
-# import reversion
-
-class BaseAddressRecord(Ip, ObjectUrlMixin, DisplayMixin):
- """AddressRecord is the class that generates A and AAAA records
+class BaseAddressRecord(Ip, LabelDomainMixin, CydnsRecord):
+ """
+ AddressRecord is the class that generates A and AAAA records
>>> AddressRecord(label=label, domain=domain_object, ip_str=ip_str,
... ip_type=ip_type)
"""
- label = models.CharField(
- max_length=63, blank=True, null=True,
- validators=[validate_first_label],
- help_text='The short hostname goes here. If this is a '
- 'record ' 'for the selected domain, leave this field '
- 'blank')
- domain = models.ForeignKey(Domain, null=False, help_text='FQDN of the '
- 'domain after the short hostname. '
- '(Ex: <i>Vlan</i>.<i>DC</i>.mozilla.com)')
- fqdn = models.CharField(max_length=255, blank=True, null=True,
- validators=[validate_name])
- ttl = models.PositiveIntegerField(default=3600, blank=True, null=True,
- validators=[validate_ttl],
- help_text='Time to Live of the record')
- description = models.CharField(max_length=1000, blank=True, null=True,
- help_text="A description of this record.")
- views = models.ManyToManyField(View, blank=True)
-
- search_fields = ('fqdn', 'ip_str')
+ search_fields = ("fqdn", "ip_str")
class Meta:
abstract = True
@@ -53,7 +28,13 @@ def __str__(self):
self.obj_type(), str(self.ip_str))
def __repr__(self):
- return '<Address Record "{0}">'.format(str(self))
+ return "<Address Record '{0}'>".format(str(self))
+
+ @property
+ def rdtype(self):
+ if self.ip_type == IP_TYPE_6:
+ return 'AAAA'
+ return 'A'
def details(self):
"""For tables."""
@@ -74,94 +55,68 @@ def eg_metadata(self):
{'name': 'ip_str', 'datatype': 'string', 'editable': True},
]}
- @property
- def rdtype(self):
- if self.ip_type == '6':
- return 'AAAA'
- return 'A'
-
@classmethod
def get_api_fields(cls):
- return ['fqdn', 'ip_str', 'ip_type', 'description', 'ttl']
-
- def save(self, *args, **kwargs):
- self.full_clean()
- set_fqdn(self)
- check_TLD_condition(self)
- update_soa(self)
- if self.pk:
- # We need to get the domain from the db. If it's not our current
- # domain, call prune_tree on the domain in the db later.
- db_domain = self.__class__.objects.get(pk=self.pk).domain
- if self.domain == db_domain:
- db_domain = None
- else:
- db_domain = None
- super(BaseAddressRecord, self).save(*args, **kwargs)
- if db_domain:
- from cyder.cydns.utils import prune_tree
- prune_tree(db_domain)
+ return super(BaseAddressRecord, cls).get_api_fields() + ['ip_str',
+ 'ip_type']
def clean(self, *args, **kwargs):
validate_glue = kwargs.pop('validate_glue', True)
if validate_glue:
self.check_glue_status()
- set_fqdn(self)
- check_TLD_condition(self)
- try:
- if self.domain and self.domain.delegated:
- self.validate_delegation_conditions()
- except ObjectDoesNotExist:
- pass
- check_for_cname(self)
-
- urd = kwargs.pop('update_reverse_domain', False)
- self.clean_ip(update_reverse_domain=urd)
+ self.clean_ip()
+ self.set_fqdn()
+ self.check_TLD_condition()
+ self.validate_delegation_conditions()
+ self.check_no_ns_soa_condition(self.domain)
+ self.check_for_cname()
from cyder.cydhcp.interface.static_intr.models import StaticInterface
if not kwargs.pop('ignore_interface', False):
- if StaticInterface.objects.filter(fqdn=self.fqdn,
- ip_upper=self.ip_upper,
- ip_lower=self.ip_lower).exists():
- raise ValidationError('A Static Interface has already '
- 'reserved this A record.')
+ if StaticInterface.objects.filter(
+ fqdn=self.fqdn, ip_upper=self.ip_upper,
+ ip_lower=self.ip_lower).exists():
+ raise ValidationError(
+ "A Static Interface has already reserved this A "
+ "record.")
def delete(self, *args, **kwargs):
- """Address Records that are glue records or that are pointed to
+ """
+ Address Records that are glue records or that are pointed to
by a CNAME should not be removed from the database.
"""
if kwargs.pop('validate_glue', True):
if self.nameserver_set.exists():
raise ValidationError(
- "Cannot delete the record {0}. It is a ' 'glue record."
- .format(self.obj_type()))
+ "Cannot delete the record {0}. It is a glue "
+ "record.".format(self.record_type()))
if kwargs.pop('check_cname', True):
if CNAME.objects.filter(target=self.fqdn):
- raise ValidationError('A CNAME points to this {0} record. '
- 'Change the CNAME before deleting this '
- 'record.'.format(self.obj_type()))
+ raise ValidationError(
+ "A CNAME points to this {0} record. Change the CNAME "
+ "before deleting this record.".format(self.record_type()))
- from cyder.cydns.utils import prune_tree
- objs_domain = self.domain
super(BaseAddressRecord, self).delete(*args, **kwargs)
- prune_tree(objs_domain)
-
- def set_fqdn(self):
- set_fqdn(self)
def validate_delegation_conditions(self):
- """If our domain is delegated then an A record can only have a
- name that is the same as a nameserver in that domain (glue)."""
+ """
+ If our domain is delegated then an A record can only have a
+ name that is the same as a nameserver in that domain (glue).
+ """
+ if not (self.domain and self.domain.delegated):
+ return
if self.domain.nameserver_set.filter(server=self.fqdn).exists():
return
else:
# Confusing error messege?
- raise ValidationError('You can only create A records in a '
- 'delegated domain that have an NS record '
- 'pointing to them.')
+ raise ValidationError(
+ "You can only create A records in a delegated domain that "
+ "have an NS record pointing to them."
+ )
def check_glue_status(self):
- """If this record is a "glue" record for a Nameserver instance,
+ """
+ If this record is a "glue" record for a Nameserver instance,
do not allow modifications to this record. The Nameserver will
need to point to a different record before this record can
be updated.
@@ -176,36 +131,32 @@ def check_glue_status(self):
# The label of the domain changed. Make sure it's not a glue record
Nameserver = cydns.nameserver.models.Nameserver
if Nameserver.objects.filter(addr_glue=self).exists():
- raise ValidationError('This record is a glue record for a '
- 'Nameserver. Change the Nameserver to '
- 'edit this record.')
-
- def obj_type(self):
- # If PTR didn't share this field, we would use 'A' and 'AAAA'
- # instead of '4' and '6'.
- if self.ip_type == '4':
+ raise ValidationError(
+ "This record is a glue record for a Nameserver. Change the "
+ "Nameserver to edit this record."
+ )
+
+ def record_type(self):
+ if self.ip_type == IP_TYPE_4:
return 'A'
else:
return 'AAAA'
class AddressRecord(BaseAddressRecord):
- """AddressRecord is the class that generates A and AAAA records
+ """
+ AddressRecord is the class that generates A and AAAA records
>>> AddressRecord(label=label, domain=domain_object, ip_str=ip_str,
... ip_type=ip_type)
"""
id = models.AutoField(primary_key=True)
- reverse_domain = models.ForeignKey(Domain, null=True, blank=True,
- related_name='addressrecordomain_set')
template = _("{bind_name:$lhs_just} {ttl} {rdclass:$rdclass_just} "
"{rdtype:$rdtype_just} {ip_str:$rhs_just}")
class Meta:
- db_table = 'address_record'
- unique_together = ('label', 'domain', 'fqdn', 'ip_upper', 'ip_lower',
- 'ip_type')
-
-## reversion.(AddressRecord)
+ db_table = "address_record"
+ unique_together = ("label", "domain", "fqdn", "ip_upper", "ip_lower",
+ "ip_type")
View
7 cyder/cydns/cname/forms.py
@@ -1,9 +1,10 @@
from django import forms
-from django.forms import ModelForm
+
from cyder.cydns.cname.models import CNAME
+from cyder.cydns.forms import DNSForm
-class CNAMEForm(ModelForm):
+class CNAMEForm(DNSForm):
class Meta:
model = CNAME
@@ -13,7 +14,7 @@ class Meta:
# https://code.djangoproject.com/ticket/9321
-class CNAMEFQDNForm(ModelForm):
+class CNAMEFQDNForm(DNSForm):
class Meta:
model = CNAME
View
67 cyder/cydns/cname/models.py
@@ -2,18 +2,16 @@
from django.core.exceptions import ValidationError, ObjectDoesNotExist
import cyder
-import cydns
-from cyder.cydns.models import CydnsRecord
-from cyder.cydns.validation import validate_name, find_root_domain
+from cyder.cydns.models import CydnsRecord, LabelDomainMixin
+from cyder.cydns.validation import validate_name
from cyder.cydns.search_utils import smart_fqdn_exists
-# import reversion
-
from gettext import gettext as _
-class CNAME(CydnsRecord):
- """CNAMES can't point to an any other records. Said another way,
+class CNAME(CydnsRecord, LabelDomainMixin):
+ """
+ CNAMES can't point to an any other records. Said another way,
CNAMES can't be at the samle level as any other record. This means
that when you are creating a CNAME every other record type must be
checked to make sure that the name about to be taken by the CNAME
@@ -29,6 +27,7 @@ class CNAME(CydnsRecord):
help_text="CNAME Target")
template = _("{bind_name:$lhs_just} {ttl} {rdclass:$rdclass_just} "
"{rdtype:$rdtype_just} {target:$rhs_just}.")
+
search_fields = ('fqdn', 'target')
class Meta:
@@ -38,10 +37,6 @@ class Meta:
def __str__(self):
return "{0} CNAME {1}".format(self.fqdn, self.target)
- @property
- def rdtype(self):
- return 'CNAME'
-
def details(self):
"""For tables."""
data = super(CNAME, self).details()
@@ -58,6 +53,10 @@ def eg_metadata(self):
{'name': 'target', 'datatype': 'string', 'editable': True},
]}
+ @property
+ def rdtype(self):
+ return 'CNAME'
+
@classmethod
def get_api_fields(cls):
return super(CNAME, cls).get_api_fields() + ['target']
@@ -68,14 +67,14 @@ def save(self, *args, **kwargs):
def clean(self, *args, **kwargs):
super(CNAME, self).clean(*args, **kwargs)
- super(CNAME, self).check_for_delegation()
if self.fqdn == self.target:
raise ValidationError("CNAME loop detected.")
self.check_SOA_condition()
self.existing_node_check()
def check_SOA_condition(self):
- """We need to check if the domain is the root domain in a zone.
+ """
+ We need to check if the domain is the root domain in a zone.
If the domain is the root domain, it will have an soa, but the
master domain will have no soa (or it will have a a different
soa).
@@ -84,16 +83,20 @@ def check_SOA_condition(self):
self.domain
except ObjectDoesNotExist:
return # Validation will fail eventually
- root_domain = find_root_domain(self.domain.soa)
+ if not self.domain.soa:
+ return
+ root_domain = self.domain.soa.root_domain
if root_domain is None:
return
if self.fqdn == root_domain.name:
- raise ValidationError("ou cannot create a CNAME that points to "
- "the root of a zone.")
- return
+ raise ValidationError(
+ "You cannot create a CNAME who's left hand side is at the "
+ "same level as an SOA"
+ )
def existing_node_check(self):
- """Make sure no other nodes exist at the level of this CNAME.
+ """
+ Make sure no other nodes exist at the level of this CNAME.
"If a CNAME RR is present at a node, no other data should be
present; this ensures that the data for
@@ -124,19 +127,21 @@ def existing_node_check(self):
qset = smart_fqdn_exists(self.fqdn, cn=False)
if qset:
objects = qset.all()
- raise ValidationError("Objects with this name already exist: {0}".
- format(objects))
- MX = cydns.mx.models.MX
+ raise ValidationError(
+ "Objects with this name already exist: {0}".format(objects)
+ )
+ MX = cyder.cydns.mx.models.MX
if MX.objects.filter(server=self.fqdn):
- raise ValidationError("RFC 2181 says you shouldn't point MX "
- "records at CNAMEs and an MX points to"
- " this name!")
- PTR = cyder.cydns.ptr.models.PTR
- if PTR.objects.filter(name=self.fqdn):
- raise ValidationError("RFC 1034 says you shouldn't point PTR "
- "records at CNAMEs, and a PTR points to"
- " this name!")
+ raise ValidationError(
+ "RFC 2181 says you shouldn't point MX records at CNAMEs and "
+ "an MX points to this name!"
+ )
+ # There are preexisting records that break this rule. We can't support
+ # this requirement until those records are fixed
+ # PTR = cydns.ptr.models.PTR
+ # if PTR.objects.filter(name=self.fqdn):
+ # raise ValidationError("RFC 1034 says you shouldn't point PTR "
+ # "records at CNAMEs, and a PTR points to"
+ # " this name!")
# Should SRV's not be allowed to point to a CNAME? /me looks for an RFC
-
-# reversion.(CNAME)
View
51 cyder/cydns/cname/tests/test_models.py
@@ -1,7 +1,6 @@
from django.core.exceptions import ValidationError
import cyder.base.tests
-from cyder.cydns.soa.models import SOA
from cyder.cydns.domain.models import Domain
from cyder.cydns.nameserver.models import Nameserver
from cyder.cydns.mx.models import MX
@@ -13,6 +12,7 @@
from cyder.cydhcp.interface.static_intr.models import StaticInterface
from cyder.cydns.ip.utils import ip_to_domain_name
+from cyder.cydns.tests.utils import create_fake_zone
from cyder.core.system.models import System
@@ -32,27 +32,11 @@ def create_domain(self, name, ip_type=None, delegated=False):
return d
def setUp(self):
- primary = "ns5.oregonstate.edu"
- contact = "admin.oregonstate.edu"
- retry = 1234
- refresh = 1234123
- self.soa = SOA(primary=primary, contact=contact, retry=retry,
- refresh=refresh)
- self.soa.save()
-
- self.g = Domain(name="gz")
- self.g.save()
- self.c_g = Domain(name="coo.gz")
- self.c_g.soa = self.soa
- self.c_g.save()
- self.d = Domain(name="dz")
- self.d.save()
-
- self.arpa = self.create_domain(name='arpa')
- self.arpa.save()
- self.i_arpa = self.create_domain(name='in-addr.arpa')
- self.i_arpa.save()
- self.r1 = self.create_domain(name="10")
+ self.g = create_fake_zone("gz", suffix="")
+ self.c_g = create_fake_zone("coo.gz", suffix="")
+ self.d = create_fake_zone("dz", suffix="")
+
+ self.r1 = create_fake_zone("10.in-addr.arpa", suffix="")
self.r1.save()
self.s = System()
@@ -87,7 +71,7 @@ def test_add(self):
self.do_add(label, domain, data)
self.assertRaises(ValidationError, self.do_add, *(label, domain, data))
- label = ""
+ label = "hooo"
domain = self.g
data = "foo.com"
self.do_add(label, domain, data)
@@ -163,6 +147,18 @@ def test_address_record_exists(self):
cn = CNAME(label=label, domain=dom, target=data)
self.assertRaises(ValidationError, cn.full_clean)
+ def test_address_record_exists_upper_case(self):
+ label = "testyfoo"
+ data = "wat"
+ dom, _ = Domain.objects.get_or_create(name="cd")
+ dom, _ = Domain.objects.get_or_create(name="what.cd")
+
+ rec, _ = AddressRecord.objects.get_or_create(
+ label=label, domain=dom, ip_type='4', ip_str="128.193.1.1")
+
+ cn = CNAME(label=label.title(), domain=dom, target=data)
+ self.assertRaises(ValidationError, cn.full_clean)
+
def test_address_record_cname_exists(self):
label = "testyfoo"
data = "wat"
@@ -170,7 +166,8 @@ def test_address_record_cname_exists(self):
dom, _ = Domain.objects.get_or_create(name="what.cd")
CNAME.objects.get_or_create(
- label=label, domain=dom, target=data)
+ label=label, domain=dom, target=data
+ )
rec = AddressRecord(label=label, domain=dom, ip_str="128.193.1.1")
self.assertRaises(ValidationError, rec.save)
@@ -311,8 +308,10 @@ def test_intr_cname_exists(self):
cn.full_clean()
cn.save()
- intr = StaticInterface(label=label, domain=dom, ip_str="10.0.0.2",
- ip_type='4', system=self.s, mac="00:11:22:33:44:55")
+ intr = StaticInterface(
+ label=label, domain=dom, ip_str="10.0.0.2", ip_type='4',
+ system=self.s, mac="00:11:22:33:44:55"
+ )
self.assertRaises(ValidationError, intr.clean)
cn.label = "differentlabel"
View
667 cyder/cydns/cybind/builder.py
@@ -10,9 +10,13 @@
import re
import time
-from cyder.settings.dnsbuilds import STAGE_DIR, PROD_DIR, LOCK_FILE
-from cyder.settings.dnsbuilds import (NAMED_CHECKZONE_OPTS,
- MAX_ALLOWED_LINES_CHANGED)
+from settings.dnsbuilds import (
+ STAGE_DIR, PROD_DIR, LOCK_FILE, STOP_UPDATE_FILE, NAMED_CHECKZONE_OPTS,
+ MAX_ALLOWED_LINES_CHANGED, MAX_ALLOWED_CONFIG_LINES_REMOVED,
+ NAMED_CHECKZONE, NAMED_CHECKCONF, LAST_RUN_FILE, BIND_PREFIX
+)
+
+from core.task.models import Task
from cyder.cydns.domain.models import SOA
from cyder.cydns.view.models import View
@@ -20,6 +24,8 @@
from cyder.cydns.cybind.models import DNSBuildRun
from cyder.cydns.cybind.serial_utils import get_serial
+from core.utils import fail_mail
+
class BuildError(Exception):
"""Exception raised when there is an error in the build process."""
@@ -27,13 +33,13 @@ class BuildError(Exception):
class SVNBuilderMixin(object):
svn_ignore = [re.compile("---\s.+\s+\(revision\s\d+\)"),
- re.compile("\+\+\+\s.+\s+\(working copy\)"),
- re.compile("\+\+\+\s.+\s+\(revision \d+\)")]
+ re.compile("\+\+\+\s.+\s+\(working copy\)")]
vcs_type = 'svn'
- def svn_lines_changed(self):
- """This function will collect some metrics on how many lines were added
+ def svn_lines_changed(self, dirname):
+ """
+ This function will collect some metrics on how many lines were added
and removed during the build process.
:returns: (int, int) -> (lines_added, lines_removed)
@@ -47,23 +53,26 @@ def svn_lines_changed(self):
tens of lines is not a large concern.
"""
cwd = os.getcwd()
- os.chdir(self.PROD_DIR)
+ os.chdir(dirname)
try:
- command_str = "svn add --force .".format(self.PROD_DIR)
+ command_str = "svn add --force .".format(dirname)
+ self.log("Calling `{0}` in {1}".
+ format(command_str, dirname))
stdout, stderr, returncode = self.shell_out(command_str)
if returncode != 0:
- raise BuildError("\nFailed to add files to svn."
+ raise BuildError("Failed to add files to svn."
"\ncommand: {0}:\nstdout: {1}\nstderr:{2}".
format(command_str, stdout, stderr))
command_str = "svn diff --depth=infinity ."
stdout, stderr, returncode = self.shell_out(command_str)
if returncode != 0:
- raise BuildError("\nFailed to add files to svn."
+ raise BuildError("Failed to add files to svn."
"\ncommand: {0}:\nstdout: {1}\nstderr:{2}".
format(command_str, stdout, stderr))
except Exception:
raise
finally:
+ self.log("Changing pwd to {0}".format(cwd))
os.chdir(cwd) # go back!
la, lr = 0, 0
@@ -81,63 +90,87 @@ def svn_ignore(line):
lr += 1
elif line.startswith('+'):
la += 1
-
return la, lr
def svn_sanity_check(self, lines_changed):
- """If sanity checks fail, this function will return a string which is
+ """
+ If sanity checks fail, this function will return a string which is
True-ish. If all sanity cheecks pass, a Falsy value will be
- returned."""
+ returned.
+ """
# svn diff changes and react if changes are too large
- if (lambda x, y: x + y)(*lines_changed) > MAX_ALLOWED_LINES_CHANGED:
- pass
- # email and fail
- # Make sure we can run the script again
- # rm -rf stage/
- # rm lock.file
- return False
+ if sum(lines_changed) > MAX_ALLOWED_LINES_CHANGED:
+ if self.FORCE:
+ self.log("Sanity check failed but FORCE == True. "
+ "Ignoring thresholds.")
+ else:
+ raise BuildError("Wow! Too many lines changed during this "
+ "checkin. {0} lines add, {1} lines removed."
+ .format(*lines_changed))
def svn_checkin(self, lines_changed):
# svn add has already been called
cwd = os.getcwd()
os.chdir(self.PROD_DIR)
- self.log('LOG_INFO', "Changing pwd to {0}".format(self.PROD_DIR))
+ self.log("Changing pwd to {0}".format(self.PROD_DIR))
try:
- """
- command_str = "svn add --force .".format(self.PROD_DIR)
- stdout, stderr, returncode = self.shell_out(command_str)
- if returncode != 0:
- raise BuildError("\nFailed to add files to svn."
- "\ncommand: {0}:\nstdout: {1}\nstderr:{2}".
- format(command_str, stdout, stderr))
- """
-
ci_message = _("Checking in DNS. {0} lines were added and {1} were"
" removed".format(*lines_changed))
- self.log('LOG_INFO', "Commit message: {0}".format(ci_message))
- command_str = "svn ci {0} -m \"{1}\"".format(self.PROD_DIR,
- ci_message)
+ self.log("Commit message: {0}".format(ci_message))
+ command_str = "svn ci {0} -m \"{1}\"".format(
+ self.PROD_DIR, ci_message)
stdout, stderr, returncode = self.shell_out(command_str)
if returncode != 0:
- raise BuildError("\nFailed to check in changes."
+ raise BuildError("Failed to check in changes."
"\ncommand: {0}:\nstdout: {1}\nstderr:{2}".
format(command_str, stdout, stderr))
else:
- self.log('LOG_INFO', "Changes have been checked in.")
+ self.log("Changes have been checked in.")
finally:
os.chdir(cwd) # go back!
- self.log('LOG_INFO', "Changing pwd to {0}".format(cwd))
+ self.log("Changing pwd to {0}".format(cwd))
return
def vcs_checkin(self):
- lines_changed = self.svn_lines_changed()
+ command_str = "svn add --force .".format(self.PROD_DIR)
+ stdout, stderr, returncode = self.shell_out(command_str)
+ try:
+ cwd = os.getcwd()
+ os.chdir(self.PROD_DIR)
+ self.log("Calling `svn up` in {0}".format(self.PROD_DIR))
+ command_str = "svn up"
+ stdout, stderr, returncode = self.shell_out(command_str)
+ if returncode != 0:
+ raise BuildError("Failed to svn up."
+ "\ncommand: {0}:\nstdout: {1}\nstderr:{2}".
+ format(command_str, stdout, stderr))
+ finally:
+ os.chdir(cwd) # go back!
+ self.log("Changing pwd to {0}".format(cwd))
+
+ lines_changed = self.svn_lines_changed(self.PROD_DIR)
self.svn_sanity_check(lines_changed)
if lines_changed == (0, 0):
- self.log('LOG_INFO', "PUSH_TO_PROD is True but "
- "svn_lines_changed found that no lines different "
+ self.log("PUSH_TO_PROD is True but "
+ "svn_lines_changed found that no lines differ "
"from last svn checkin.")
else:
- self.log('LOG_INFO', "PUSH_TO_PROD is True. Checking into "
+ config_lines_changed = self.svn_lines_changed(
+ os.path.join(self.PROD_DIR, 'config')
+ )
+ config_lines_removed = config_lines_changed[1]
+ if config_lines_removed > MAX_ALLOWED_CONFIG_LINES_REMOVED:
+ if self.FORCE:
+ self.log("Config sanity check failed but "
+ "FORCE == True. Ignoring thresholds.")
+ else:
+ raise BuildError(
+ "Wow! Too many lines removed from the config dir ({0} "
+ "lines removed). Manually make sure this commit is "
+ "okay." .format(config_lines_removed)
+ )
+
+ self.log("PUSH_TO_PROD is True. Checking into "
"svn.")
self.svn_checkin(lines_changed)
@@ -147,7 +180,10 @@ def __init__(self, **kwargs):
defaults = {
'STAGE_DIR': STAGE_DIR,
'PROD_DIR': PROD_DIR,
+ 'BIND_PREFIX': BIND_PREFIX,
'LOCK_FILE': LOCK_FILE,
+ 'STOP_UPDATE_FILE': STOP_UPDATE_FILE,
+ 'LAST_RUN_FILE': LAST_RUN_FILE,
'STAGE_ONLY': False,
'NAMED_CHECKZONE_OPTS': NAMED_CHECKZONE_OPTS,
'CLOBBER_STAGE': False,
@@ -156,22 +192,90 @@ def __init__(self, **kwargs):
'PRESERVE_STAGE': False,
'LOG_SYSLOG': True,
'DEBUG': False,
+ 'FORCE': False,
'bs': DNSBuildRun() # Build statistic
}
for k, default in defaults.iteritems():
setattr(self, k, kwargs.get(k, default))
# This is very specific to python 2.6
- syslog.openlog('dnsbuild', 0, syslog.LOG_USER)
+ syslog.openlog('dnsbuild', 0, syslog.LOG_LOCAL6)
self.lock_fd = None
- def log(self, log_level, msg, **kwargs):
+ def status(self):
+ """Print the status of the build system"""
+ is_locked = False
+ try:
+ self.lock_fd = open(self.LOCK_FILE, 'w+')
+ fcntl.flock(self.lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
+ fcntl.flock(self.lock_fd, fcntl.LOCK_UN)
+ except IOError, exc_value:
+ if exc_value[0] == 11:
+ is_locked = True
+ if is_locked:
+ print "IS_LOCKED=True"
+ else:
+ print "IS_LOCKED=False"
+ print "LOCK_FILE={0}".format(self.LOCK_FILE)
+
+ if os.path.exists(self.STOP_UPDATE_FILE):
+ print "STOP_UPDATE_FILE_EXISTS=True"
+ else:
+ print "STOP_UPDATE_FILE_EXISTS=False"
+ print "STOP_UPDATE_FILE={0}".format(self.STOP_UPDATE_FILE)
+
+ if os.path.exists(self.STAGE_DIR):
+ print "STAGE_DIR_EXISTS=True"
+ else:
+ print "STAGE_DIR_EXISTS=False"
+ print "STAGE_DIR={0}".format(self.STAGE_DIR)
+
+ if os.path.exists(self.PROD_DIR):
+ print "PROD_DIR_EXISTS=True"
+ else:
+ print "PROD_DIR_EXISTS=False"
+ print "PROD_DIR={0}".format(self.PROD_DIR)
+
+ print "LAST_RUN_FILE={0}".format(self.LAST_RUN_FILE)
+
+ def format_title(self, title):
+ return "{0} {1} {0}".format('=' * ((30 - len(title)) / 2), title)
+
+ def get_scheduled(self):
+ """
+ Find all dns tasks that indicate we need to rebuild a certain zone.
+ Evalutate the queryset so nothing slips in (our DB isolation *should*
+ cover this). This will ensure that if a record is changed during the
+ build it's build request will not be deleted and will be serviced
+ during the next build.
+
+ If the build is successful we will delete all the scheduled tasks
+ return by this function
+
+ note::
+ When we are not checking files into SVN we do not need to delete
+ the scheduled tasks. Not checking files into SVN is indicative of a
+ troubleshoot build.
+ """
+ ts = [t for t in Task.dns.all()]
+ ts_len = len(ts)
+ self.log("{0} zone{1} requested to be rebuilt".format(
+ ts_len, 's' if ts_len != 1 else '')
+ )
+ return ts
+
+ def log(self, msg, log_level='LOG_INFO', **kwargs):
# Eventually log this stuff into bs
# Let's get the callers name and log that
curframe = inspect.currentframe()
calframe = inspect.getouterframes(curframe, 2)
- callername = calframe[1][3]
- fmsg = "[{0}] {1}".format(callername, msg)
+ callername = "[{0}]".format(calframe[1][3])
+ root_domain = kwargs.get('root_domain', None)
+ if root_domain:
+ fmsg = "{0:20} < {1} > {2}".format(callername,
+ root_domain.name, msg)
+ else:
+ fmsg = "{0:20} {1}".format(callername, msg)
if hasattr(syslog, log_level):
ll = getattr(syslog, log_level)
else:
@@ -189,7 +293,7 @@ def build_staging(self, force=False):
"""
if os.path.exists(self.STAGE_DIR) and not force:
raise BuildError("The DNS build scripts tried to build the staging"
- " but area already exists.")
+ " area but the area already exists.")
try:
os.makedirs(self.STAGE_DIR)
except OSError:
@@ -200,18 +304,18 @@ def clear_staging(self, force=False):
"""
rm -rf the staging area. Fail if the staging area doesn't exist.
"""
- self.log('LOG_INFO', "Attempting rm -rf staging "
+ self.log("Attempting rm -rf staging "
"area. ({0})...".format(self.STAGE_DIR))
if os.path.exists(self.STAGE_DIR) or force:
try:
shutil.rmtree(self.STAGE_DIR)
except OSError, e:
if e.errno == 2:
- self.log('LOG_WARNING', "Staging was "
- "not present.")
+ self.log("Staging was not present.",
+ log_level='LOG_WARNING')
else:
raise
- self.log('LOG_INFO', "Staging area cleared")
+ self.log("Staging area cleared")
else:
if not force:
raise BuildError("The DNS build scripts tried to remove the "
@@ -220,33 +324,46 @@ def clear_staging(self, force=False):
def lock(self):
"""
- Try to write a lock file. Fail if the lock already exists.
+ Tryies to write a lock file. Returns True if we get the lock, else
+ return False.
"""
try:
if not os.path.exists(os.path.dirname(self.LOCK_FILE)):
os.makedirs(os.path.dirname(self.LOCK_FILE))
- self.log('LOG_INFO', "Attempting aquire mutext "
+ self.log("Attempting acquire mutext "
"({0})...".format(self.LOCK_FILE))
self.lock_fd = open(self.LOCK_FILE, 'w+')
fcntl.flock(self.lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
- self.log('LOG_INFO', "Lock written.")
+ self.log(self.format_title("Mutex Acquired"))
+ return True
except IOError, exc_value:
+ self.lock_fd = None
# IOError: [Errno 11] Resource temporarily unavailable
if exc_value[0] == 11:
- raise BuildError("DNS build script attempted to aquire the "
- "build mutex but a process already has it.")
+ self.log(
+ "DNS build script attempted to acquire the "
+ "build mutux but another process already has it."
+ )
+ fail_mail(
+ "An attempt was made to start the DNS build script "
+ "while an instance of the script was already running. "
+ "The attempt was denied.",
+ subject="Concurrent DNS builds attempted.")
+ return False
else:
raise
def unlock(self):
"""
- Try to remove the lock file. Fail very loudly if the lock doesn't exist
- and this function is called.
+ Trys to remove the lock file.
"""
- self.log('LOG_INFO', "Attempting release mutex "
+ if not self.lock_fd:
+ return False
+ self.log("Attempting release mutex "
"({0})...".format(self.LOCK_FILE))
fcntl.flock(self.lock_fd, fcntl.LOCK_UN)
- self.log('LOG_INFO', "Unlock Complete.")
+ self.log("Unlock Complete.")
+ return True
def calc_target(self, root_domain, soa):
"""
@@ -284,15 +401,15 @@ def calc_target(self, root_domain, soa):
zone_path = tmp_path + '/'
return zone_path
- def write_stage_zone(self, stage_fname, root_domain, soa, fname, data):
+ def write_stage_zone(self, stage_fname, root_domain, fname, data):
"""
Write a zone_file.
Return the path to the file.
"""
if not os.path.exists(os.path.dirname(stage_fname)):
os.makedirs(os.path.dirname(stage_fname))
- self.log('LOG_INFO', "Stage zone file is {0}".format(stage_fname,
- soa=soa))
+ self.log("Stage zone file is {0}".format(stage_fname),
+ root_domain=root_domain)
with open(stage_fname, 'w+') as fd:
fd.write(data)
return stage_fname
@@ -309,42 +426,45 @@ def shell_out(self, command, use_shlex=True):
stdout, stderr = p.communicate()
return stdout, stderr, p.returncode
- def named_checkzone(self, zone_file, root_domain, soa):
+ def named_checkzone(self, zone_file, root_domain):
"""Shell out and call named-checkzone on the zone file. If it returns
with errors raise a BuildError.
"""
# Make sure we have the write tools to do the job
- command_str = "which named-checkzone"
+ command_str = "test -f {0}".format(NAMED_CHECKZONE)
stdout, stderr, returncode = self.shell_out(command_str)
if returncode != 0:
raise BuildError("Couldn't find named-checkzone.")
# Check the zone file.
- command_str = "named-checkzone {0} {1} {2}".format(
- self.NAMED_CHECKZONE_OPTS, root_domain.name,
- zone_file)
- self.log('LOG_INFO', "Calling `named-checkzone {0} "
- "{1}`".format(root_domain.name, zone_file))
+ command_str = "{0} {1} {2} {3}".format(
+ NAMED_CHECKZONE, self.NAMED_CHECKZONE_OPTS,
+ root_domain.name, zone_file)
+ self.log(
+ "Calling `{0} {1} {2}`".
+ format(NAMED_CHECKZONE, root_domain.name, zone_file),
+ root_domain=root_domain
+ )
stdout, stderr, returncode = self.shell_out(command_str)
if returncode != 0:
raise BuildError("\nnamed-checkzone failed on zone {0}. "
- "\ncommand: {1}:\nstdout: {2}\nstderr:{3}".
+ "\ncommand: {1}\nstdout: {2}\nstderr:{3}\n".
format(root_domain.name, command_str, stdout,
stderr))
def named_checkconf(self, conf_file):
- command_str = "which named-checkconf"
+ command_str = "test -f {0}".format(NAMED_CHECKCONF)
stdout, stderr, returncode = self.shell_out(command_str)
if returncode != 0:
- raise BuildError("Couldn't find named-checkconf.")
+ raise BuildError("Couldn't find {0}".format(NAMED_CHECKCONF))
- command_str = "named-checkconf {0}".format(conf_file)
- self.log('LOG_INFO', "Calling `named-checkconf {0}` ".
- format(conf_file))
+ command_str = "{0} {1}".format(NAMED_CHECKCONF, conf_file)
+ self.log("Calling `{0} {1}` ".
+ format(NAMED_CHECKCONF, conf_file))
stdout, stderr, returncode = self.shell_out(command_str)
if returncode != 0:
raise BuildError("\nnamed-checkconf rejected config {0}. "
- "\ncommand: {1}:\nstdout: {2}\nstderr:{3}".
+ "\ncommand: {1}\nstdout: {2}\nstderr:{3}\n".
format(conf_file, command_str, stdout,
stderr))
@@ -358,12 +478,17 @@ def stage_to_prod(self, src):
"stage_to_prod".format(src))
dst = src.replace(self.STAGE_DIR, self.PROD_DIR)
dst_dir = os.path.dirname(dst)
+
+ if self.STAGE_ONLY:
+ self.log("Did not copy {0} to {1}".format(src, dst))
+ return dst
+
if not os.path.exists(dst_dir):
os.makedirs(dst_dir)
# copy2 will copy file metadata
try:
shutil.copy2(src, dst)
- self.log('LOG_INFO', "Copied {0} to {1}".format(src, dst))
+ self.log("Copied {0} to {1}".format(src, dst))
except (IOError, os.error) as why:
raise BuildError("cp -p {0} {1} caused {2}".format(src,
dst, str(why)))
@@ -384,7 +509,7 @@ def write_stage_config(self, config_fname, stmts):
fd.write(stmts)
return stage_config
- def build_zone(self, view, rel_fname, view_data, root_domain, soa):
+ def build_zone(self, view, file_meta, view_data, root_domain):
"""
This function will write the zone's zone file to the the staging area
and call named-checkconf on the files before they are copied over to
@@ -393,15 +518,13 @@ def build_zone(self, view, rel_fname, view_data, root_domain, soa):
written to the file system `None` will be returned instead of the path
to the file.
"""
- self.log('LOG_INFO', "{0} for view '{1}' will be rebuilt.".format(soa,
- view))
-
- stage_fname = os.path.join(self.STAGE_DIR, rel_fname)
- self.write_stage_zone(stage_fname, root_domain, soa, rel_fname,
- view_data)
- self.log('LOG_INFO', "Built stage_{0}_file to "
- "{1}".format(view.name, stage_fname))
- self.named_checkzone(stage_fname, root_domain, soa)
+ stage_fname = os.path.join(self.STAGE_DIR, file_meta['rel_fname'])
+ self.write_stage_zone(
+ stage_fname, root_domain, file_meta['rel_fname'], view_data
+ )
+ self.log("Built stage_{0}_file to {1}".format(view.name, stage_fname),
+ root_domain=root_domain)
+ self.named_checkzone(stage_fname, root_domain)
prod_fname = self.stage_to_prod(stage_fname)
@@ -410,165 +533,307 @@ def build_zone(self, view, rel_fname, view_data, root_domain, soa):
def calc_fname(self, view, root_domain):
return "{0}.{1}".format(root_domain.name, view.name)
- def render_zone_stmt(self, zone_name, file_path):
+ def render_zone_stmt(self, soa, zone_name, file_meta):
zone_stmt = "zone \"{0}\" IN {{{{\n".format(zone_name)
zone_stmt += "\ttype {ztype};\n" # We'll format this later
- zone_stmt += "\tfile \"{0}\";\n".format(file_path)
+ if soa.is_signed:
+ zone_stmt += "\tfile \"{0}.signed\";\n".format(
+ file_meta['bind_fname']
+ )
+ else:
+ zone_stmt += "\tfile \"{0}\";\n".format(
+ file_meta['bind_fname']
+ )
zone_stmt += "}};\n"
return zone_stmt
- def verify_prev_build(self, prod_fname, view_data, root_domain, soa):
- if not view_data:
- return
- serial = get_serial(os.path.join(prod_fname))
+ def verify_previous_build(self, file_meta, view, root_domain, soa):
+ force_rebuild, new_serial = False, None
+ serial = get_serial(os.path.join(file_meta['prod_fname']))
if not serial.isdigit():
- if not soa.serial:
- soa.serial = int(time.time()) - 1
- soa.dirty = True
+ new_serial = int(time.time())
+ force_rebuild = True
# it's a new serial
- self.log('LOG_NOTICE', "{0} appears to be a new zone. Building"
- " {1} with initial serial {2}".format(soa, prod_fname,
- soa.serial + 1), soa=soa)
+ self.log(
+ 'LOG_NOTICE', "{0} appears to be a new zone. Building {1} "
+ "with initial serial {2}".format(soa, file_meta['prod_fname'],
+ new_serial),
+ root_domain=root_domain)
elif int(serial) != soa.serial:
# Looks like someone made some changes... let's nuke them.
# We should probably email someone too.
- self.log('LOG_NOTICE', "{0} has serial {1} in svn ({2}) and "
- "serial {3} in the database. SOA is being marked as "
- "dirty.".format(soa, serial, prod_fname, soa.serial),
- soa=soa)
- soa.dirty = True
+ self.log(
+ 'LOG_NOTICE', "{0} has serial {1} in svn ({2}) and serial "
+ "{3} in the database. Zone will be rebuilt."
+ .format(soa, serial, file_meta['prod_fname'],
+ soa.serial),
+ root_domain=root_domain)
+ force_rebuild = True
# Choose the highest serial so any slave nameservers don't get
# confused.
- soa.serial = max(int(serial), soa.serial)
-
- def build_view(self, view, view_data, root_domain, soa):
- rel_zdir = self.calc_target(root_domain, soa)
- fname = self.calc_fname(view, root_domain)
- rel_fname = os.path.join(rel_zdir, fname)
- prod_fname = os.path.join(self.PROD_DIR, rel_fname)
- # If there is zomething different about the zone, like it's new or
- # it's serial doesn't match the one if the db, :func:`verify_prev`
- # will mark the soa as dirty.
- self.verify_prev_build(prod_fname, view_data, root_domain, soa)
- if soa.dirty:
- # Update the new zone strings with the correct serial number.
- self.log('LOG_INFO', "{0} is seen as dirty.".format(soa), soa=soa)
- self.log('LOG_INFO', "Prev serial was {0}. Using serial "
- "new serial {1}.".format(soa.serial,
- soa.serial + 1), soa=soa)
- self.build_zone(view, rel_fname,
- view_data.format(serial=soa.serial + 1),
- root_domain, soa)
- else:
- # private_data and public_data are not used because the soa
- # was not dirty and doesn't have any new changes in it,
- # instead the data already in prod_fname is checked again
- # using named_checkzone. Later prod_fname is pointed to by
- # the view's config file in the form of a zone statement
- # (see render_zone_stmt).
- self.log('LOG_INFO', "{0} is seen as NOT dirty.".format(soa),
- soa=soa)
- self.named_checkzone(prod_fname, root_domain, soa)
-
- return self.render_zone_stmt(root_domain.name, prod_fname)
-
- def build_zone_files(self):
+ new_serial = max(int(serial), soa.serial)
+
+ return force_rebuild, new_serial
+
+ def get_file_meta(self, view, root_domain, soa):
"""
- This function builds and then writes zone files to the file system.
- This function also returns a list of zone statements.
+ This function trys to pull all file login into one place.
+ Files:
+ * rel_zone_dir
+ - This is the directory path to where the zone file will be
+ placed. It's relative to where the script things the SVN root
+ is. See :func:`calc_target` for more info.
+ * fname
+ - This is the name of the file, which is usually in the format
+ <zone-name>.<view-name>. See :func:`calc_fname` for more
+ info.
+ * rel_fname
+ - The joining of rel_zone_dir + fname
+
+ * prod_fname
+ - Where the final zone file will be written (full path name)
+
+ * bind_fnam
+ - The path name used in the zones `zone` statement. See
+ :func:`render_zone_stmt` for more info.
"""
- # Keep track of which zones we build and what they look like.
-
- private_zone_stmts, public_zone_stmts = [], []
+ file_meta = {}
+ rel_zone_dir = self.calc_target(root_domain, soa)
+ file_meta['fname'] = self.calc_fname(view, root_domain)
+ file_meta['rel_fname'] = os.path.join(rel_zone_dir, file_meta['fname'])
+ file_meta['prod_fname'] = os.path.join(self.PROD_DIR,
+ file_meta['rel_fname'])
+ file_meta['bind_fname'] = os.path.join(self.BIND_PREFIX,
+ file_meta['rel_fname'])
+ return file_meta
+
+ def build_zone_files(self, soa_pks_to_rebuild):
+ zone_stmts = {}
for soa in SOA.objects.all():
- zinfo = soa.root_domain, soa
-
- # The *_data vars help us to decide whether we should build a zone.
- # If there are no records in the public/private view the
- # corresponding *_data var will be the emptry string. We will not
- # build and write a zone file for a view that does not have any
- # data.
-
- t_start = time.time() # tic
- private_data, public_data = build_zone_data(*zinfo)
- build_time = time.time() - t_start # toc
- self.log('LOG_INFO', 'Built {0} in {1} seconds '.format(soa,