Exemple #1
0
    def find_zone_serial(self, zone_name):
        """Get serial from a zone by running knotc

        :returns: serial (int or None)
        :raises: exceptions.Backend
        """
        zone_name = zone_name.rstrip('.')
        LOG.debug("Finding %s", zone_name)
        # Output example:
        # [530336536.com.] type: slave | serial: 0 | next-event: idle |
        # auto-dnssec: disabled]
        try:
            out, err = execute(self._knotc_cmd_name, 'zone-status', zone_name)
        except ProcessExecutionError as e:
            if 'no such zone' in e.stdout:
                # Zone not found
                return None

            LOG.error(_LE("Command output: %(out)r Stderr: %(err)r"), {
                'out': e.stdout,
                'err': e.stderr
            })
            raise exceptions.Backend(e)

        try:
            serial = out.split('|')[1].split()[1]
            return int(serial)
        except Exception as e:
            LOG.error(_LE("Unable to parse knotc output: %r"), out)
            raise exceptions.Backend("Unexpected knotc zone-status output")
Exemple #2
0
 def _check_dirs(self, *dirnames):
     """Check if directories are writable
     """
     for dn in dirnames:
         if not os.path.isdir(dn):
             raise exceptions.Backend("Missing directory %s" % dn)
         if not os.access(dn, os.W_OK):
             raise exceptions.Backend("Directory not writable: %s" % dn)
Exemple #3
0
 def _execute_nsd4(self, command):
     try:
         LOG.debug('Executing NSD4 control call: %s on %s' % (command,
                   self.host))
         result = self._command(command)
     except (ssl.SSLError, socket.error) as e:
         LOG.debug('NSD4 control call failure: %s' % e)
         raise exceptions.Backend(e)
     if result != 'ok':
         raise exceptions.Backend(result)
Exemple #4
0
    def _rebuild_data_cdb(self):
        """Rebuild data.cdb file from zone datafiles
        Requires global lock

        On zone creation, axfr-get creates datafiles atomically by doing
        rename. On zone deletion, os.remove deletes the file atomically
        Globbing and reading the datafiles can be done without locking on
        them.
        The data and data.cdb files are written into a unique temp directory
        """

        tmpdir = tempfile.mkdtemp(dir=self._datafiles_dir)
        data_fn = os.path.join(tmpdir, 'data')
        tmp_cdb_fn = os.path.join(tmpdir, 'data.cdb')

        try:
            self._concatenate_zone_datafiles(data_fn,
                                             self._datafiles_path_glob)
            # Generate the data.cdb file
            LOG.info("Updating data.cdb")
            LOG.debug("Convert %s to %s", data_fn, tmp_cdb_fn)
            try:
                out, err = execute(
                    cfg.CONF[CFG_GROUP].tinydns_data_cmd_name,
                    cwd=tmpdir
                )
            except ProcessExecutionError as e:
                LOG.error("Failed to generate data.cdb")
                LOG.error("Command output: %(out)r Stderr: %(err)r",
                          {
                              'out': e.stdout,
                              'err': e.stderr
                          })
                raise exceptions.Backend("Failed to generate data.cdb")

            LOG.debug("Move %s to %s", tmp_cdb_fn, self._tinydns_cdb_filename)
            try:
                os.rename(tmp_cdb_fn, self._tinydns_cdb_filename)
            except OSError:
                os.remove(tmp_cdb_fn)
                LOG.error("Unable to move data.cdb to %s",
                          self._tinydns_cdb_filename)
                raise exceptions.Backend("Unable to move data.cdb")

        finally:
            try:
                os.remove(data_fn)
            except OSError:
                pass
            try:
                os.removedirs(tmpdir)
            except OSError:
                pass
