Ejemplo n.º 1
0
def fip_key_to_data(key):
    m = re.match(FIP_REGEX, key)

    # NOTE: Ensure that the fip matches region:floatingip_id or raise, if
    # not this will cause a 500.
    if m is None:
        msg = 'Floating IP %s is not in the format of <region>:<uuid>'
        raise exceptions.BadRequest(msg % key)
    return m.groups()
Ejemplo n.º 2
0
    def patch_one(self, zone_id):
        """Update Zone"""
        # TODO(kiall): This needs cleanup to say the least..
        request = pecan.request
        context = request.environ['context']
        body = request.body_dict
        response = pecan.response

        # TODO(kiall): Validate we have a sane UUID for zone_id

        # Fetch the existing zone
        zone = self.central_api.get_zone(context, zone_id)

        # Don't allow updates to zones that are being deleted
        if zone.action == "DELETE":
            raise exceptions.BadRequest('Can not update a deleting zone')

        if request.content_type == 'application/json-patch+json':
            # Possible pattern:
            #
            # 1) Load existing zone.
            # 2) Apply patch, maintain list of changes.
            # 3) Return changes, after passing through the code ^ for plain
            #    JSON.
            #
            # Difficulties:
            #
            # 1) "Nested" resources? records inside a recordset.
            # 2) What to do when a zone doesn't exist in the first place?
            # 3) ...?
            raise NotImplemented('json-patch not implemented')
        else:
            # Update the zone object with the new values
            zone = DesignateAdapter.parse('API_v2', body, zone)

            zone.validate()
            # If masters are specified then we set zone.transferred_at to None
            # which will cause a new transfer
            if 'attributes' in zone.obj_what_changed():
                zone.transferred_at = None

            # Update and persist the resource

            if zone.type == 'SECONDARY' and 'email' in zone.obj_what_changed():
                msg = "Changed email is not allowed."
                raise exceptions.InvalidObject(msg)

            increment_serial = zone.type == 'PRIMARY'
            zone = self.central_api.update_zone(
                context, zone, increment_serial=increment_serial)

        if zone.status == 'PENDING':
            response.status_int = 202
        else:
            response.status_int = 200

        return DesignateAdapter.render('API_v2', zone, request=request)
Ejemplo n.º 3
0
 def _parse_zonefile(self, request):
     """ Parses a POSTed zonefile into a dnspython zone object """
     try:
         dnszone = dns.zone.from_text(
             request.body,
             # Don't relativize, otherwise we end
             # up with '@' record names.
             relativize=False,
             # Dont check origin, we allow missing
             # NS records (missing SOA records are
             # taken care of in _create_zone).
             check_origin=False)
     except dns.zone.UnknownOrigin:
         raise exceptions.BadRequest('The $ORIGIN statement is required and'
                                     ' must be the first statement in the'
                                     ' zonefile.')
     except dns.exception.SyntaxError:
         raise exceptions.BadRequest('Malformed zonefile.')
     return dnszone
Ejemplo n.º 4
0
    def put_one(self, zone_id, recordset_id):
        """Update RecordSet"""
        request = pecan.request
        context = request.environ['context']
        body = request.body_dict
        response = pecan.response

        # Fetch the existing recordset
        recordset = self.central_api.get_recordset(context, zone_id,
                                                   recordset_id)

        # TODO(graham): Move this further down the stack
        if recordset.managed and not context.edit_managed_records:
            raise exceptions.BadRequest('Managed records may not be updated')

        # SOA recordsets cannot be updated manually
        if recordset['type'] == 'SOA':
            raise exceptions.BadRequest(
                'Updating SOA recordsets is not allowed')

        # NS recordsets at the zone root cannot be manually updated
        if recordset['type'] == 'NS':
            zone = self.central_api.get_domain(context, zone_id)
            if recordset['name'] == zone['name']:
                raise exceptions.BadRequest(
                    'Updating a root zone NS record is not allowed')

        # Convert to APIv2 Format

        recordset = DesignateAdapter.parse('API_v2', body, recordset)

        recordset.validate()

        # Persist the resource
        recordset = self.central_api.update_recordset(context, recordset)

        if recordset['status'] == 'PENDING':
            response.status_int = 202
        else:
            response.status_int = 200

        return DesignateAdapter.render('API_v2', recordset, request=request)
