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()
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)
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
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)
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
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()
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)
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']) }
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)
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))
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')
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)
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
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)
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
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
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)