Merge "Optimize MiniDNS for fewer syscalls"

This commit is contained in:
Jenkins 2017-09-21 02:01:41 +00:00 committed by Gerrit Code Review
commit b926a1d4df
1 changed files with 93 additions and 85 deletions

View File

@ -13,25 +13,28 @@
# under the License. # under the License.
import os import os
import shutil
import tempfile
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
import six
from nova import exception from nova import exception
from nova.i18n import _ from nova.i18n import _
from nova.network import dns_driver from nova.network import dns_driver
CONF = cfg.CONF CONF = cfg.CONF
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class MiniDNS(dns_driver.DNSDriver): class MiniDNS(dns_driver.DNSDriver):
"""Trivial DNS driver. This will read/write to a local, flat file """Trivial DNS driver. This will read/write to either a local,
and have no effect on your actual DNS system. This class is flat file or an in memory StringIO and have no effect on your actual
strictly for testing purposes, and should keep you out of dependency DNS system. This class is strictly for testing purposes, and should
hell. keep you out of dependency hell.
A file is used when CONF.log_dir is set. This is relevant for when
two different DNS driver instances share the same data file.
Note that there is almost certainly a race condition here that Note that there is almost certainly a race condition here that
will manifest anytime instances are rapidly created and deleted. will manifest anytime instances are rapidly created and deleted.
@ -39,25 +42,23 @@ class MiniDNS(dns_driver.DNSDriver):
""" """
def __init__(self): def __init__(self):
filename = None
if CONF.log_dir: if CONF.log_dir:
self.filename = os.path.join(CONF.log_dir, "dnstest.txt") filename = os.path.join(CONF.log_dir, "dnstest.txt")
self.tempdir = None self.file = open(filename, 'w+')
else: else:
self.tempdir = tempfile.mkdtemp() self.file = six.StringIO()
self.filename = os.path.join(self.tempdir, "dnstest.txt") if not filename or not os.path.exists(filename):
LOG.debug('minidns file is |%s|', self.filename) self.file.write("# minidns\n\n\n")
self.file.flush()
if not os.path.exists(self.filename):
with open(self.filename, "w+") as f:
f.write("# minidns\n\n\n")
def get_domains(self): def get_domains(self):
entries = [] entries = []
with open(self.filename, 'r') as infile: self.file.seek(0)
for line in infile: for line in self.file:
entry = self.parse_line(line) entry = self.parse_line(line)
if entry and entry['address'] == 'domain': if entry and entry['address'] == 'domain':
entries.append(entry['name']) entries.append(entry['name'])
return entries return entries
def qualify(self, name, domain): def qualify(self, name, domain):
@ -79,9 +80,10 @@ class MiniDNS(dns_driver.DNSDriver):
if self.get_entries_by_name(name, domain): if self.get_entries_by_name(name, domain):
raise exception.FloatingIpDNSExists(name=name, domain=domain) raise exception.FloatingIpDNSExists(name=name, domain=domain)
with open(self.filename, 'a+') as outfile: self.file.seek(0, os.SEEK_END)
outfile.write("%s %s %s\n" % self.file.write("%s %s %s\n" %
(address, self.qualify(name, domain), type)) (address, self.qualify(name, domain), type))
self.file.flush()
def parse_line(self, line): def parse_line(self, line):
vals = line.split() vals = line.split()
@ -103,17 +105,19 @@ class MiniDNS(dns_driver.DNSDriver):
raise exception.InvalidInput(_("Invalid name")) raise exception.InvalidInput(_("Invalid name"))
deleted = False deleted = False
outfile = tempfile.NamedTemporaryFile('w', delete=False) keeps = []
with open(self.filename, 'r') as infile: self.file.seek(0)
for line in infile: for line in self.file:
entry = self.parse_line(line) entry = self.parse_line(line)
if (not entry or if (not entry or
entry['name'] != self.qualify(name, domain)): entry['name'] != self.qualify(name, domain)):
outfile.write(line) keeps.append(line)
else: else:
deleted = True deleted = True
outfile.close() self.file.truncate(0)
shutil.move(outfile.name, self.filename) self.file.seek(0)
self.file.write(''.join(keeps))
self.file.flush()
if not deleted: if not deleted:
LOG.warning('Cannot delete entry |%s|', self.qualify(name, domain)) LOG.warning('Cannot delete entry |%s|', self.qualify(name, domain))
raise exception.NotFound raise exception.NotFound
@ -123,76 +127,80 @@ class MiniDNS(dns_driver.DNSDriver):
if not self.get_entries_by_name(name, domain): if not self.get_entries_by_name(name, domain):
raise exception.NotFound raise exception.NotFound
outfile = tempfile.NamedTemporaryFile('w', delete=False) lines = []
with open(self.filename, 'r') as infile: self.file.seek(0)
for line in infile: for line in self.file:
entry = self.parse_line(line) entry = self.parse_line(line)
if (entry and if (entry and
entry['name'] == self.qualify(name, domain)): entry['name'] == self.qualify(name, domain)):
outfile.write("%s %s %s\n" % lines.append("%s %s %s\n" %
(address, self.qualify(name, domain), entry['type'])) (address, self.qualify(name, domain), entry['type']))
else: else:
outfile.write(line) lines.append(line)
outfile.close() self.file.truncate(0)
shutil.move(outfile.name, self.filename) self.file.seek(0)
self.file.write(''.join(lines))
self.file.flush()
def get_entries_by_address(self, address, domain): def get_entries_by_address(self, address, domain):
entries = [] entries = []
with open(self.filename, 'r') as infile: self.file.seek(0)
for line in infile: for line in self.file:
entry = self.parse_line(line) entry = self.parse_line(line)
if entry and entry['address'] == address.lower(): if entry and entry['address'] == address.lower():
if entry['name'].endswith(domain.lower()): if entry['name'].endswith(domain.lower()):
name = entry['name'].split(".")[0] name = entry['name'].split(".")[0]
if name not in entries: if name not in entries:
entries.append(name) entries.append(name)
return entries return entries
def get_entries_by_name(self, name, domain): def get_entries_by_name(self, name, domain):
entries = [] entries = []
with open(self.filename, 'r') as infile: self.file.seek(0)
for line in infile: for line in self.file:
entry = self.parse_line(line) entry = self.parse_line(line)
if (entry and if (entry and
entry['name'] == self.qualify(name, domain)): entry['name'] == self.qualify(name, domain)):
entries.append(entry['address']) entries.append(entry['address'])
return entries return entries
def delete_dns_file(self): def delete_dns_file(self):
if os.path.exists(self.filename): self.file.close()
try: try:
os.remove(self.filename) if os.path.exists(self.file.name):
except OSError: try:
pass os.remove(self.file.name)
if self.tempdir and os.path.exists(self.tempdir): except OSError:
try: pass
shutil.rmtree(self.tempdir) except AttributeError:
except OSError: # This was a BytesIO, which has no name.
pass pass
def create_domain(self, fqdomain): def create_domain(self, fqdomain):
if self.get_entries_by_name(fqdomain, ''): if self.get_entries_by_name(fqdomain, ''):
raise exception.FloatingIpDNSExists(name=fqdomain, domain='') raise exception.FloatingIpDNSExists(name=fqdomain, domain='')
with open(self.filename, 'a+') as outfile: self.file.seek(0, os.SEEK_END)
outfile.write("%s %s %s\n" % self.file.write("%s %s %s\n" % ('domain', fqdomain, 'domain'))
('domain', fqdomain, 'domain')) self.file.flush()
def delete_domain(self, fqdomain): def delete_domain(self, fqdomain):
deleted = False deleted = False
outfile = tempfile.NamedTemporaryFile('w', delete=False) keeps = []
with open(self.filename, 'r') as infile: self.file.seek(0)
for line in infile: for line in self.file:
entry = self.parse_line(line) entry = self.parse_line(line)
if (not entry or if (not entry or
entry['domain'] != fqdomain.lower()): entry['domain'] != fqdomain.lower()):
outfile.write(line) keeps.append(line)
else: else:
LOG.info("deleted %s", entry) LOG.info("deleted %s", entry)
deleted = True deleted = True
outfile.close() self.file.truncate(0)
shutil.move(outfile.name, self.filename) self.file.seek(0)
self.file.write(''.join(keeps))
self.file.flush()
if not deleted: if not deleted:
LOG.warning('Cannot delete domain |%s|', fqdomain) LOG.warning('Cannot delete domain |%s|', fqdomain)
raise exception.NotFound raise exception.NotFound