Exemple #5
0
    def _execute_knotc(self, *knotc_args, **kw):
        """Run the Knot client and check the output

        :param expected_output: expected output (default: 'OK')
        :type expected_output: str
        :param expected_error: expected alternative output, will be \
        logged as info(). Default: not set.
        :type expected_error: str
        """
        # Knotc returns "0" even on failure, we have to check for 'OK'
        # https://gitlab.labs.nic.cz/labs/knot/issues/456

        LOG.debug("Executing knotc with %r", knotc_args)
        expected = kw.get('expected_output', 'OK')
        expected_alt = kw.get('expected_error', None)
        try:
            out, err = execute(self._knotc_cmd_name, *knotc_args)
            out = out.rstrip()
            LOG.debug("Command output: %r" % out)
            if out != expected:
                if expected_alt is not None and out == expected_alt:
                    LOG.info(_LI("Ignoring error: %r"), out)
                else:
                    raise ProcessExecutionError(stdout=out, stderr=err)

        except ProcessExecutionError as e:
            LOG.error(_LE("Command output: %(out)r Stderr: %(err)r"), {
                'out': e.stdout,
                'err': e.stderr
            })
            raise exceptions.Backend(e)
Exemple #6
0
    def _check_zone_exists(self, zone):

        try:
            requests.get(self._build_url(zone),
                         headers=self.headers).raise_for_status()
        except requests.HTTPError as e:
            if e.response.status_code == 404:
                return False
            else:
                LOG.error('HTTP error in check zone exists. Zone %s', zone)
                raise exceptions.Backend(e)
        except requests.ConnectionError as e:
            LOG.error('Connection error in check zone exists. Zone %s', zone)
            raise exceptions.Backend(e)

        return True
Exemple #7
0
 def _execute_rndc(self, rndc_call):
     try:
         LOG.debug('Executing RNDC call: %s' % " ".join(rndc_call))
         utils.execute(*rndc_call)
     except utils.processutils.ProcessExecutionError as e:
         LOG.debug('RNDC call failure: %s' % e)
         raise exceptions.Backend(e)
Exemple #8
0
    def _perform_axfr_from_minidns(self, zone_name):
        """Instruct axfr-get to request an AXFR from MiniDNS.

        :raises: exceptions.Backend on error
        """
        zone_fn = self._datafiles_path_tpl % zone_name
        zone_tmp_fn = self._datafiles_tmp_path_tpl % zone_name

        # Perform AXFR, create or update a zone datafile
        # No need to lock globally here.
        # Axfr-get creates the datafile atomically by doing rename
        mdns_hostname, mdns_port = random.choice(self._masters)
        with lockutils.lock("%s.lock" % zone_name):
            LOG.debug("writing to %s", zone_fn)
            cmd = (self._tcpclient_cmd_name, mdns_hostname, "%d" % mdns_port,
                   self._axfr_get_cmd_name, zone_name, zone_fn, zone_tmp_fn)

            LOG.debug("Executing AXFR as %r", ' '.join(cmd))
            try:
                out, err = execute(*cmd)
            except ProcessExecutionError as e:
                LOG.error("Error executing AXFR as %r", ' '.join(cmd))
                LOG.error("Command output: %(out)r Stderr: %(err)r", {
                    'out': e.stdout,
                    'err': e.stderr
                })
                raise exceptions.Backend(str(e))

            finally:
                try:
                    os.remove(zone_tmp_fn)
                except OSError:
                    pass
Exemple #9
0
 def delete_domain(self, context, domain):
     LOG.debug('Delete Domain')
     response, retry = self._make_and_send_dns_message(
         domain.name, self.timeout, CC, DELETE, CLASSCC, self.host,
         self.port)
     if response is None:
         raise exceptions.Backend()
Exemple #10
0
 def delete_zone(self, context, zone):
     LOG.debug('Delete Zone')
     response, retry = self._make_and_send_dns_message(
         zone.name, self.timeout, pcodes.CC, pcodes.DELETE, pcodes.CLASSCC,
         self.host, self.port)
     if response is None:
         raise exceptions.Backend("failed delete_zone()")
