class FloatingIPController(rest.RestController): _view = floatingips_views.FloatingIPView() _resource_schema = schema.Schema('v2', 'floatingip') _collection_schema = schema.Schema('v2', 'floatingips') @pecan.expose(template='json:', content_type='application/json') def get_all(self, **params): """List Floating IP PTRs for a Tenant""" request = pecan.request context = request.environ['context'] fips = self.central_api.list_floatingips(context) return self._view.list(context, request, fips) @pecan.expose(template='json:', content_type='application/json') def patch_one(self, fip_key): """ Set or unset a PTR """ request = pecan.request context = request.environ['context'] body = request.body_dict region, id_ = fip_key_to_data(fip_key) # Validate the request conforms to the schema self._resource_schema.validate(body) fip = self.central_api.update_floatingip( context, region, id_, objects.FloatingIP().from_dict(body['floatingip'])) if fip: return self._view.show(context, request, fip) @pecan.expose(template='json:', content_type='application/json') def get_one(self, fip_key): """ Get PTR """ request = pecan.request context = request.environ['context'] region, id_ = fip_key_to_data(fip_key) fip = self.central_api.get_floatingip(context, region, id_) return self._view.show(context, request, fip)
class QuotasController(rest.RestController): _view = quotas_view.QuotasView() _resource_schema = schema.Schema('admin', 'quota') @staticmethod def get_path(): return '.quotas' @pecan.expose(template='json:', content_type='application/json') def get_one(self, tenant_id): request = pecan.request context = pecan.request.environ['context'] context.all_tenants = True quotas = self.central_api.get_quotas(context, tenant_id) return self._view.show(context, request, quotas) @pecan.expose(template='json:', content_type='application/json') def patch_one(self, tenant_id): """Modify a Quota""" request = pecan.request response = pecan.response context = request.environ['context'] context.all_tenants = True body = request.body_dict # Validate the request conforms to the schema self._resource_schema.validate(body) values = self._view.load(context, request, body) for resource, hard_limit in values.items(): self.central_api.set_quota(context, tenant_id, resource, hard_limit) response.status_int = 200 quotas = self.central_api.get_quotas(context, tenant_id) return self._view.show(context, request, quotas) @pecan.expose(template=None, content_type='application/json') def delete_one(self, tenant_id): """Reset to the Default Quotas""" request = pecan.request response = pecan.response context = request.environ['context'] context.all_tenants = True self.central_api.reset_quotas(context, tenant_id) response.status_int = 204 return ''
class TransferAcceptsController(rest.RestController): _view = zone_transfer_accepts_view.ZoneTransferAcceptsView() _resource_schema = schema.Schema('v2', 'transfer_accept') _collection_schema = schema.Schema('v2', 'transfer_accepts') SORT_KEYS = ['created_at', 'id', 'updated_at'] @pecan.expose(template='json:', content_type='application/json') @utils.validate_uuid('transfer_accept_id') def get_one(self, transfer_accept_id): """Get transfer_accepts""" request = pecan.request context = request.environ['context'] transfer_accepts = \ self.central_api.get_zone_transfer_accept( context, transfer_accept_id) return self._view.show(context, request, transfer_accepts) @pecan.expose(template='json:', content_type='application/json') def post_all(self): """Create ZoneTransferAccept""" request = pecan.request response = pecan.response context = request.environ['context'] body = request.body_dict # Validate the request conforms to the schema self._resource_schema.validate(body) # Convert from APIv2 -> Central format values = self._view.load(context, request, body) # Create the zone_transfer_request zone_transfer_accept = self.central_api.create_zone_transfer_accept( context, ZoneTransferAccept(**values)) response.status_int = 201 response.headers['Location'] = self._view._get_resource_href( request, zone_transfer_accept) # Prepare and return the response body return self._view.show(context, request, zone_transfer_accept)
def test_recordset(self): validator = schema.Schema('v2', 'recordset') # Pass Expected validator.validate({ 'recordset': { 'id': 'b22d09e0-efa3-11e2-b778-0800200c9a66', 'zone_id': 'b22d09e0-efa3-11e2-b778-0800200c9a66', 'name': 'example.com.', 'type': 'A' } }) # Pass Expected validator.validate({ 'recordset': { 'id': 'b22d09e0-efa3-11e2-b778-0800200c9a66', 'zone_id': 'b22d09e0-efa3-11e2-b778-0800200c9a66', 'name': 'example.com.', 'type': 'MX' } })
class TldsController(rest.RestController): _view = tlds_view.TldsView() _resource_schema = schema.Schema('v2', 'tld') _collection_schema = schema.Schema('v2', 'tlds') SORT_KEYS = ['created_at', 'id', 'updated_at', 'name'] @pecan.expose(template='json:', content_type='application/json') def get_one(self, tld_id): """ Get Tld """ request = pecan.request context = request.environ['context'] tld = central_api.get_tld(context, tld_id) return self._view.show(context, request, tld) @pecan.expose(template='json:', content_type='application/json') def get_all(self, **params): """ List Tlds """ request = pecan.request context = request.environ['context'] # Extract the pagination params marker, limit, sort_key, sort_dir = self._get_paging_params(params) # Extract any filter params. accepted_filters = ('name') criterion = dict( (k, params[k]) for k in accepted_filters if k in params) tlds = central_api.find_tlds(context, criterion, marker, limit, sort_key, sort_dir) return self._view.list(context, request, tlds) @pecan.expose(template='json:', content_type='application/json') def post_all(self): """ Create Tld """ request = pecan.request response = pecan.response context = request.environ['context'] body = request.body_dict # Validate the request conforms to the schema self._resource_schema.validate(body) # Convert from APIv2 -> Central format values = self._view.load(context, request, body) # Create the tld tld = central_api.create_tld(context, values) response.status_int = 201 response.headers['Location'] = self._view._get_resource_href( request, tld) # Prepare and return the response body return self._view.show(context, request, tld) @pecan.expose(template='json:', content_type='application/json') @pecan.expose(template='json:', content_type='application/json-patch+json') def patch_one(self, tld_id): """ Update Tld """ request = pecan.request context = request.environ['context'] body = request.body_dict response = pecan.response # Fetch the existing tld tld = central_api.get_tld(context, tld_id) # Convert to APIv2 Format tld = self._view.show(context, request, tld) if request.content_type == 'application/json-patch+json': raise NotImplemented('json-patch not implemented') else: tld = utils.deep_dict_merge(tld, body) # Validate the request conforms to the schema self._resource_schema.validate(tld) values = self._view.load(context, request, body) tld = central_api.update_tld(context, tld_id, values) response.status_int = 200 return self._view.show(context, request, tld) @pecan.expose(template=None, content_type='application/json') def delete_one(self, tld_id): """ Delete Tld """ request = pecan.request response = pecan.response context = request.environ['context'] central_api.delete_tld(context, tld_id) response.status_int = 204 # NOTE: This is a hack and a half.. But Pecan needs it. return ''
# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import flask from oslo_log import log as logging from designate import schema from designate.central import rpcapi as central_rpcapi LOG = logging.getLogger(__name__) blueprint = flask.Blueprint('limits', __name__) limits_schema = schema.Schema('v1', 'limits') @blueprint.route('/schemas/limits', methods=['GET']) def get_limits_schema(): return flask.jsonify(limits_schema.raw) @blueprint.route('/limits', methods=['GET']) def get_limits(): context = flask.request.environ.get('context') central_api = central_rpcapi.CentralAPI.get_instance() absolute_limits = central_api.get_absolute_limits(context)
class ZonesController(rest.RestController): _view = zones_view.ZonesView() _resource_schema = schema.Schema('v2', 'zone') _collection_schema = schema.Schema('v2', 'zones') SORT_KEYS = [ 'created_at', 'id', 'updated_at', 'name', 'tenant_id', 'serial', 'ttl' ] recordsets = recordsets.RecordSetsController() @pecan.expose(template=None, content_type='text/dns') @pecan.expose(template='json:', content_type='application/json') 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 _get_json(self, request, context, zone_id): """ 'Normal' zone get """ zone = central_api.get_domain(context, zone_id) return self._view.show(context, request, zone) def _get_zonefile(self, request, context, zone_id): """ Export zonefile """ servers = central_api.get_domain_servers(context, zone_id) domain = central_api.get_domain(context, zone_id) criterion = {'domain_id': zone_id} recordsets = central_api.find_recordsets(context, criterion) records = [] for recordset in recordsets: criterion = { 'domain_id': domain['id'], 'recordset_id': recordset['id'] } raw_records = central_api.find_records(context, criterion) for record in raw_records: records.append({ 'name': recordset['name'], 'type': recordset['type'], 'ttl': recordset['ttl'], 'priority': record['priority'], 'data': record['data'], }) return utils.render_template('bind9-zone.jinja2', servers=servers, domain=domain, records=records) @pecan.expose(template='json:', content_type='application/json') def get_all(self, **params): """ List Zones """ request = pecan.request context = request.environ['context'] marker, limit, sort_key, sort_dir = self._get_paging_params(params) # Extract any filter params. accepted_filters = ( 'name', 'email', ) criterion = dict( (k, params[k]) for k in accepted_filters if k in params) zones = central_api.find_domains(context, criterion, marker, limit, sort_key, sort_dir) return self._view.list(context, request, zones) @pecan.expose(template='json:', content_type='application/json') def post_all(self): """ Create Zone """ request = pecan.request response = pecan.response context = request.environ['context'] if request.content_type == 'text/dns': return self._post_zonefile(request, response, context) elif request.content_type == 'application/json': return self._post_json(request, response, context) else: raise exceptions.UnsupportedContentType( 'Content-type must be text/dns or application/json') def _post_json(self, request, response, context): """ 'Normal' zone creation """ body = request.body_dict # Validate the request conforms to the schema self._resource_schema.validate(body) # Convert from APIv2 -> Central format values = self._view.load(context, request, body) # Create the zone zone = central_api.create_domain(context, values) # Prepare the response headers # If the zone has been created asynchronously if zone['status'] == 'PENDING': response.status_int = 202 else: response.status_int = 201 response.headers['Location'] = self._view._get_resource_href( request, zone) # Prepare and return the response body return self._view.show(context, request, zone) def _post_zonefile(self, request, response, context): """ Import Zone """ dnspython_zone = self._parse_zonefile(request) # TODO(artom) This should probably be handled with transactions zone = self._create_zone(context, dnspython_zone) try: self._create_records(context, zone['id'], dnspython_zone) except exceptions.Base as e: central_api.delete_domain(context, zone['id']) raise e if zone['status'] == 'PENDING': response.status_int = 202 else: response.status_int = 201 response.headers['Location'] = self._view._get_resource_href( request, zone) return self._view.show(context, request, zone) @pecan.expose(template='json:', content_type='application/json') @pecan.expose(template='json:', content_type='application/json-patch+json') 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 = central_api.get_domain(context, zone_id) # Convert to APIv2 Format zone = self._view.show(context, request, 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: zone = utils.deep_dict_merge(zone, body) # Validate the request conforms to the schema self._resource_schema.validate(zone) values = self._view.load(context, request, body) zone = central_api.update_domain(context, zone_id, values) if zone['status'] == 'PENDING': response.status_int = 202 else: response.status_int = 200 return self._view.show(context, request, zone) @pecan.expose(template=None, content_type='application/json') def delete_one(self, zone_id): """ Delete Zone """ request = pecan.request response = pecan.response context = request.environ['context'] # TODO(kiall): Validate we have a sane UUID for zone_id zone = central_api.delete_domain(context, zone_id) if zone['status'] == 'DELETING': response.status_int = 202 else: response.status_int = 204 # NOTE: This is a hack and a half.. But Pecan needs it. return '' #TODO(artom) Methods below may be useful elsewhere, consider putting them # somewhere reusable. 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 _record2json(self, record_type, rdata): if record_type == 'MX': return { 'data': rdata.exchange.to_text(), 'priority': str(rdata.preference) } elif record_type == 'SRV': return { 'data': '%s %s %s' % (str(rdata.weight), str(rdata.port), rdata.target.to_text()), 'priority': str(rdata.priority) } else: return {'data': rdata.to_text()} def _create_records(self, context, zone_id, dnspython_zone): """ Creates the records """ for record_name in dnspython_zone.nodes.keys(): for rdataset in dnspython_zone.nodes[record_name]: record_type = rdatatype.to_text(rdataset.rdtype) if record_type == 'SOA': continue # Create the recordset values = { 'domain_id': zone_id, 'name': record_name.to_text(), 'type': record_type, } recordset = central_api.create_recordset( context, zone_id, values) for rdata in rdataset: if (record_type == 'NS' and record_name == dnspython_zone.origin): # Don't create NS records for the domain, they've been # taken care of as servers pass else: # Everything else, including delegation NS, gets # created values = self._record2json(record_type, rdata) central_api.create_record(context, zone_id, recordset['id'], values) def _parse_zonefile(self, request): """ Parses a POSTed zonefile into a dnspython zone object """ try: dnspython_zone = dnszone.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 dnszone.UnknownOrigin: raise exceptions.BadRequest('The $ORIGIN statement is required and' ' must be the first statement in the' ' zonefile.') except dnsexception.SyntaxError: raise exceptions.BadRequest('Malformed zonefile.') return dnspython_zone
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import flask from oslo_config import cfg from oslo_log import log as logging from designate import exceptions from designate import schema from designate import objects from designate.central import rpcapi as central_rpcapi LOG = logging.getLogger(__name__) blueprint = flask.Blueprint('servers', __name__) server_schema = schema.Schema('v1', 'server') servers_schema = schema.Schema('v1', 'servers') default_pool_id = cfg.CONF['service:central'].default_pool_id # Servers are no longer used. They have been replaced by nameservers, which # is stored as a PoolAttribute. However, the v1 server API calls still need # to work def _pool_ns_record_to_server(pool_ns_record): server_values = { 'id': pool_ns_record.id, 'created_at': pool_ns_record.created_at, 'updated_at': pool_ns_record.updated_at, 'version': pool_ns_record.version, 'name': pool_ns_record.hostname
class TransferRequestsController(rest.RestController): _view = zone_transfer_requests_view.ZoneTransferRequestsView() _resource_schema = schema.Schema('v2', 'transfer_request') _collection_schema = schema.Schema('v2', 'transfer_requests') SORT_KEYS = ['created_at', 'id', 'updated_at'] @pecan.expose(template='json:', content_type='application/json') @utils.validate_uuid('transfer_request_id') def get_one(self, transfer_request_id): """Get transfer_request""" request = pecan.request context = request.environ['context'] transfer_request = \ self.central_api.get_zone_transfer_request( context, transfer_request_id) return self._view.show(context, request, transfer_request) @pecan.expose(template='json:', content_type='application/json') def get_all(self, **params): """List ZoneTransferRequests""" request = pecan.request context = request.environ['context'] # Extract the pagination params marker, limit, sort_key, sort_dir = self._get_paging_params(params) # Extract any filter params. criterion = self._apply_filter_params(params, ('status', ), {}) zone_transfer_requests = self.central_api.find_zone_transfer_requests( context, criterion, marker, limit, sort_key, sort_dir) return self._view.list(context, request, zone_transfer_requests) @pecan.expose(template='json:', content_type='application/json') @utils.validate_uuid('zone_id') def post_all(self, zone_id): """Create ZoneTransferRequest""" request = pecan.request response = pecan.response context = request.environ['context'] body = request.body_dict if body['transfer_request'] is not None: body['transfer_request']['zone_id'] = zone_id # Validate the request conforms to the schema self._resource_schema.validate(body) # Convert from APIv2 -> Central format values = self._view.load(context, request, body) # Create the zone_transfer_request zone_transfer_request = self.central_api.create_zone_transfer_request( context, ZoneTransferRequest(**values)) response.status_int = 201 response.headers['Location'] = self._view._get_resource_href( request, zone_transfer_request) # Prepare and return the response body return self._view.show(context, request, zone_transfer_request) @pecan.expose(template='json:', content_type='application/json') @pecan.expose(template='json:', content_type='application/json-patch+json') @utils.validate_uuid('zone_transfer_request_id') def patch_one(self, zone_transfer_request_id): """Update ZoneTransferRequest""" request = pecan.request context = request.environ['context'] body = request.body_dict response = pecan.response # Fetch the existing zone_transfer_request zt_request = self.central_api.get_zone_transfer_request( context, zone_transfer_request_id) # Convert to APIv2 Format zt_request_data = self._view.show(context, request, zt_request) if request.content_type == 'application/json-patch+json': raise NotImplemented('json-patch not implemented') else: zt_request_data = utils.deep_dict_merge(zt_request_data, body) # Validate the request conforms to the schema self._resource_schema.validate(zt_request_data) zt_request.update(self._view.load(context, request, body)) zt_request = self.central_api.update_zone_transfer_request( context, zt_request) response.status_int = 200 return self._view.show(context, request, zt_request) @pecan.expose(template=None, content_type='application/json') @utils.validate_uuid('zone_transfer_request_id') def delete_one(self, zone_transfer_request_id): """Delete ZoneTransferRequest""" request = pecan.request response = pecan.response context = request.environ['context'] self.central_api.delete_zone_transfer_request( context, zone_transfer_request_id) response.status_int = 204 # NOTE: This is a hack and a half.. But Pecan needs it. return ''
# Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import flask from designate.openstack.common import log as logging from designate import exceptions from designate import schema from designate.api import get_central_api from designate.objects import Record from designate.objects import RecordSet LOG = logging.getLogger(__name__) blueprint = flask.Blueprint('records', __name__) record_schema = schema.Schema('v1', 'record') records_schema = schema.Schema('v1', 'records') def _find_recordset(context, domain_id, name, type): return get_central_api().find_recordset(context, { 'domain_id': domain_id, 'name': name, 'type': type, }) def _find_or_create_recordset(context, domain_id, name, type, ttl): try: recordset = _find_recordset(context, domain_id, name, type) except exceptions.RecordSetNotFound:
class RecordsController(rest.RestController): _view = records_view.RecordsView() _resource_schema = schema.Schema('v2', 'record') _collection_schema = schema.Schema('v2', 'records') SORT_KEYS = [ 'created_at', 'id', 'updated_at', 'domain_id', 'tenant_id', 'recordset_id', 'status' ] @pecan.expose(template='json:', content_type='application/json') @utils.validate_uuid('zone_id', 'recordset_id', 'record_id') def get_one(self, zone_id, recordset_id, record_id): """ Get Record """ request = pecan.request context = request.environ['context'] record = self.central_api.get_record(context, zone_id, recordset_id, record_id) return self._view.show(context, request, record) @pecan.expose(template='json:', content_type='application/json') @utils.validate_uuid('zone_id', 'recordset_id') def get_all(self, zone_id, recordset_id, **params): """ List Records """ request = pecan.request context = request.environ['context'] # Extract the pagination params marker, limit, sort_key, sort_dir = self._get_paging_params(params) # Extract any filter params. accepted_filters = ('data', ) criterion = dict( (k, params[k]) for k in accepted_filters if k in params) criterion['domain_id'] = zone_id criterion['recordset_id'] = recordset_id records = self.central_api.find_records(context, criterion, marker, limit, sort_key, sort_dir) return self._view.list(context, request, records, [zone_id, recordset_id]) @pecan.expose(template='json:', content_type='application/json') @utils.validate_uuid('zone_id', 'recordset_id') def post_all(self, zone_id, recordset_id): """ Create Record """ request = pecan.request response = pecan.response context = request.environ['context'] body = request.body_dict # Validate the request conforms to the schema self._resource_schema.validate(body) # Convert from APIv2 -> Central format values = self._view.load(context, request, body) # Create the records record = self.central_api.create_record(context, zone_id, recordset_id, Record(**values)) # Prepare the response headers if record['status'] == 'PENDING': response.status_int = 202 else: response.status_int = 201 response.headers['Location'] = self._view._get_resource_href( request, record, [zone_id, recordset_id]) # Prepare and return the response body return self._view.show(context, request, record) @pecan.expose(template='json:', content_type='application/json') @pecan.expose(template='json:', content_type='application/json-patch+json') @utils.validate_uuid('zone_id', 'recordset_id', 'record_id') def patch_one(self, zone_id, recordset_id, record_id): """ Update Record """ request = pecan.request context = request.environ['context'] body = request.body_dict response = pecan.response # Fetch the existing record record = self.central_api.get_record(context, zone_id, recordset_id, record_id) # Convert to APIv2 Format record = self._view.show(context, request, record) if request.content_type == 'application/json-patch+json': raise NotImplemented('json-patch not implemented') else: record = utils.deep_dict_merge(record, body) # Validate the request conforms to the schema self._resource_schema.validate(record) values = self._view.load(context, request, body) record = self.central_api.update_record(context, zone_id, recordset_id, record_id, values) if record['status'] == 'PENDING': response.status_int = 202 else: response.status_int = 200 return self._view.show(context, request, record) @pecan.expose(template=None, content_type='application/json') @utils.validate_uuid('zone_id', 'recordset_id', 'record_id') def delete_one(self, zone_id, recordset_id, record_id): """ Delete Record """ request = pecan.request response = pecan.response context = request.environ['context'] record = self.central_api.delete_record(context, zone_id, recordset_id, record_id) if record['status'] == 'DELETING': response.status_int = 202 else: response.status_int = 204 # NOTE: This is a hack and a half.. But Pecan needs it. return ''
class PoolsController(rest.RestController): _view = pools_view.PoolsView() _resource_schema = schema.Schema('v2', 'pool') _collection_schema = schema.Schema('v2', 'pools') SORT_KEYS = ['created_at', 'id', 'updated_at', 'name'] @pecan.expose(template='json:', content_type='application/json') @utils.validate_uuid('pool_id') def get_one(self, pool_id): """Get the specific Pool""" request = pecan.request context = request.environ['context'] pool = self.central_api.get_pool(context, pool_id) return self._view.show(context, request, pool) @pecan.expose(template='json:', content_type='application/json') def get_all(self, **params): """List all Pools""" request = pecan.request context = request.environ['context'] # Extract the pagination params marker, limit, sort_key, sort_dir = self._get_paging_params(params) # Extract any filter params. accepted_filters = ('name') criterion = dict( (k, params[k]) for k in accepted_filters if k in params) pools = self.central_api.find_pools(context, criterion, marker, limit, sort_key, sort_dir) return self._view.list(context, request, pools) @pecan.expose(template='json:', content_type='application/json') def post_all(self): """Create a Pool""" request = pecan.request response = pecan.response context = request.environ['context'] body = request.body_dict # Validate the request conforms to the schema self._resource_schema.validate(body) # Convert from APIv2 -> Central format values = self._view.load(context, request, body) # Create the pool pool = self.central_api.create_pool(context, Pool(**values)) response.status_int = 201 response.headers['Location'] = self._view._get_resource_href( request, pool) # Prepare and return the response body return self._view.show(context, request, pool) @pecan.expose(template='json:', content_type='application/json') @pecan.expose(template='json:', content_type='application/json-patch+json') @utils.validate_uuid('pool_id') def patch_one(self, pool_id): """Update the specific pool""" request = pecan.request context = request.environ['context'] body = request.body_dict response = pecan.response # Fetch the existing pool pool = self.central_api.get_pool(context, pool_id) # Convert to APIv2 Format pool_data = self._view.show(context, request, pool) if request.content_type == 'application/json-patch+json': raise NotImplemented('json-patch not implemented') else: pool_data = utils.deep_dict_merge(pool_data, body) # Validate the new set of data self._resource_schema.validate(pool_data) # Update and persist the resource pool.update(self._view.load(context, request, body)) pool = self.central_api.update_pool(context, pool) response.status_int = 200 return self._view.show(context, request, pool) @pecan.expose(template=None, content_type='application/json') @utils.validate_uuid('pool_id') def delete_one(self, pool_id): """Delete the specific pool""" request = pecan.request response = pecan.response context = request.environ['context'] self.central_api.delete_pool(context, pool_id) response.status_int = 204 # NOTE: This is a hack and a half.. But Pecan needs it. return ''
def test_constructor(self): quota = schema.Schema('admin', 'quota') self.assertIsInstance(quota, schema.Schema)
def test_constructor(self): zone = schema.Schema('v1', 'domain') self.assertIsInstance(zone, schema.Schema)
def test_constructor(self): domain = schema.Schema('v1', 'domain') self.assertIsInstance(domain, schema.Schema)
def test_recordset(self): validator = schema.Schema('v2', 'recordset') # Pass Expected validator.validate({ 'recordset': { 'id': 'b22d09e0-efa3-11e2-b778-0800200c9a66', 'zone_id': 'b22d09e0-efa3-11e2-b778-0800200c9a66', 'name': 'example.com.', 'type': 'A', 'records': [ { 'address': "127.0.0.1" }, { 'address': "127.0.0.2" }, ] } }) # Pass Expected validator.validate({ 'recordset': { 'id': 'b22d09e0-efa3-11e2-b778-0800200c9a66', 'zone_id': 'b22d09e0-efa3-11e2-b778-0800200c9a66', 'name': 'example.com.', 'type': 'MX', 'records': [ { 'preference': 10, 'exchange': 'mail.example.com.' }, ] } }) with self.assertRaises(exceptions.InvalidObject): # Fail Expected - Empty Records Array validator.validate({ 'recordset': { 'id': 'b22d09e0-efa3-11e2-b778-0800200c9a66', 'zone_id': 'b22d09e0-efa3-11e2-b778-0800200c9a66', 'name': 'example.com.', 'type': 'A', 'records': [] } }) with self.assertRaises(exceptions.InvalidObject): # Fail Expected - No Records validator.validate({ 'recordset': { 'id': 'b22d09e0-efa3-11e2-b778-0800200c9a66', 'zone_id': 'b22d09e0-efa3-11e2-b778-0800200c9a66', 'name': 'example.com.', 'type': 'A' } }) with self.assertRaises(exceptions.InvalidObject): # Fail Expected - MX records in an A RRset validator.validate({ 'recordset': { 'id': 'b22d09e0-efa3-11e2-b778-0800200c9a66', 'zone_id': 'b22d09e0-efa3-11e2-b778-0800200c9a66', 'name': 'example.com.', 'type': 'A', 'records': [ { 'address': "127.0.0.1" }, { 'preference': 10, 'exchange': 'mail.example.com.' }, ] } }) with self.assertRaises(exceptions.InvalidObject): # Fail Expected - A records in an MX RRset validator.validate({ 'recordset': { 'id': 'b22d09e0-efa3-11e2-b778-0800200c9a66', 'zone_id': 'b22d09e0-efa3-11e2-b778-0800200c9a66', 'name': 'example.com.', 'type': 'MX', 'records': [ { 'preference': 10, 'exchange': 'mail.example.com.' }, { 'address': "127.0.0.1" }, ] } }) with self.assertRaises(exceptions.InvalidObject): # Fail Expected - AAAA records in an A RRset validator.validate({ 'recordset': { 'id': 'b22d09e0-efa3-11e2-b778-0800200c9a66', 'zone_id': 'b22d09e0-efa3-11e2-b778-0800200c9a66', 'name': 'example.com.', 'type': 'A', 'records': [ { 'address': "127.0.0.1" }, { 'address': "::1" }, ] } })
# Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import flask from oslo_log import log as logging from oslo_config import cfg from designate import schema from designate.central import rpcapi as central_rpcapi from designate.objects import TsigKey LOG = logging.getLogger(__name__) blueprint = flask.Blueprint('tsigkeys', __name__) tsigkey_schema = schema.Schema('v1', 'tsigkey') tsigkeys_schema = schema.Schema('v1', 'tsigkeys') default_pool_id = cfg.CONF['service:central'].default_pool_id @blueprint.route('/schemas/tsigkey', methods=['GET']) def get_tsigkey_schema(): return flask.jsonify(tsigkey_schema.raw) @blueprint.route('/schemas/tsigkeys', methods=['GET']) def get_tsigkeys_schema(): return flask.jsonify(tsigkeys_schema.raw) @blueprint.route('/tsigkeys', methods=['POST'])
class RecordSetsController(rest.RestController): _view = recordsets_view.RecordSetsView() _resource_schema = schema.Schema('v2', 'recordset') _collection_schema = schema.Schema('v2', 'recordsets') SORT_KEYS = [ 'created_at', 'id', 'updated_at', 'domain_id', 'tenant_id', 'name', 'type', 'ttl', 'records' ] @pecan.expose(template='json:', content_type='application/json') @utils.validate_uuid('zone_id', 'recordset_id') def get_one(self, zone_id, recordset_id): """Get RecordSet""" request = pecan.request context = request.environ['context'] recordset = self.central_api.get_recordset(context, zone_id, recordset_id) return self._view.show(context, request, recordset) @pecan.expose(template='json:', content_type='application/json') @utils.validate_uuid('zone_id') def get_all(self, zone_id, **params): """List RecordSets""" request = pecan.request context = request.environ['context'] # NOTE: We need to ensure the domain actually exists, otherwise we may # return deleted recordsets instead of a domain not found self.central_api.get_domain(context, zone_id) # Extract the pagination params marker, limit, sort_key, sort_dir = self._get_paging_params(params) # Extract any filter params. accepted_filters = ( 'name', 'type', 'ttl', 'data', ) criterion = dict( (k, params[k]) for k in accepted_filters if k in params) criterion['domain_id'] = zone_id # Data must be filtered separately, through the Records table recordsets_with_data = set() data = criterion.pop('data', None) # Retrieve recordsets recordsets = self.central_api.find_recordsets(context, criterion, marker, limit, sort_key, sort_dir) # 'data' filter param: only return recordsets with matching data if data: records = self.central_api.find_records(context, criterion={ 'data': data, 'domain_id': zone_id }) recordsets_with_data.update( [record.recordset_id for record in records]) recordsets = [ recordset for recordset in recordsets if recordset.id in recordsets_with_data ] return self._view.list(context, request, recordsets, [zone_id]) @pecan.expose(template='json:', content_type='application/json') @utils.validate_uuid('zone_id') def post_all(self, zone_id): """Create RecordSet""" request = pecan.request response = pecan.response context = request.environ['context'] body = request.body_dict # Validate the request conforms to the schema self._resource_schema.validate(body) # Convert from APIv2 -> Central format values = self._view.load(context, request, body) # SOA recordsets cannot be created manually if values['type'] == 'SOA': raise exceptions.BadRequest( "Creating a SOA recordset is not allowed") # Create the recordset recordset = self.central_api.create_recordset(context, zone_id, RecordSet(**values)) # Prepare the response headers if recordset['status'] == 'PENDING': response.status_int = 202 else: response.status_int = 201 response.headers['Location'] = self._view._get_resource_href( request, recordset, [zone_id]) # Prepare and return the response body return self._view.show(context, request, recordset) @pecan.expose(template='json:', content_type='application/json') @utils.validate_uuid('zone_id', 'recordset_id') 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) @pecan.expose(template='json:', content_type='application/json') @utils.validate_uuid('zone_id', 'recordset_id') 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 self._view.show(context, request, recordset)
# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import flask from designate.openstack.common import log as logging from designate import schema from designate.central import rpcapi as central_rpcapi from designate.api.v1 import load_values LOG = logging.getLogger(__name__) central_api = central_rpcapi.CentralAPI() blueprint = flask.Blueprint('domains', __name__) domain_schema = schema.Schema('v1', 'domain') domains_schema = schema.Schema('v1', 'domains') servers_schema = schema.Schema('v1', 'servers') @blueprint.route('/schemas/domain', methods=['GET']) def get_domain_schema(): return flask.jsonify(domain_schema.raw) @blueprint.route('/schemas/domains', methods=['GET']) def get_domains_schema(): return flask.jsonify(domains_schema.raw) @blueprint.route('/domains', methods=['POST'])
class ZonesController(rest.RestController): _view = zones_view.ZonesView() _resource_schema = schema.Schema('v2', 'zone') _collection_schema = schema.Schema('v2', 'zones') SORT_KEYS = [ 'created_at', 'id', 'updated_at', 'name', 'tenant_id', 'serial', 'ttl', 'status' ] recordsets = recordsets.RecordSetsController() tasks = tasks.TasksController() @pecan.expose(template='json:', content_type='application/json') @pecan.expose(template=None, content_type='text/dns') @utils.validate_uuid('zone_id') 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( ['application/json', 'text/dns']) 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 _get_json(self, request, context, zone_id): """'Normal' zone get""" zone = self.central_api.get_domain(context, zone_id) return self._view.show(context, request, zone) def _get_zonefile(self, request, context, zone_id): """Export zonefile""" servers = self.central_api.get_domain_servers(context, zone_id) domain = self.central_api.get_domain(context, zone_id) criterion = {'domain_id': zone_id} recordsets = self.central_api.find_recordsets(context, criterion) records = [] for recordset in recordsets: criterion = { 'domain_id': domain['id'], 'recordset_id': recordset['id'] } raw_records = self.central_api.find_records(context, criterion) for record in raw_records: records.append({ 'name': recordset['name'], 'type': recordset['type'], 'ttl': recordset['ttl'], 'data': record['data'], }) return utils.render_template('bind9-zone.jinja2', servers=servers, domain=domain, records=records) @pecan.expose(template='json:', content_type='application/json') def get_all(self, **params): """List Zones""" request = pecan.request context = request.environ['context'] marker, limit, sort_key, sort_dir = self._get_paging_params(params) # Extract any filter params. accepted_filters = ( 'name', 'email', 'status', ) criterion = dict( (k, params[k]) for k in accepted_filters if k in params) zones = self.central_api.find_domains(context, criterion, marker, limit, sort_key, sort_dir) return self._view.list(context, request, zones) @pecan.expose(template='json:', content_type='application/json') def post_all(self): """Create Zone""" request = pecan.request response = pecan.response context = request.environ['context'] if request.content_type == 'text/dns': return self._post_zonefile(request, response, context) elif request.content_type == 'application/json': return self._post_json(request, response, context) else: raise exceptions.UnsupportedContentType( 'Content-type must be text/dns or application/json') def _post_json(self, request, response, context): """'Normal' zone creation""" body = request.body_dict # We need to check the zone type before validating the schema since if # it's the type is SECONDARY we need to set the email to the mgmt email zone = body.get('zone') if isinstance(zone, dict): if 'type' not in zone: zone['type'] = 'PRIMARY' if zone['type'] == 'SECONDARY': mgmt_email = CONF['service:central'].managed_resource_email body['zone']['email'] = mgmt_email # Validate the request conforms to the schema self._resource_schema.validate(body) # Convert from APIv2 -> Central format values = self._view.load(context, request, body) # TODO(ekarlso): Fix this once setter or so works. masters = values.pop('masters', []) zone = objects.Domain.from_dict(values) zone.set_masters(masters) # Create the zone zone = self.central_api.create_domain(context, zone) # Prepare the response headers # If the zone has been created asynchronously if zone['status'] == 'PENDING': response.status_int = 202 else: response.status_int = 201 response.headers['Location'] = self._view._get_resource_href( request, zone) # Prepare and return the response body return self._view.show(context, request, zone) def _post_zonefile(self, request, response, context): """Import Zone""" try: dnspython_zone = dnszone.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) domain = dnsutils.from_dnspython_zone(dnspython_zone) domain.type = 'PRIMARY' for rrset in list(domain.recordsets): if rrset.type in ('NS', 'SOA'): domain.recordsets.remove(rrset) except dnszone.UnknownOrigin: raise exceptions.BadRequest('The $ORIGIN statement is required and' ' must be the first statement in the' ' zonefile.') except dnsexception.SyntaxError: raise exceptions.BadRequest('Malformed zonefile.') zone = self.central_api.create_domain(context, domain) if zone['status'] == 'PENDING': response.status_int = 202 else: response.status_int = 201 response.headers['Location'] = self._view._get_resource_href( request, zone) return self._view.show(context, request, zone) @pecan.expose(template='json:', content_type='application/json') @pecan.expose(template='json:', content_type='application/json-patch+json') @utils.validate_uuid('zone_id') 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_domain(context, zone_id) # Convert to APIv2 Format zone_data = self._view.show(context, request, 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: zone_data = utils.deep_dict_merge(zone_data, body) # Validate the new set of data self._resource_schema.validate(zone_data) # Unpack the values values = self._view.load(context, request, body) zone.set_masters(values.pop('masters', [])) # 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 zone.update(values) 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_domain( context, zone, increment_serial=increment_serial) if zone.status == 'PENDING': response.status_int = 202 else: response.status_int = 200 return self._view.show(context, request, zone) @pecan.expose(template='json:', content_type='application/json') @utils.validate_uuid('zone_id') def delete_one(self, zone_id): """Delete Zone""" request = pecan.request response = pecan.response context = request.environ['context'] zone = self.central_api.delete_domain(context, zone_id) response.status_int = 202 return self._view.show(context, request, zone)