コード例 #1
0
ファイル: floatingips.py プロジェクト: jhedden/designate
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)
コード例 #2
0
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 ''
コード例 #3
0
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)
コード例 #4
0
    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'
            }
        })
コード例 #5
0
ファイル: tlds.py プロジェクト: arjunpola/designate
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 ''
コード例 #6
0
ファイル: limits.py プロジェクト: stenstad/designate
#      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)
コード例 #7
0
ファイル: zones.py プロジェクト: arjunpola/designate
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
コード例 #8
0
# 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
コード例 #9
0
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 ''
コード例 #10
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 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:
コード例 #11
0
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 ''
コード例 #12
0
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 ''
コード例 #13
0
ファイル: __init__.py プロジェクト: bopopescu/OpenStack-Stein
    def test_constructor(self):
        quota = schema.Schema('admin', 'quota')

        self.assertIsInstance(quota, schema.Schema)
コード例 #14
0
ファイル: __init__.py プロジェクト: bopopescu/OpenStack-Ocata
    def test_constructor(self):
        zone = schema.Schema('v1', 'domain')

        self.assertIsInstance(zone, schema.Schema)
コード例 #15
0
ファイル: __init__.py プロジェクト: zacdev/designate
    def test_constructor(self):
        domain = schema.Schema('v1', 'domain')

        self.assertIsInstance(domain, schema.Schema)
コード例 #16
0
    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"
                        },
                    ]
                }
            })
コード例 #17
0
ファイル: tsigkeys.py プロジェクト: kiall/designate-py3
# 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'])
コード例 #18
0
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)
コード例 #19
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 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'])
コード例 #20
0
ファイル: __init__.py プロジェクト: jhedden/designate
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)