Exemple #11
0
    def gen_create_payload(self, zone, masters, contract_id, gid, tenant_id,
                           target):
        if contract_id is None:
            raise exceptions.Backend(
                'contractId is required for zone creation')

        masters = self.build_masters_field(masters)
        body = {
            'zone': zone['name'],
            'type': 'secondary',
            'comment': 'Created by Designate for Tenant %s' % tenant_id,
            'masters': masters,
        }
        # Add tsigKey if it exists
        if target.options.get('tsig_key_name'):
            # It's not mentioned in doc, but json schema supports specification
            # TsigKey in the same zone creation body
            body.update({'tsigKey': self.gen_tsig_payload(target)})

        params = {
            'contractId': contract_id,
            'gid': gid,
        }
        return {
            'url': 'config-dns/v2/zones',
            'params': params,
            'json': body,
        }
Exemple #12
0
    def delete_zone(self, context, zone):
        """Delete a DNS zone"""

        try:
            requests.delete(self._build_url(zone.name),
                            headers=self.headers).raise_for_status()
        except requests.HTTPError as e:
            raise exceptions.Backend(e)
Exemple #13
0
 def wrapper(*a, **kw):
     try:
         return fn(*a, **kw)
     except exceptions.Backend:
         raise
     except Exception as e:
         LOG.error("Unhandled exception %s", e, exc_info=True)
         raise exceptions.Backend(str(e))
Exemple #14
0
 def wrapper(*a, **kw):
     try:
         return fn(*a, **kw)
     except exceptions.Backend as e:
         raise e
     except Exception as e:
         LOG.error(_LE("Unhandled exception %s"), e.message, exc_info=True)
         raise exceptions.Backend(e.message)
Exemple #15
0
def wrap_backend_call():
    """
    Wraps backend calls, ensuring any exception raised is a Backend exception.
    """
    try:
        yield
    except exceptions.Backend:
        raise
    except Exception as e:
        raise exceptions.Backend('Unknown backend failure: %r' % e)
Exemple #16
0
    def create_zone(self, payload):
        result = self.post(payload)
        # NOTE: ignore error about duplicate SZ in AKAMAI
        if result.status_code == 409 and result.reason == 'Conflict':
            LOG.info("Can't create zone %s because it already exists",
                     payload['json']['zone'])

        elif not result.ok:
            json_res = result.json()
            raise exceptions.Backend('Zone creation failed due to: %s' %
                                     json_res['detail'])
Exemple #17
0
    def validate_deletion_is_complete(self, request_id):
        check_url = '/config-dns/v2/zones/delete-requests/%s' % request_id
        deleted = False
        attempt = 0
        while not deleted and attempt < 10:
            result = self.get(check_url)
            deleted = result.json()['isComplete']
            attempt += 1
            time.sleep(1.0)

        if not deleted:
            raise exceptions.Backend('Zone was not deleted after %s attempts' %
                                     attempt)
Exemple #18
0
    def __init__(self, agent_service):
        """Configure the backend"""
        super(MSDNSBackend, self).__init__(agent_service)

        self._dnsutils = utilsfactory.get_dnsutils()

        masters = cfg.CONF['service:agent'].masters
        if not masters:
            raise exceptions.Backend("Missing agent AXFR masters")
        # Only ip addresses are needed
        self._masters = [ns.split(":")[0] for ns in masters]

        LOG.info("AXFR masters: %r", self._masters)
Exemple #19
0
    def delete_zone(self, context, zone):
        """Delete a DNS zone"""

        # First verify that the zone exists
        if self._check_zone_exists(zone):
            try:
                requests.delete(self._build_url(zone),
                                headers=self.headers).raise_for_status()
            except requests.HTTPError as e:
                raise exceptions.Backend(e)
        else:
            LOG.warning(
                "Trying to delete zone %s but that zone is not "
                "present in the ns1 backend. Assuming success.", zone)