Ejemplo n.º 5
0
 def _apply_filter_params(self, params, accepted_filters, criterion):
     invalid = []
     for k in params:
         if k in accepted_filters:
             criterion[k] = params[k].replace("*", "%")
         else:
             invalid.append(k)
     if invalid:
         raise exceptions.BadRequest('Invalid filters %s' %
                                     ', '.join(invalid))
     else:
         return criterion
Ejemplo n.º 6
0
    def test_call_exception_raised(self, mock_notify):
        self.backend.create_zone.side_effect = exceptions.BadRequest()
        self.zone = objects.Zone(name='example.org.', action='CREATE')
        self.actor = zone.ZoneActionOnTarget(
            self.executor,
            self.context,
            self.zone,
            self.target,
        )

        self.assertFalse(self.actor())

        mock_notify.assert_not_called()
Ejemplo n.º 7
0
    def get_all(self, export_id):
        context = pecan.request.environ['context']
        target = {'tenant_id': context.tenant}
        policy.check('zone_export', context, target)

        export = self.central_api.get_zone_export(context, export_id)

        if export.location and export.location.startswith('designate://'):
            return self.central_api.\
                export_zone(context, export['zone_id'])
        else:
            msg = 'Zone can not be exported synchronously'
            raise exceptions.BadRequest(msg)
Ejemplo n.º 8
0
    def post_all(self):
        """Initialize a Target Syncing"""
        request = pecan.request
        context = request.environ['context']

        body = request.body_dict

        fields = ['target_id', 'timestamp']
        for f in fields:
            if f not in body:
                raise exceptions.BadRequest('Failed to supply correct fields')

        if (not isinstance(body['timestamp'], int) or body['timestamp'] < 0):
            raise exceptions.BadRequest(
                'Timestamp should be a positive integer')

        pool_id = CONF['service:pool_manager'].pool_id

        return {
            'message':
            self.pool_mgr_api.target_sync(context, pool_id, body['target_id'],
                                          body['timestamp'])
        }
Ejemplo n.º 9
0
 def _create_zone(self, context, dnspython_zone):
     """ Creates the initial zone """
     # dnspython never builds a zone with more than one SOA, even if we give
     # it a zonefile that contains more than one
     soa = dnspython_zone.get_rdataset(dnspython_zone.origin, 'SOA')
     if soa is None:
         raise exceptions.BadRequest('An SOA record is required')
     email = soa[0].rname.to_text().rstrip('.')
     email = email.replace('.', '@', 1)
     values = {
         'name': dnspython_zone.origin.to_text(),
         'email': email,
         'ttl': str(soa.ttl)
     }
     return central_api.create_domain(context, values)
Ejemplo n.º 10
0
def update_record(domain_id, record_id):
    context = flask.request.environ.get('context')
    values = flask.request.json

    central_api = central_rpcapi.CentralAPI.get_instance()

    # NOTE: We need to ensure the domain actually exists, otherwise we may
    #       return a record not found instead of a domain not found
    criterion = {"id": domain_id, "type": "PRIMARY", "action": "!DELETE"}
    central_api.find_zone(context, criterion)

    # Fetch the existing resource
    # NOTE(kiall): We use "find_record" rather than "get_record" as we do not
    #              have the recordset_id.
    criterion = {'zone_id': domain_id, 'id': record_id}
    record = central_api.find_record(context, criterion)

    # TODO(graham): Move this further down the stack
    if record.managed and not context.edit_managed_records:
        raise exceptions.BadRequest('Managed records may not be updated')

    # Find the associated recordset
    recordset = central_api.get_recordset(context, domain_id,
                                          record.recordset_id)

    # Prepare a dict of fields for validation
    record_data = record_schema.filter(_format_record_v1(record, recordset))
    record_data.update(values)

    # Validate the new set of data
    record_schema.validate(record_data)

    # Update and persist the resource
    record.update(_extract_record_values(values))
    record = central_api.update_record(context, record)

    # Update the recordset resource (if necessary)
    recordset.update(_extract_recordset_values(values))
    if len(recordset.obj_what_changed()) > 0:
        recordset = central_api.update_recordset(context, recordset)
        LOG.info(_LI("Updated %(recordset)s"), {'recordset': recordset})

    # Format and return the response
    record = _format_record_v1(record, recordset)

    return flask.jsonify(record_schema.filter(record))
Ejemplo n.º 11
0
    def get_one(self, zone_id):
        """ Get Zone """
        # TODO(kiall): Validate we have a sane UUID for zone_id

        request = pecan.request
        context = request.environ['context']
        if 'Accept' not in request.headers:
            raise exceptions.BadRequest('Missing Accept header')
        best_match = request.accept.best_match(
            ['text/dns', 'application/json'])
        if best_match == 'text/dns':
            return self._get_zonefile(request, context, zone_id)
        elif best_match == 'application/json':
            return self._get_json(request, context, zone_id)
        else:
            raise exceptions.UnsupportedAccept(
                'Accept must be text/dns or application/json')
Ejemplo n.º 12
0
    def delete_one(self, zone_id, recordset_id):
        """Delete RecordSet"""
        request = pecan.request
        response = pecan.response
        context = request.environ['context']

        # Fetch the existing recordset
        recordset = self.central_api.get_recordset(context, zone_id,
                                                   recordset_id)
        if recordset['type'] == 'SOA':
            raise exceptions.BadRequest(
                'Deleting a SOA recordset is not allowed')

        recordset = self.central_api.delete_recordset(context, zone_id,
                                                      recordset_id)
        response.status_int = 202

        return DesignateAdapter.render('API_v2', recordset, request=request)
Ejemplo n.º 13
0
def from_dnspython_zone(dnspython_zone):
    # dnspython never builds a zone with more than one SOA, even if we give
    # it a zonefile that contains more than one
    soa = dnspython_zone.get_rdataset(dnspython_zone.origin, 'SOA')
    if soa is None:
        raise exceptions.BadRequest('An SOA record is required')
    email = soa[0].rname.to_text().rstrip('.')
    email = email.replace('.', '@', 1)
    values = {
        'name': dnspython_zone.origin.to_text(),
        'email': email,
        'ttl': soa.ttl,
        'serial': soa[0].serial,
        'retry': soa[0].retry,
        'expire': soa[0].expire
    }

    zone = objects.Domain(**values)

    rrsets = dnspyrecords_to_recordsetlist(dnspython_zone.nodes)
    zone.recordsets = rrsets
    return zone
Ejemplo n.º 14
0
def delete_record(domain_id, record_id):
    context = flask.request.environ.get('context')

    central_api = central_rpcapi.CentralAPI.get_instance()

    # NOTE: We need to ensure the domain actually exists, otherwise we may
    #       return a record not found instead of a domain not found
    criterion = {"id": domain_id, "type": "PRIMARY"}
    central_api.find_domain(context, criterion=criterion)

    # Find the record
    criterion = {'domain_id': domain_id, 'id': record_id}
    record = central_api.find_record(context, criterion)

    # Cannot delete a managed record via the API.
    if record['managed'] is True:
        raise exceptions.BadRequest('Managed records may not be deleted')

    central_api.delete_record(context, domain_id, record['recordset_id'],
                              record_id)

    _delete_recordset_if_empty(context, domain_id, record['recordset_id'])
    return flask.Response(status=200)