Exemple #20
0
    def create_zone(self, context, zone):
        """Create a DNS zone"""

        masters = []
        for master in self.masters:
            host = master.host
            if netaddr.IPAddress(host).version == 6:
                host = '[%s]' % host
            masters.append('%s:%d' % (host, master.port))

        data = {
            "name": zone.name,
            "kind": "slave",
            "masters": masters,

        }
        if self.tsigkey_name:
            data['slave_tsig_key_ids'] = [self.tsigkey_name]

        if self._check_zone_exists(zone):
            LOG.info(
                '%s exists on the server. Deleting zone before creation', zone
            )

            try:
                self.delete_zone(context, zone)
            except exceptions.Backend:
                LOG.error('Could not delete pre-existing zone %s', zone)
                raise

        try:
            requests.post(
                self._build_url(),
                json=data,
                headers=self.headers
            ).raise_for_status()
        except requests.HTTPError as e:
            # check if the zone was actually created - even with errors pdns
            # will create the zone sometimes
            if self._check_zone_exists(zone):
                LOG.info("%s was created with an error. Deleting zone", zone)
                try:
                    self.delete_zone(context, zone)
                except exceptions.Backend:
                    LOG.error('Could not delete errored zone %s', zone)
            raise exceptions.Backend(e)

        self.mdns_api.notify_zone_changed(
            context, zone, self.host, self.port, self.timeout,
            self.retry_interval, self.max_retries, self.delay)
Exemple #21
0
    def _execute_rndc(self, rndc_op):
        """Execute rndc

        :param rndc_op: rndc arguments
        :type rndc_op: list
        :returns: None
        :raises: exceptions.Backend
        """
        try:
            rndc_call = self._rndc_call_base + rndc_op
            LOG.debug('Executing RNDC call: %r', rndc_call)
            utils.execute(*rndc_call)
        except utils.processutils.ProcessExecutionError as e:
            raise exceptions.Backend(e)
Exemple #22
0
    def delete_zone(self, context, zone):
        """Delete a DNS zone"""

        headers = {"X-API-Key": self.api_token}

        try:
            requests.delete(self._build_url(zone.name),
                            headers=headers).raise_for_status()
        except requests.HTTPError as e:
            if e.response.status_code == 404 or e.response.status_code == 422:
                LOG.warning("Trying to delete zone %s but that zone is not "
                            "present in the pdns backend. Assuming success.")
                return

            raise exceptions.Backend(e)
Exemple #23
0
    def delete_zone(self, zone_name):
        # - try to delete with force=True
        # - if we get Forbidden error - try to delete it with Checks logic

        result = self.post(self.gen_delete_payload(zone_name, force=True))

        if result.status_code == 403 and result.reason == 'Forbidden':
            result = self.post(self.gen_delete_payload(zone_name, force=False))
            if result.ok:
                request_id = result.json().get('requestId')
                LOG.info('Run soft delete for zone (%s) and requestId (%s)',
                         zone_name, request_id)

                if request_id is None:
                    reason = 'requestId missed in response'
                    raise exceptions.Backend(
                        'Zone deletion failed due to: %s' % reason)

                self.validate_deletion_is_complete(request_id)

        if not result.ok and result.status_code != 404:
            reason = result.json().get('detail') or result.json()
            raise exceptions.Backend('Zone deletion failed due to: %s' %
                                     reason)
Exemple #24
0
 def _check_conf(self):
     """Run gdnsd to check its configuration
     """
     try:
         out, err = utils.execute(
             cfg.CONF[CFG_GROUP].gdnsd_cmd_name,
             '-D', '-x', 'checkconf', '-c', self._confdir_path,
             run_as_root=False,
         )
     except ProcessExecutionError as e:
         LOG.error("Command output: %(out)r Stderr: %(err)r",
                   {
                       'out': e.stdout,
                       'err': e.stderr
                   })
         raise exceptions.Backend("Configuration check failed")
Exemple #25
0
    def _execute_rndc(self, rndc_op):
        """Execute rndc

        :param rndc_op: rndc arguments
        :type rndc_op: list
        :returns: None
        :raises: exceptions.Backend
        """
        try:
            rndc_call = self._rndc_call_base + rndc_op
            LOG.debug('Executing RNDC call: %r with timeout %s', rndc_call,
                      self._rndc_timeout)
            utils.execute(*rndc_call, timeout=self._rndc_timeout)
        except (utils.processutils.ProcessExecutionError,
                subprocess.TimeoutExpired) as e:
            raise exceptions.Backend(e)
    def create_zone(self, context, zone):
        """Create a DNS zone"""

        masters = \
            ['%s:%d' % (master.host, master.port) for master in self.masters]

        data = {
            "name": zone.name,
            "kind": "slave",
            "masters": masters,
        }
        headers = {"X-API-Key": self.api_token}

        try:
            requests.post(self._build_url(), json=data,
                          headers=headers).raise_for_status()
        except requests.HTTPError as e:
            raise exceptions.Backend(e)
Exemple #27
0
    def create_zone(self, context, zone):

        master = self._get_master()
        # designate requires "." at end of zone name, NS1 requires omitting
        data = {
            "zone": zone.name.rstrip('.'),
            "secondary": {
                "enabled": True,
                "primary_ip": master.host,
                "primary_port": master.port
            }
        }
        if self.tsigkey_name:
            tsig = {
                "enabled": True,
                "hash": self.tsigkey_hash,
                "name": self.tsigkey_name,
                "key": self.tsigkey_value
            }
            data['secondary']['tsig'] = tsig

        if not self._check_zone_exists(zone):
            try:
                requests.put(self._build_url(zone),
                             json=data,
                             headers=self.headers).raise_for_status()
            except requests.HTTPError as e:
                # check if the zone was actually created
                if self._check_zone_exists(zone):
                    LOG.info("%s was created with an error. Deleting zone",
                             zone.name)
                    try:
                        self.delete_zone(context, zone)
                    except exceptions.Backend:
                        LOG.error('Could not delete errored zone %s',
                                  zone.name)
                raise exceptions.Backend(e)
        else:
            LOG.info("Can't create zone %s because it already exists",
                     zone.name)

        self.mdns_api.notify_zone_changed(context, zone, self.host, self.port,
                                          self.timeout, self.retry_interval,
                                          self.max_retries, self.delay)
Exemple #28
0
    def __init__(self, *a, **kw):
        """Configure the backend"""
        super(DjbdnsBackend, self).__init__(*a, **kw)
        conf = cfg.CONF[CFG_GROUP_NAME]

        self._resolver = dns.resolver.Resolver(configure=False)
        self._resolver.timeout = SOA_QUERY_TIMEOUT
        self._resolver.lifetime = SOA_QUERY_TIMEOUT
        self._resolver.nameservers = [conf.query_destination]
        self._masters = [
            utils.split_host_port(ns)
            for ns in cfg.CONF['service:agent'].masters
        ]
        LOG.info("Resolvers: %r", self._resolver.nameservers)
        LOG.info("AXFR masters: %r", self._masters)
        if not self._masters:
            raise exceptions.Backend("Missing agent AXFR masters")

        self._tcpclient_cmd_name = conf.tcpclient_cmd_name
        self._axfr_get_cmd_name = conf.axfr_get_cmd_name

        # Directory where data.cdb lives, usually /var/lib/djbdns/root
        tinydns_root_dir = os.path.join(conf.tinydns_datadir, 'root')

        # Usually /var/lib/djbdns/root/data.cdb
        self._tinydns_cdb_filename = os.path.join(tinydns_root_dir, 'data.cdb')
        LOG.info("data.cdb path: %r", self._tinydns_cdb_filename)

        # Where the agent puts the zone datafiles,
        # usually /var/lib/djbdns/datafiles
        self._datafiles_dir = datafiles_dir = os.path.join(
            conf.tinydns_datadir, 'datafiles')
        self._datafiles_tmp_path_tpl = os.path.join(datafiles_dir, "%s.ztmp")
        self._datafiles_path_tpl = os.path.join(datafiles_dir, "%s.zonedata")
        self._datafiles_path_glob = self._datafiles_path_tpl % '*'

        self._check_dirs(tinydns_root_dir, datafiles_dir)
Exemple #29
0
 def test_create_zone_raises_on_exception(self, mock_execute):
     mock_execute.side_effect = exceptions.Backend('badop')
     self.assertRaises(exceptions.Backend, self.backend.create_zone,
                       self.admin_context, self.zone)
Exemple #30
0
    def test_delete_zone_already_deleted(self, mock_execute):
        mock_execute.side_effect = exceptions.Backend('not found')

        self.backend.delete_zone(self.admin_context, self.zone)