Ejemplo n.º 15
0
    def update_domain(self, context, domain_id, values, increment_serial=True):
        # TODO(kiall): Refactor this method into *MUCH* smaller chunks.
        domain = self.storage_api.get_domain(context, domain_id)

        target = {
            'domain_id': domain_id,
            'domain_name': domain['name'],
            'tenant_id': domain['tenant_id']
        }

        policy.check('update_domain', context, target)

        if 'tenant_id' in values:
            # NOTE(kiall): Ensure the user is allowed to delete a domain from
            #              the original tenant.
            policy.check('delete_domain', context, target)

            # NOTE(kiall): Ensure the user is allowed to create a domain in
            #              the new tenant.
            target = {'domain_id': domain_id, 'tenant_id': values['tenant_id']}
            policy.check('create_domain', context, target)

        if 'name' in values and values['name'] != domain['name']:
            raise exceptions.BadRequest('Renaming a domain is not allowed')

        if increment_serial:
            # Increment the serial number
            values['serial'] = utils.increment_serial(domain['serial'])

        with self.storage_api.update_domain(context, domain_id,
                                            values) as domain:
            with wrap_backend_call():
                self.backend.update_domain(context, domain)

        utils.notify(context, 'central', 'domain.update', domain)

        return domain
Ejemplo n.º 16
0
def from_dnspython_zone(dnspython_zone):
    # dnspython never builds a zone with more than one SOA, even if we give
    # it a zonefile that contains more than one
    soa = dnspython_zone.get_rdataset(dnspython_zone.origin, 'SOA')
    if soa is None:
        raise exceptions.BadRequest('An SOA record is required')
    if soa.ttl == 0:
        soa.ttl = cfg.CONF['service:central'].min_ttl
    email = soa[0].rname.to_text(omit_final_dot=True).decode('utf-8')
    email = email.replace('.', '@', 1)
    values = {
        'name': dnspython_zone.origin.to_text().decode('utf-8'),
        'email': email,
        'ttl': soa.ttl,
        'serial': soa[0].serial,
        'retry': soa[0].retry,
        'expire': soa[0].expire
    }

    zone = objects.Zone(**values)

    rrsets = dnspyrecords_to_recordsetlist(dnspython_zone.nodes)
    zone.recordsets = rrsets
    return zone
Ejemplo n.º 17
0
    def put_one(self, zone_id, recordset_id):
        """Update RecordSet"""
        request = pecan.request
        context = request.environ['context']
        body = request.body_dict
        response = pecan.response

        # Fetch the existing recordset
        recordset = self.central_api.get_recordset(context, zone_id,
                                                   recordset_id)

        # SOA recordsets cannot be updated manually
        if recordset['type'] == 'SOA':
            raise exceptions.BadRequest(
                'Updating SOA recordsets is not allowed')

        # NS recordsets at the zone root cannot be manually updated
        if recordset['type'] == 'NS':
            zone = self.central_api.get_domain(context, zone_id)
            if recordset['name'] == zone['name']:
                raise exceptions.BadRequest(
                    'Updating a root zone NS record is not allowed')

        # Convert to APIv2 Format
        recordset_data = self._view.show(context, request, recordset)
        recordset_data = utils.deep_dict_merge(recordset_data, body)
        new_recordset = self._view.load(context, request, body)

        # Validate the new set of data
        self._resource_schema.validate(recordset_data)

        # Get original list of Records
        original_records = set()
        for record in recordset.records:
            original_records.add(record.data)
        # Get new list of Records
        new_records = set()
        if 'records' in new_recordset:
            for record in new_recordset['records']:
                new_records.add(record.data)
        # Get differences of Records
        records_to_add = new_records.difference(original_records)
        records_to_rm = original_records.difference(new_records)

        # Update all items except records
        record_update = False
        if 'records' in new_recordset:
            record_update = True
            del new_recordset['records']
        recordset.update(new_recordset)

        # Remove deleted records if we have provided a records array
        if record_update:
            recordset.records[:] = [
                record for record in recordset.records
                if record.data not in records_to_rm
            ]

        # Add new records
        for record in records_to_add:
            recordset.records.append(Record(data=record))

        # Persist the resource
        recordset = self.central_api.update_recordset(context, recordset)

        if recordset['status'] == 'PENDING':
            response.status_int = 202
        else:
            response.status_int = 200

        return self._view.show(context, request, recordset)