示例#1
0
def _multi_send(method,
                context,
                topic,
                msg,
                timeout=None,
                envelope=False,
                _msg_id=None):
    """
    Wraps the sending of messages,
    dispatches to the matchmaker and sends
    message to all relevant hosts.
    """
    conf = CONF
    LOG.debug(_("%(msg)s") % {'msg': ' '.join(map(pformat, (topic, msg)))})

    queues = _get_matchmaker().queues(topic)
    LOG.debug(_("Sending message(s) to: %s"), queues)

    # Don't stack if we have no matchmaker results
    if len(queues) == 0:
        LOG.warn(_("No matchmaker results. Not casting."))
        # While not strictly a timeout, callers know how to handle
        # this exception and a timeout isn't too big a lie.
        raise rpc_common.Timeout(_("No match from matchmaker."))

    # This supports brokerless fanout (addresses > 1)
    for queue in queues:
        (_topic, ip_addr) = queue
        _addr = "tcp://%s:%s" % (ip_addr, conf.rpc_zmq_port)

        if method.__name__ == '_cast':
            eventlet.spawn_n(method, _addr, context, _topic, msg, timeout,
                             envelope, _msg_id)
            return
        return method(_addr, context, _topic, msg, timeout, envelope)
示例#2
0
 def _error_callback(exc):
     if isinstance(exc, qpid_exceptions.Empty):
         LOG.debug(
             _('Timed out waiting for RPC response: %s') % str(exc))
         raise rpc_common.Timeout()
     else:
         LOG.exception(
             _('Failed to consume message from queue: %s') % str(exc))
示例#3
0
 def _error_callback(exc):
     if isinstance(exc, socket.timeout):
         LOG.debug(_('Timed out waiting for RPC response: %s') %
                   str(exc))
         raise rpc_common.Timeout()
     else:
         LOG.exception(_('Failed to consume message from queue: %s') %
                       str(exc))
         info['do_consume'] = True
示例#4
0
def multicall(conf, context, topic, msg, timeout=None):
    """Make a call that returns multiple times."""

    check_serialize(msg)

    method = msg.get('method')
    if not method:
        return
    args = msg.get('args', {})
    version = msg.get('version', None)
    namespace = msg.get('namespace', None)

    try:
        consumer = CONSUMERS[topic][0]
    except (KeyError, IndexError):
        raise rpc_common.Timeout("No consumers available")
    else:
        return consumer.call(context, version, method, namespace, args,
                             timeout)
示例#5
0
 def __iter__(self):
     """Return a result until we get a reply with an 'ending" flag"""
     if self._done:
         raise StopIteration
     while True:
         try:
             data = self._dataqueue.get(timeout=self._timeout)
             result = self._process_data(data)
         except queue.Empty:
             self.done()
             raise rpc_common.Timeout()
         except Exception:
             with excutils.save_and_reraise_exception():
                 self.done()
         if self._got_ending:
             self.done()
             raise StopIteration
         if isinstance(result, Exception):
             self.done()
             raise result
         yield result
示例#6
0
    def call(self, context, version, method, namespace, args, timeout):
        done = eventlet.event.Event()

        def _inner():
            ctxt = RpcContext.from_dict(context.to_dict())
            try:
                rval = self.proxy.dispatch(context, version, method, namespace,
                                           **args)
                res = []
                # Caller might have called ctxt.reply() manually
                for (reply, failure) in ctxt._response:
                    if failure:
                        six.reraise(failure[0], failure[1], failure[2])
                    res.append(reply)
                # if ending not 'sent'...we might have more data to
                # return from the function itself
                if not ctxt._done:
                    if inspect.isgenerator(rval):
                        for val in rval:
                            res.append(val)
                    else:
                        res.append(rval)
                done.send(res)
            except rpc_common.ClientException as e:
                done.send_exception(e._exc_info[1])
            except Exception as e:
                done.send_exception(e)

        thread = eventlet.greenthread.spawn(_inner)

        if timeout:
            start_time = time.time()
            while not done.ready():
                eventlet.greenthread.sleep(1)
                cur_time = time.time()
                if (cur_time - start_time) > timeout:
                    thread.kill()
                    raise rpc_common.Timeout()

        return done.wait()
示例#7
0
class ApiV2ZonesTest(ApiV2TestCase):
    __test__ = True

    def setUp(self):
        super(ApiV2ZonesTest, self).setUp()

        # Create a server
        self.create_server()

    def test_missing_accept(self):
        self.client.get('/zones/123', status=400)

    def test_bad_accept(self):
        self.client.get('/zones/123',
                        headers={'Accept': 'test/goat'},
                        status=406)

    def test_missing_content_type(self):
        self.client.post('/zones', status=415)

    def test_bad_content_type(self):
        self.client.post('/zones',
                         headers={'Content-type': 'test/goat'},
                         status=415)

    def test_create_zone(self):
        # Create a zone
        fixture = self.get_domain_fixture(0)
        response = self.client.post_json('/zones/', {'zone': fixture})

        # Check the headers are what we expect
        self.assertEqual(201, response.status_int)
        self.assertEqual('application/json', response.content_type)

        # Check the body structure is what we expect
        self.assertIn('zone', response.json)
        self.assertIn('links', response.json['zone'])
        self.assertIn('self', response.json['zone']['links'])

        # Check the values returned are what we expect
        self.assertIn('id', response.json['zone'])
        self.assertIn('created_at', response.json['zone'])
        self.assertEqual('ACTIVE', response.json['zone']['status'])
        self.assertIsNone(response.json['zone']['updated_at'])

        for k in fixture:
            self.assertEqual(fixture[k], response.json['zone'][k])

    def test_create_zone_validation(self):
        # NOTE: The schemas should be tested separatly to the API. So we
        #       don't need to test every variation via the API itself.
        # Fetch a fixture
        fixture = self.get_domain_fixture(0)

        # Add a junk field to the wrapper
        body = {'zone': fixture, 'junk': 'Junk Field'}

        # Ensure it fails with a 400
        response = self.client.post_json('/zones/', body, status=400)
        self.assertEqual(400, response.status_int)

        # Add a junk field to the body
        fixture['junk'] = 'Junk Field'

        # Ensure it fails with a 400
        body = {'zone': fixture}
        self.client.post_json('/zones/', body, status=400)

    @patch.object(central_service.Service,
                  'create_domain',
                  side_effect=rpc_common.Timeout())
    def test_create_zone_timeout(self, _):
        fixture = self.get_domain_fixture(0)

        body = {'zone': fixture}
        self.client.post_json('/zones/', body, status=504)

    @patch.object(central_service.Service,
                  'create_domain',
                  side_effect=exceptions.DuplicateDomain())
    def test_create_zone_duplicate(self, _):
        fixture = self.get_domain_fixture(0)

        body = {'zone': fixture}
        self.client.post_json('/zones/', body, status=409)

    def test_get_zones(self):
        response = self.client.get('/zones/')

        # Check the headers are what we expect
        self.assertEqual(200, response.status_int)
        self.assertEqual('application/json', response.content_type)

        # Check the body structure is what we expect
        self.assertIn('zones', response.json)
        self.assertIn('links', response.json)
        self.assertIn('self', response.json['links'])

        # We should start with 0 zones
        self.assertEqual(0, len(response.json['zones']))

        # Test with 1 zone
        self.create_domain()

        response = self.client.get('/zones/')

        self.assertIn('zones', response.json)
        self.assertEqual(1, len(response.json['zones']))

        # test with 2 zones
        self.create_domain(fixture=1)

        response = self.client.get('/zones/')

        self.assertIn('zones', response.json)
        self.assertEqual(2, len(response.json['zones']))

    @patch.object(central_service.Service,
                  'find_domains',
                  side_effect=rpc_common.Timeout())
    def test_get_zones_timeout(self, _):
        self.client.get('/zones/', status=504)

    def test_get_zone(self):
        # Create a zone
        zone = self.create_domain()

        response = self.client.get('/zones/%s' % zone['id'],
                                   headers=[('Accept', 'application/json')])

        # Check the headers are what we expect
        self.assertEqual(200, response.status_int)
        self.assertEqual('application/json', response.content_type)

        # Check the body structure is what we expect
        self.assertIn('zone', response.json)
        self.assertIn('links', response.json['zone'])
        self.assertIn('self', response.json['zone']['links'])

        # Check the values returned are what we expect
        self.assertIn('id', response.json['zone'])
        self.assertIn('created_at', response.json['zone'])
        self.assertEqual('ACTIVE', response.json['zone']['status'])
        self.assertIsNone(response.json['zone']['updated_at'])
        self.assertEqual(zone['name'], response.json['zone']['name'])
        self.assertEqual(zone['email'], response.json['zone']['email'])

    @patch.object(central_service.Service,
                  'get_domain',
                  side_effect=rpc_common.Timeout())
    def test_get_zone_timeout(self, _):
        self.client.get('/zones/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980',
                        headers={'Accept': 'application/json'},
                        status=504)

    @patch.object(central_service.Service,
                  'get_domain',
                  side_effect=exceptions.DomainNotFound())
    def test_get_zone_missing(self, _):
        self.client.get('/zones/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980',
                        headers={'Accept': 'application/json'},
                        status=404)

    def test_get_zone_invalid_id(self):
        self.skip('We don\'t guard against this in APIv2 yet')

        # The letter "G" is not valid in a UUID
        self.client.get('/zones/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff9GG',
                        headers={'Accept': 'application/json'},
                        status=404)

        # Badly formed UUID
        self.client.get('/zones/2fdadfb1cf964259ac6bbb7b6d2ff9GG',
                        headers={'Accept': 'application/json'},
                        status=404)

        # Integer
        self.client.get('/zones/12345',
                        headers={'Accept': 'application/json'},
                        status=404)

    def test_update_zone(self):
        # Create a zone
        zone = self.create_domain()

        # Prepare an update body
        body = {'zone': {'email': 'prefix-%s' % zone['email']}}

        response = self.client.patch_json('/zones/%s' % zone['id'],
                                          body,
                                          status=200)

        # Check the headers are what we expect
        self.assertEqual(200, response.status_int)
        self.assertEqual('application/json', response.content_type)

        # Check the body structure is what we expect
        self.assertIn('zone', response.json)
        self.assertIn('links', response.json['zone'])
        self.assertIn('self', response.json['zone']['links'])
        self.assertIn('status', response.json['zone'])

        # Check the values returned are what we expect
        self.assertIn('id', response.json['zone'])
        self.assertIsNotNone(response.json['zone']['updated_at'])
        self.assertEqual('prefix-%s' % zone['email'],
                         response.json['zone']['email'])

    def test_update_zone_validation(self):
        # NOTE: The schemas should be tested separatly to the API. So we
        #       don't need to test every variation via the API itself.
        # Create a zone
        zone = self.create_domain()

        # Prepare an update body with junk in the wrapper
        body = {
            'zone': {
                'email': 'prefix-%s' % zone['email']
            },
            'junk': 'Junk Field'
        }

        # Ensure it fails with a 400
        self.client.patch_json('/zones/%s' % zone['id'], body, status=400)

        # Prepare an update body with junk in the body
        body = {
            'zone': {
                'email': 'prefix-%s' % zone['email'],
                'junk': 'Junk Field'
            }
        }

        # Ensure it fails with a 400
        self.client.patch_json('/zones/%s' % zone['id'], body, status=400)

    @patch.object(central_service.Service,
                  'get_domain',
                  side_effect=exceptions.DuplicateDomain())
    def test_update_zone_duplicate(self, _):
        # Prepare an update body
        body = {'zone': {'email': '*****@*****.**'}}

        # Ensure it fails with a 409
        self.client.patch_json('/zones/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980',
                               body,
                               status=409)

    @patch.object(central_service.Service,
                  'get_domain',
                  side_effect=rpc_common.Timeout())
    def test_update_zone_timeout(self, _):
        # Prepare an update body
        body = {'zone': {'email': '*****@*****.**'}}

        # Ensure it fails with a 504
        self.client.patch_json('/zones/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980',
                               body,
                               status=504)

    @patch.object(central_service.Service,
                  'get_domain',
                  side_effect=exceptions.DomainNotFound())
    def test_update_zone_missing(self, _):
        # Prepare an update body
        body = {'zone': {'email': '*****@*****.**'}}

        # Ensure it fails with a 404
        self.client.patch_json('/zones/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980',
                               body,
                               status=404)

    def test_update_zone_invalid_id(self):
        self.skip('We don\'t guard against this in APIv2 yet')

        # Prepare an update body
        body = {'zone': {'email': '*****@*****.**'}}

        # The letter "G" is not valid in a UUID
        self.client.patch_json('/zones/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff9GG',
                               body,
                               status=404)

        # Badly formed UUID
        self.client.patch_json('/zones/2fdadfb1cf964259ac6bbb7b6d2ff980',
                               body,
                               status=404)

        # Integer
        self.client.patch_json('/zones/12345', body, status=404)

    def test_delete_zone(self):
        zone = self.create_domain()

        self.client.delete('/zones/%s' % zone['id'], status=204)

    @patch.object(central_service.Service,
                  'delete_domain',
                  side_effect=rpc_common.Timeout())
    def test_delete_zone_timeout(self, _):
        self.client.delete('/zones/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980',
                           status=504)

    @patch.object(central_service.Service,
                  'delete_domain',
                  side_effect=exceptions.DomainNotFound())
    def test_delete_zone_missing(self, _):
        self.client.delete('/zones/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980',
                           status=404)

    def test_delete_zone_invalid_id(self):
        self.skip('We don\'t guard against this in APIv2 yet')

        # The letter "G" is not valid in a UUID
        self.client.delete('/zones/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff9GG',
                           status=404)

        # Badly formed UUID
        self.client.delete('/zones/2fdadfb1cf964259ac6bbb7b6d2ff980',
                           status=404)

        # Integer
        self.client.delete('/zones/12345', status=404)

    # Zone import/export
    def test_missing_origin(self):
        self.client.post('/zones',
                         self.get_zonefile_fixture(variant='noorigin'),
                         headers={'Content-type': 'text/dns'},
                         status=400)

    def test_missing_soa(self):
        self.client.post('/zones',
                         self.get_zonefile_fixture(variant='nosoa'),
                         headers={'Content-type': 'text/dns'},
                         status=400)

    def test_malformed_zonefile(self):
        self.client.post('/zones',
                         self.get_zonefile_fixture(variant='malformed'),
                         headers={'Content-type': 'text/dns'},
                         status=400)

    def test_import_export(self):
        # Since v2 doesn't support getting records, import and export the
        # fixture, making sure they're the same according to dnspython
        post_response = self.client.post('/zones',
                                         self.get_zonefile_fixture(),
                                         headers={'Content-type': 'text/dns'})
        get_response = self.client.get('/zones/%s' %
                                       post_response.json['zone']['id'],
                                       headers={'Accept': 'text/dns'})
        exported_zonefile = get_response.body
        imported = dnszone.from_text(self.get_zonefile_fixture())
        exported = dnszone.from_text(exported_zonefile)
        # Compare SOA emails, since zone comparison takes care of origin
        imported_soa = imported.get_rdataset(imported.origin, 'SOA')
        imported_email = imported_soa[0].rname.to_text()
        exported_soa = exported.get_rdataset(exported.origin, 'SOA')
        exported_email = exported_soa[0].rname.to_text()
        self.assertEqual(imported_email, exported_email)
        # Delete SOAs since they have, at the very least, different serials,
        # and dnspython considers that to be not equal.
        imported.delete_rdataset(imported.origin, 'SOA')
        exported.delete_rdataset(exported.origin, 'SOA')
        # Delete non-delegation NS, since they won't be the same
        imported.delete_rdataset(imported.origin, 'NS')
        exported.delete_rdataset(exported.origin, 'NS')
        self.assertEqual(imported, exported)
示例#8
0
class ApiV1RecordsTest(ApiV1Test):
    __test__ = True

    def setUp(self):
        super(ApiV1RecordsTest, self).setUp()

        self.domain = self.create_domain()

    def test_create_record(self):
        fixture = self.get_record_fixture(self.domain['name'], 0)

        # Create a record
        response = self.post('domains/%s/records' % self.domain['id'],
                             data=fixture)

        self.assertIn('id', response.json)
        self.assertIn('name', response.json)
        self.assertEqual(response.json['name'], fixture['name'])

    @patch.object(central_service.Service, 'create_record')
    def test_create_record_trailing_slash(self, mock):
        # Create a record with a trailing slash
        self.post('domains/%s/records/' % self.domain['id'],
                  data=self.get_record_fixture(self.domain['name'], 0))

        # verify that the central service is called
        self.assertTrue(mock.called)

    def test_create_record_junk(self):
        fixture = self.get_record_fixture(self.domain['name'], 0)

        # Add a junk property
        fixture['junk'] = 'Junk Field'

        # Create a record, Ensuring it fails with a 400
        self.post('domains/%s/records' % self.domain['id'],
                  data=fixture,
                  status_code=400)

    def test_create_record_utf_description(self):
        fixture = self.get_record_fixture(self.domain['name'], 0)

        #Add a UTF-8 riddled description
        fixture['description'] = "utf-8:2H₂+O₂⇌2H₂O,R=4.7kΩ,⌀200mm∮E⋅da=Q,n" \
                                 ",∑f(i)=∏g(i),∀x∈ℝ:⌈x⌉"

        # Create a record, Ensuring it succeeds
        self.post('domains/%s/records' % self.domain['id'], data=fixture)

    def test_create_record_description_too_long(self):
        fixture = self.get_record_fixture(self.domain['name'], 0)

        #Add a description that is too long
        fixture['description'] = "x" * 161

        # Create a record, Ensuring it Fails with a 400
        self.post('domains/%s/records' % self.domain['id'],
                  data=fixture,
                  status_code=400)

    def test_create_record_negative_ttl(self):
        # Create a record
        fixture = self.get_record_fixture(self.domain['name'], 0)
        fixture['ttl'] = -1

        # Create a record, Ensuring it Fails with a 400
        self.post('domains/%s/records' % self.domain['id'],
                  data=fixture,
                  status_code=400)

    @patch.object(central_service.Service,
                  'create_record',
                  side_effect=rpc_common.Timeout())
    def test_create_record_timeout(self, _):
        fixture = self.get_record_fixture(self.domain['name'], 0)

        # Create a record
        self.post('domains/%s/records' % self.domain['id'],
                  data=fixture,
                  status_code=504)

    def test_create_wildcard_record(self):
        # Prepare a record
        fixture = self.get_record_fixture(self.domain['name'], 0)
        fixture['name'] = '*.%s' % fixture['name']

        # Create a record
        response = self.post('domains/%s/records' % self.domain['id'],
                             data=fixture)

        self.assertIn('id', response.json)
        self.assertIn('name', response.json)
        self.assertEqual(response.json['name'], fixture['name'])

    def test_create_srv_record(self):
        # Prepare a record
        fixture = self.get_record_fixture(self.domain['name'], 0)
        fixture['type'] = 'SRV'
        fixture['name'] = '_sip._udp.%s' % fixture['name']
        fixture['priority'] = 10
        fixture['data'] = '0 5060 sip.%s' % self.domain['name']

        # Create a record
        response = self.post('domains/%s/records' % self.domain['id'],
                             data=fixture)

        self.assertIn('id', response.json)
        self.assertEqual(response.json['type'], fixture['type'])
        self.assertEqual(response.json['name'], fixture['name'])
        self.assertEqual(response.json['priority'], fixture['priority'])
        self.assertEqual(response.json['data'], fixture['data'])

    def test_create_invalid_data_srv_record(self):
        # Prepare a record
        fixture = self.get_record_fixture(self.domain['name'], 0)
        fixture['type'] = 'SRV'
        fixture['name'] = '_sip._udp.%s' % fixture['name']
        fixture['priority'] = 10

        invalid_datas = [
            'I 5060 sip.%s' % self.domain['name'],
            '5060 sip.%s' % self.domain['name'],
            '5060 I sip.%s' % self.domain['name'],
            '0 5060 sip',
            'sip',
            'sip.%s' % self.domain['name'],
        ]

        for invalid_data in invalid_datas:
            fixture['data'] = invalid_data
            # Attempt to create the record
            self.post('domains/%s/records' % self.domain['id'],
                      data=fixture,
                      status_code=400)

    def test_create_invalid_name_srv_record(self):
        # Prepare a record
        fixture = self.get_record_fixture(self.domain['name'], 0)
        fixture['type'] = 'SRV'
        fixture['priority'] = 10
        fixture['data'] = '0 5060 sip.%s' % self.domain['name']

        invalid_names = [
            '%s' % fixture['name'],
            '_udp.%s' % fixture['name'],
            'sip._udp.%s' % fixture['name'],
            '_sip.udp.%s' % fixture['name'],
        ]

        for invalid_name in invalid_names:
            fixture['name'] = invalid_name

            # Attempt to create the record
            self.post('domains/%s/records' % self.domain['id'],
                      data=fixture,
                      status_code=400)

    def test_create_invalid_name(self):
        # Prepare a record
        fixture = self.get_record_fixture(self.domain['name'], 0)

        invalid_names = [
            'org',
            'example.org',
            '$$.example.org',
            '*example.org.',
            '*.*.example.org.',
            'abc.*.example.org.',
        ]

        for invalid_name in invalid_names:
            fixture['name'] = invalid_name

            # Create a record
            response = self.post('domains/%s/records' % self.domain['id'],
                                 data=fixture,
                                 status_code=400)

            self.assertNotIn('id', response.json)

    def test_get_records(self):
        response = self.get('domains/%s/records' % self.domain['id'])

        self.assertIn('records', response.json)
        self.assertEqual(0, len(response.json['records']))

        # Create a record
        self.create_record(self.domain)

        response = self.get('domains/%s/records' % self.domain['id'])

        self.assertIn('records', response.json)
        self.assertEqual(1, len(response.json['records']))

        # Create a second record
        self.create_record(self.domain, fixture=1)

        response = self.get('domains/%s/records' % self.domain['id'])

        self.assertIn('records', response.json)
        self.assertEqual(2, len(response.json['records']))

    @patch.object(central_service.Service, 'find_records')
    def test_get_records_trailing_slash(self, mock):
        self.get('domains/%s/records/' % self.domain['id'])

        # verify that the central service is called
        self.assertTrue(mock.called)

    @patch.object(central_service.Service,
                  'find_records',
                  side_effect=rpc_common.Timeout())
    def test_get_records_timeout(self, _):
        self.get('domains/%s/records' % self.domain['id'], status_code=504)

    def test_get_records_missing_domain(self):
        self.get('domains/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980/records',
                 status_code=404)

    def test_get_records_invalid_domain_id(self):
        self.get('domains/2fdadfb1cf964259ac6bbb7b6d2ff980/records',
                 status_code=404)

    def test_get_record(self):
        # Create a record
        record = self.create_record(self.domain)

        response = self.get('domains/%s/records/%s' %
                            (self.domain['id'], record['id']))

        self.assertIn('id', response.json)
        self.assertEqual(response.json['id'], record['id'])

    @patch.object(central_service.Service, 'get_record')
    def test_get_record_trailing_slash(self, mock):
        # Create a record
        record = self.create_record(self.domain)

        self.get('domains/%s/records/%s/' % (self.domain['id'], record['id']))

        # verify that the central service is called
        self.assertTrue(mock.called)

    def test_update_record(self):
        # Create a record
        record = self.create_record(self.domain)

        data = {'name': 'prefix-%s' % record['name']}

        response = self.put('domains/%s/records/%s' %
                            (self.domain['id'], record['id']),
                            data=data)

        self.assertIn('id', response.json)
        self.assertEqual(response.json['id'], record['id'])

        self.assertIn('name', response.json)
        self.assertEqual(response.json['name'], 'prefix-%s' % record['name'])

    @patch.object(central_service.Service, 'update_record')
    def test_update_record_trailing_slash(self, mock):
        # Create a record
        record = self.create_record(self.domain)

        data = {'name': 'prefix-%s' % record['name']}

        self.put('domains/%s/records/%s/' % (self.domain['id'], record['id']),
                 data=data)

        # verify that the central service is called
        self.assertTrue(mock.called)

    def test_update_record_junk(self):
        # Create a record
        record = self.create_record(self.domain)

        data = {'name': 'prefix-%s' % record['name'], 'junk': 'Junk Field'}

        self.put('domains/%s/records/%s' % (self.domain['id'], record['id']),
                 data=data,
                 status_code=400)

    def test_update_record_outside_domain_fail(self):
        # Create a record
        record = self.create_record(self.domain)

        data = {'name': 'test.someotherdomain.com'}

        self.put('domains/%s/records/%s' % (self.domain['id'], record['id']),
                 data=data,
                 status_code=400)

    @patch.object(central_service.Service,
                  'update_record',
                  side_effect=rpc_common.Timeout())
    def test_update_record_timeout(self, _):
        # Create a record
        record = self.create_record(self.domain)

        data = {'name': 'test.example.org.'}

        self.put('domains/%s/records/%s' % (self.domain['id'], record['id']),
                 data=data,
                 status_code=504)

    def test_update_record_missing(self):
        data = {'name': 'test.example.org.'}

        self.put('domains/%s/records/2fdadfb1-cf96-4259-ac6b-'
                 'bb7b6d2ff980' % self.domain['id'],
                 data=data,
                 status_code=404)

    def test_update_record_invalid_id(self):
        data = {'name': 'test.example.org.'}

        self.put('domains/%s/records/2fdadfb1cf964259ac6bbb7b6d2ff980' %
                 self.domain['id'],
                 data=data,
                 status_code=404)

    def test_update_record_missing_domain(self):
        data = {'name': 'test.example.org.'}

        self.put(
            'domains/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980/records/'
            '2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980',
            data=data,
            status_code=404)

    def test_update_record_invalid_domain_id(self):
        data = {'name': 'test.example.org.'}

        self.put(
            'domains/2fdadfb1cf964259ac6bbb7b6d2ff980/records/'
            '2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980',
            data=data,
            status_code=404)

    def test_delete_record(self):
        # Create a record
        record = self.create_record(self.domain)

        self.delete('domains/%s/records/%s' %
                    (self.domain['id'], record['id']))

        # Esnure we can no longer fetch the record
        self.get('domains/%s/records/%s' % (self.domain['id'], record['id']),
                 status_code=404)

    @patch.object(central_service.Service, 'delete_record')
    def test_delete_record_trailing_slash(self, mock):
        # Create a record
        record = self.create_record(self.domain)

        self.delete('domains/%s/records/%s/' %
                    (self.domain['id'], record['id']))

        # verify that the central service is called
        self.assertTrue(mock.called)

    @patch.object(central_service.Service,
                  'delete_record',
                  side_effect=rpc_common.Timeout())
    def test_delete_record_timeout(self, _):
        # Create a record
        record = self.create_record(self.domain)

        self.delete('domains/%s/records/%s' %
                    (self.domain['id'], record['id']),
                    status_code=504)

    def test_delete_record_missing(self):
        self.delete('domains/%s/records/2fdadfb1-cf96-4259-ac6b-'
                    'bb7b6d2ff980' % self.domain['id'],
                    status_code=404)

    def test_delete_record_missing_domain(self):
        self.delete(
            'domains/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980/records/'
            '2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980',
            status_code=404)

    def test_delete_record_invalid_domain_id(self):
        self.delete(
            'domains/2fdadfb1cf964259ac6bbb7b6d2ff980/records/'
            '2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980',
            status_code=404)
示例#9
0
class ApiV1ServersTest(ApiV1Test):
    __test__ = True

    def test_create_server(self):
        # Create a server
        fixture = self.get_server_fixture(0)

        response = self.post('servers', data=fixture)

        self.assertIn('id', response.json)
        self.assertIn('name', response.json)
        self.assertEqual(response.json['name'], fixture['name'])

    @patch.object(central_service.Service, 'create_server')
    def test_create_server_trailing_slash(self, mock):
        # Create a server with a trailing slash
        self.post('servers/', data=self.get_server_fixture(0))

        # verify that the central service is called
        self.assertTrue(mock.called)

    def test_create_server_junk(self):
        # Create a server
        fixture = self.get_server_fixture(0)

        # Add a junk property
        fixture['junk'] = 'Junk Field'

        # Ensure it fails with a 400
        self.post('servers', data=fixture, status_code=400)

    @patch.object(central_service.Service,
                  'create_server',
                  side_effect=rpc_common.Timeout())
    def test_create_server_timeout(self, _):
        # Create a server
        fixture = self.get_server_fixture(0)

        self.post('servers', data=fixture, status_code=504)

    @patch.object(central_service.Service,
                  'create_server',
                  side_effect=exceptions.DuplicateServer())
    def test_create_server_duplicate(self, _):
        # Create a server
        fixture = self.get_server_fixture(0)

        self.post('servers', data=fixture, status_code=409)

    def test_get_servers(self):
        response = self.get('servers')

        self.assertIn('servers', response.json)
        self.assertEqual(0, len(response.json['servers']))

        # Create a server
        self.create_server()

        response = self.get('servers')

        self.assertIn('servers', response.json)
        self.assertEqual(1, len(response.json['servers']))

        # Create a second server
        self.create_server(fixture=1)

        response = self.get('servers')

        self.assertIn('servers', response.json)
        self.assertEqual(2, len(response.json['servers']))

    @patch.object(central_service.Service, 'find_servers')
    def test_get_servers_trailing_slash(self, mock):
        self.get('servers/')

        # verify that the central service is called
        self.assertTrue(mock.called)

    @patch.object(central_service.Service,
                  'find_servers',
                  side_effect=rpc_common.Timeout())
    def test_get_servers_timeout(self, _):
        self.get('servers', status_code=504)

    def test_get_server(self):
        # Create a server
        server = self.create_server()

        response = self.get('servers/%s' % server['id'])

        self.assertIn('id', response.json)
        self.assertEqual(response.json['id'], server['id'])

    @patch.object(central_service.Service, 'get_server')
    def test_get_server_trailing_slash(self, mock):
        # Create a server
        server = self.create_server()

        self.get('servers/%s/' % server['id'])

        # verify that the central service is called
        self.assertTrue(mock.called)

    @patch.object(central_service.Service,
                  'get_server',
                  side_effect=rpc_common.Timeout())
    def test_get_server_timeout(self, _):
        # Create a server
        server = self.create_server()

        self.get('servers/%s' % server['id'], status_code=504)

    def test_get_server_missing(self):
        self.get('servers/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980',
                 status_code=404)

    def test_update_server(self):
        # Create a server
        server = self.create_server()

        data = {'name': 'test.example.org.'}

        response = self.put('servers/%s' % server['id'], data=data)

        self.assertIn('id', response.json)
        self.assertEqual(response.json['id'], server['id'])

        self.assertIn('name', response.json)
        self.assertEqual(response.json['name'], 'test.example.org.')

    @patch.object(central_service.Service, 'update_server')
    def test_update_server_trailing_slash(self, mock):
        # Create a server
        server = self.create_server()

        data = {'name': 'test.example.org.'}

        self.put('servers/%s/' % server['id'], data=data)

        # verify that the central service is called
        self.assertTrue(mock.called)

    def test_update_server_junk(self):
        # Create a server
        server = self.create_server()

        data = {'name': 'test.example.org.', 'junk': 'Junk Field'}

        self.put('servers/%s' % server['id'], data=data, status_code=400)

    @patch.object(central_service.Service,
                  'update_server',
                  side_effect=rpc_common.Timeout())
    def test_update_server_timeout(self, _):
        # Create a server
        server = self.create_server()

        data = {'name': 'test.example.org.'}

        self.put('servers/%s' % server['id'], data=data, status_code=504)

    @patch.object(central_service.Service,
                  'update_server',
                  side_effect=exceptions.DuplicateServer())
    def test_update_server_duplicate(self, _):
        server = self.create_server()

        data = {'name': 'test.example.org.'}

        self.put('servers/%s' % server['id'], data=data, status_code=409)

    def test_update_server_missing(self):
        data = {'name': 'test.example.org.'}

        self.get('servers/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980',
                 data=data,
                 status_code=404)

    def test_delete_server(self):
        # Create a server
        server = self.create_server()

        # Create a second server so that we can delete the first
        # because the last remaining server is not allowed to be deleted
        server2 = self.create_server(fixture=1)

        # Now delete the server
        self.delete('servers/%s' % server['id'])

        # Ensure we can no longer fetch the deleted server
        self.get('servers/%s' % server['id'], status_code=404)

        # Also, verify we cannot delete last remaining server
        self.delete('servers/%s' % server2['id'], status_code=400)

    @patch.object(central_service.Service, 'delete_server')
    def test_delete_server_trailing_slash(self, mock):
        # Create a server
        server = self.create_server()

        self.delete('servers/%s/' % server['id'])

        # verify that the central service is called
        self.assertTrue(mock.called)

    @patch.object(central_service.Service,
                  'delete_server',
                  side_effect=rpc_common.Timeout())
    def test_delete_server_timeout(self, _):
        # Create a server
        server = self.create_server()

        self.delete('servers/%s' % server['id'], status_code=504)

    def test_delete_server_missing(self):
        self.delete('servers/9fdadfb1-cf96-4259-ac6b-bb7b6d2ff980',
                    status_code=404)
示例#10
0
class ApiV1DomainsTest(ApiV1Test):
    __test__ = True

    def test_create_domain(self):
        # Create a server
        self.create_server()

        # Create a domain
        fixture = self.get_domain_fixture(0)

        response = self.post('domains', data=fixture)

        self.assertIn('id', response.json)
        self.assertIn('name', response.json)
        self.assertEqual(response.json['name'], fixture['name'])

    @patch.object(central_service.Service, 'create_domain')
    def test_create_domain_trailing_slash(self, mock):
        # Create a server
        self.create_server()
        self.post('domains/', data=self.get_domain_fixture(0))

        # verify that the central service is called
        self.assertTrue(mock.called)

    def test_create_domain_junk(self):
        # Create a server
        self.create_server()

        # Create a domain
        fixture = self.get_domain_fixture(0)

        # Add a junk property
        fixture['junk'] = 'Junk Field'

        # Ensure it fails with a 400
        self.post('domains', data=fixture, status_code=400)

    def test_create_domain_no_servers(self):
        # Create a domain
        fixture = self.get_domain_fixture(0)

        self.post('domains', data=fixture, status_code=500)

    @patch.object(central_service.Service, 'create_domain',
                  side_effect=rpc_common.Timeout())
    def test_create_domain_timeout(self, _):
        # Create a domain
        fixture = self.get_domain_fixture(0)

        self.post('domains', data=fixture, status_code=504)

    @patch.object(central_service.Service, 'create_domain',
                  side_effect=exceptions.DuplicateDomain())
    def test_create_domain_duplicate(self, _):
        # Create a domain
        fixture = self.get_domain_fixture(0)
        self.post('domains', data=fixture, status_code=409)

    def test_create_domain_null_ttl(self):
        # Create a domain
        fixture = self.get_domain_fixture(0)
        fixture['ttl'] = None
        self.post('domains', data=fixture, status_code=400)

    def test_create_domain_negative_ttl(self):
        # Create a domain
        fixture = self.get_domain_fixture(0)
        fixture['ttl'] = -1
        self.post('domains', data=fixture, status_code=400)

    def test_create_domain_utf_description(self):
        # Create a server
        self.create_server()

        # Create a domain
        fixture = self.get_domain_fixture(0)

        #Give it a UTF-8 filled description
        fixture['description'] = "utf-8:2H₂+O₂⇌2H₂O,R=4.7kΩ,⌀200mm∮E⋅da=Q,n" \
                                 ",∑f(i)=∏g(i),∀x∈ℝ:⌈x⌉"
        #Create the domain, ensuring it suceeds, thus UTF-8 is supported
        self.post('domains', data=fixture)

    def test_create_domain_description_too_long(self):
        # Create a server
        self.create_server()

        # Create a domain
        fixture = self.get_domain_fixture(0)
        fixture['description'] = "x" * 161

        #Create the domain, ensuring it fails with a 400
        self.post('domains', data=fixture, status_code=400)

    def test_create_invalid_name(self):
        # Prepare a domain
        fixture = self.get_domain_fixture(0)

        invalid_names = [
            'org',
            'example.org',
            'example.321',
        ]

        for invalid_name in invalid_names:
            fixture['name'] = invalid_name

            # Create a record
            response = self.post('domains', data=fixture, status_code=400)

            self.assertNotIn('id', response.json)

    def test_create_invalid_email(self):
        # Prepare a domain
        fixture = self.get_domain_fixture(0)

        invalid_emails = [
            'org',
            'example.org',
            'bla.example.org',
            'org.',
            'example.org.',
            'bla.example.org.',
            'bla.example.org.',
        ]

        for invalid_email in invalid_emails:
            fixture['email'] = invalid_email

            # Create a record
            response = self.post('domains', data=fixture, status_code=400)

            self.assertNotIn('id', response.json)

    def test_get_domains(self):
        response = self.get('domains')

        self.assertIn('domains', response.json)
        self.assertEqual(0, len(response.json['domains']))

        # Create a domain
        self.create_domain()

        response = self.get('domains')

        self.assertIn('domains', response.json)
        self.assertEqual(1, len(response.json['domains']))

        # Create a second domain
        self.create_domain(fixture=1)

        response = self.get('domains')

        self.assertIn('domains', response.json)
        self.assertEqual(2, len(response.json['domains']))

    @patch.object(central_service.Service, 'find_domains')
    def test_get_domains_trailing_slash(self, mock):
        self.get('domains/')

        # verify that the central service is called
        self.assertTrue(mock.called)

    @patch.object(central_service.Service, 'find_domains',
                  side_effect=rpc_common.Timeout())
    def test_get_domains_timeout(self, _):
        self.get('domains', status_code=504)

    def test_get_domain(self):
        # Create a domain
        domain = self.create_domain()

        response = self.get('domains/%s' % domain['id'])

        self.assertIn('id', response.json)
        self.assertEqual(response.json['id'], domain['id'])

    @patch.object(central_service.Service, 'get_domain')
    def test_get_domain_trailing_slash(self, mock):
        # Create a domain
        domain = self.create_domain()

        self.get('domains/%s/' % domain['id'])

        # verify that the central service is called
        self.assertTrue(mock.called)

    @patch.object(central_service.Service, 'get_domain',
                  side_effect=rpc_common.Timeout())
    def test_get_domain_timeout(self, _):
        # Create a domain
        domain = self.create_domain()

        self.get('domains/%s' % domain['id'], status_code=504)

    def test_get_domain_missing(self):
        self.get('domains/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980',
                 status_code=404)

    def test_get_domain_invalid_id(self):
        # The letter "G" is not valid in a UUID
        self.get('domains/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff9GG',
                 status_code=404)

        self.get('domains/2fdadfb1cf964259ac6bbb7b6d2ff980', status_code=404)

    def test_update_domain(self):
        # Create a domain
        domain = self.create_domain()

        data = {'email': 'prefix-%s' % domain['email']}

        response = self.put('domains/%s' % domain['id'], data=data)

        self.assertIn('id', response.json)
        self.assertEqual(response.json['id'], domain['id'])

        self.assertIn('email', response.json)
        self.assertEqual(response.json['email'], 'prefix-%s' % domain['email'])

    @patch.object(central_service.Service, 'update_domain')
    def test_update_domain_trailing_slash(self, mock):
        # Create a domain
        domain = self.create_domain()

        data = {'email': 'prefix-%s' % domain['email']}

        self.put('domains/%s/' % domain['id'], data=data)

        # verify that the central service is called
        self.assertTrue(mock.called)

    def test_update_domain_junk(self):
        # Create a domain
        domain = self.create_domain()

        data = {'email': 'prefix-%s' % domain['email'], 'junk': 'Junk Field'}

        self.put('domains/%s' % domain['id'], data=data, status_code=400)

    def test_update_domain_name_fail(self):
        # Create a domain
        domain = self.create_domain()

        data = {'name': 'renamed.com.'}

        self.put('domains/%s' % domain['id'], data=data, status_code=400)

    def test_update_domain_null_ttl(self):
        # Create a domain
        domain = self.create_domain()

        data = {'ttl': None}

        self.put('domains/%s' % domain['id'], data=data, status_code=400)

    @patch.object(central_service.Service, 'update_domain',
                  side_effect=rpc_common.Timeout())
    def test_update_domain_timeout(self, _):
        # Create a domain
        domain = self.create_domain()

        data = {'email': 'prefix-%s' % domain['email']}

        self.put('domains/%s' % domain['id'], data=data, status_code=504)

    @patch.object(central_service.Service, 'update_domain',
                  side_effect=exceptions.DuplicateDomain())
    def test_update_domain_duplicate(self, _):
        # Create a domain
        domain = self.create_domain()

        data = {'email': 'prefix-%s' % domain['email']}

        self.put('domains/%s' % domain['id'], data=data, status_code=409)

    def test_update_domain_missing(self):
        data = {'email': '*****@*****.**'}

        self.put('domains/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980', data=data,
                 status_code=404)

    def test_update_domain_invalid_id(self):
        data = {'email': '*****@*****.**'}

        # The letter "G" is not valid in a UUID
        self.put('domains/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff9GG', data=data,
                 status_code=404)

        self.put('domains/2fdadfb1cf964259ac6bbb7b6d2ff980', data=data,
                 status_code=404)

    def test_delete_domain(self):
        # Create a domain
        domain = self.create_domain()

        self.delete('domains/%s' % domain['id'])

        # Esnure we can no longer fetch the domain
        self.get('domains/%s' % domain['id'], status_code=404)

    @patch.object(central_service.Service, 'delete_domain')
    def test_delete_domain_trailing_slash(self, mock):
        # Create a domain
        domain = self.create_domain()

        self.delete('domains/%s/' % domain['id'])

        # verify that the central service is called
        self.assertTrue(mock.called)

    @patch.object(central_service.Service, 'delete_domain',
                  side_effect=rpc_common.Timeout())
    def test_delete_domain_timeout(self, _):
        # Create a domain
        domain = self.create_domain()

        self.delete('domains/%s' % domain['id'], status_code=504)

    def test_delete_domain_missing(self):
        self.delete('domains/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980',
                    status_code=404)

    def test_delete_domain_invalid_id(self):
        # The letter "G" is not valid in a UUID
        self.delete('domains/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff9GG',
                    status_code=404)

        self.delete('domains/2fdadfb1cf964259ac6bbb7b6d2ff980',
                    status_code=404)
示例#11
0
class ApiV2RecordSetsTest(ApiV2TestCase):
    def setUp(self):
        super(ApiV2RecordSetsTest, self).setUp()

        # Create a domain
        self.domain = self.create_domain()

    def test_create_recordset(self):
        # Create a zone
        fixture = self.get_recordset_fixture(self.domain['name'], fixture=0)
        response = self.client.post_json(
            '/zones/%s/recordsets' % self.domain['id'], {'recordset': fixture})

        # Check the headers are what we expect
        self.assertEqual(201, response.status_int)
        self.assertEqual('application/json', response.content_type)

        # Check the body structure is what we expect
        self.assertIn('recordset', response.json)
        self.assertIn('links', response.json['recordset'])
        self.assertIn('self', response.json['recordset']['links'])

        # Check the values returned are what we expect
        self.assertIn('id', response.json['recordset'])
        self.assertIn('created_at', response.json['recordset'])
        self.assertIsNone(response.json['recordset']['updated_at'])

        for k in fixture:
            self.assertEqual(fixture[k], response.json['recordset'][k])

    def test_create_recordset_validation(self):
        # NOTE: The schemas should be tested separatly to the API. So we
        #       don't need to test every variation via the API itself.
        # Fetch a fixture
        fixture = self.get_recordset_fixture(self.domain['name'], fixture=0)

        # Add a junk field to the wrapper
        body = {'recordset': fixture, 'junk': 'Junk Field'}

        # Ensure it fails with a 400
        response = self.client.post_json('/zones/%s/recordsets' %
                                         self.domain['id'],
                                         body,
                                         status=400)

        self.assertEqual(400, response.status_int)

        # Add a junk field to the body
        fixture['junk'] = 'Junk Field'
        body = {'recordset': fixture}

        # Ensure it fails with a 400
        response = self.client.post_json('/zones/%s/recordsets' %
                                         self.domain['id'],
                                         body,
                                         status=400)

    @patch.object(central_service.Service,
                  'create_recordset',
                  side_effect=rpc_common.Timeout())
    def test_create_recordset_timeout(self, _):
        fixture = self.get_recordset_fixture(self.domain['name'], fixture=0)

        body = {'recordset': fixture}
        self.client.post_json('/zones/%s/recordsets' % self.domain['id'],
                              body,
                              status=504)

    @patch.object(central_service.Service,
                  'create_recordset',
                  side_effect=exceptions.DuplicateDomain())
    def test_create_recordset_duplicate(self, _):
        fixture = self.get_recordset_fixture(self.domain['name'], fixture=0)

        body = {'recordset': fixture}
        self.client.post_json('/zones/%s/recordsets' % self.domain['id'],
                              body,
                              status=409)

    def test_create_recordset_invalid_domain(self):
        fixture = self.get_recordset_fixture(self.domain['name'], fixture=0)

        body = {'recordset': fixture}
        self.client.post_json(
            '/zones/ba751950-6193-11e3-949a-0800200c9a66/recordsets',
            body,
            status=404)

    def test_get_recordsets(self):
        response = self.client.get('/zones/%s/recordsets' % self.domain['id'])

        # Check the headers are what we expect
        self.assertEqual(200, response.status_int)
        self.assertEqual('application/json', response.content_type)

        # Check the body structure is what we expect
        self.assertIn('recordsets', response.json)
        self.assertIn('links', response.json)
        self.assertIn('self', response.json['links'])

        # We should start with 0 recordsets
        self.assertEqual(0, len(response.json['recordsets']))

        data = [
            self.create_recordset(self.domain,
                                  name='x-%s.%s' % (i, self.domain['name']))
            for i in xrange(0, 10)
        ]

        url = '/zones/%s/recordsets' % self.domain['id']
        self._assert_paging(data, url, key='recordsets')

    @patch.object(central_service.Service,
                  'find_recordsets',
                  side_effect=rpc_common.Timeout())
    def test_get_recordsets_timeout(self, _):
        self.client.get(
            '/zones/ba751950-6193-11e3-949a-0800200c9a66/recordsets',
            status=504)

    def test_get_recordset(self):
        # Create a recordset
        recordset = self.create_recordset(self.domain)

        url = '/zones/%s/recordsets/%s' % (self.domain['id'], recordset['id'])
        response = self.client.get(url)

        # Check the headers are what we expect
        self.assertEqual(200, response.status_int)
        self.assertEqual('application/json', response.content_type)

        # Check the body structure is what we expect
        self.assertIn('recordset', response.json)
        self.assertIn('links', response.json['recordset'])
        self.assertIn('self', response.json['recordset']['links'])

        # Check the values returned are what we expect
        self.assertIn('id', response.json['recordset'])
        self.assertIn('created_at', response.json['recordset'])
        self.assertIsNone(response.json['recordset']['updated_at'])
        self.assertEqual(recordset['name'], response.json['recordset']['name'])
        self.assertEqual(recordset['type'], response.json['recordset']['type'])

    @patch.object(central_service.Service,
                  'get_recordset',
                  side_effect=rpc_common.Timeout())
    def test_get_recordset_timeout(self, _):
        self.client.get('/zones/%s/recordsets/ba751950-6193-11e3-949a-0800200c'
                        '9a66' % self.domain['id'],
                        headers={'Accept': 'application/json'},
                        status=504)

    @patch.object(central_service.Service,
                  'get_recordset',
                  side_effect=exceptions.RecordSetNotFound())
    def test_get_recordset_missing(self, _):
        self.client.get('/zones/%s/recordsets/ba751950-6193-11e3-949a-0800200c'
                        '9a66' % self.domain['id'],
                        headers={'Accept': 'application/json'},
                        status=404)

    def test_get_recordset_invalid_id(self):
        self.skip('We don\'t guard against this in APIv2 yet')

    def test_update_recordset(self):
        # Create a recordset
        recordset = self.create_recordset(self.domain)

        # Prepare an update body
        body = {'recordset': {'description': 'Tester'}}

        url = '/zones/%s/recordsets/%s' % (recordset['domain_id'],
                                           recordset['id'])
        response = self.client.patch_json(url, body, status=200)

        # Check the headers are what we expect
        self.assertEqual(200, response.status_int)
        self.assertEqual('application/json', response.content_type)

        # Check the body structure is what we expect
        self.assertIn('recordset', response.json)
        self.assertIn('links', response.json['recordset'])
        self.assertIn('self', response.json['recordset']['links'])

        # Check the values returned are what we expect
        self.assertIn('id', response.json['recordset'])
        self.assertIsNotNone(response.json['recordset']['updated_at'])
        self.assertEqual('Tester', response.json['recordset']['description'])

    def test_update_recordset_validation(self):
        # NOTE: The schemas should be tested separatly to the API. So we
        #       don't need to test every variation via the API itself.
        # Create a zone
        recordset = self.create_recordset(self.domain)

        # Prepare an update body with junk in the wrapper
        body = {'recordset': {'description': 'Tester'}, 'junk': 'Junk Field'}

        # Ensure it fails with a 400
        url = '/zones/%s/recordsets/%s' % (recordset['domain_id'],
                                           recordset['id'])
        self.client.patch_json(url, body, status=400)

        # Prepare an update body with junk in the body
        body = {'recordset': {'description': 'Tester', 'junk': 'Junk Field'}}

        # Ensure it fails with a 400
        url = '/zones/%s/recordsets/%s' % (recordset['domain_id'],
                                           recordset['id'])
        self.client.patch_json(url, body, status=400)

    @patch.object(central_service.Service,
                  'get_recordset',
                  side_effect=exceptions.DuplicateRecordSet())
    def test_update_recordset_duplicate(self, _):
        # Prepare an update body
        body = {'recordset': {'description': 'Tester'}}

        # Ensure it fails with a 409
        url = ('/zones/%s/recordsets/ba751950-6193-11e3-949a-0800200c9a66' %
               (self.domain['id']))
        self.client.patch_json(url, body, status=409)

    @patch.object(central_service.Service,
                  'get_recordset',
                  side_effect=rpc_common.Timeout())
    def test_update_recordset_timeout(self, _):
        # Prepare an update body
        body = {'recordset': {'description': 'Tester'}}

        # Ensure it fails with a 504
        url = ('/zones/%s/recordsets/ba751950-6193-11e3-949a-0800200c9a66' %
               (self.domain['id']))
        self.client.patch_json(url, body, status=504)

    @patch.object(central_service.Service,
                  'get_recordset',
                  side_effect=exceptions.RecordSetNotFound())
    def test_update_recordset_missing(self, _):
        # Prepare an update body
        body = {'recordset': {'description': 'Tester'}}

        # Ensure it fails with a 404
        url = ('/zones/%s/recordsets/ba751950-6193-11e3-949a-0800200c9a66' %
               (self.domain['id']))
        self.client.patch_json(url, body, status=404)

    def test_update_recordset_invalid_id(self):
        self.skip('We don\'t guard against this in APIv2 yet')

    def test_delete_recordset(self):
        recordset = self.create_recordset(self.domain)

        url = '/zones/%s/recordsets/%s' % (recordset['domain_id'],
                                           recordset['id'])
        self.client.delete(url, status=204)

    @patch.object(central_service.Service,
                  'delete_recordset',
                  side_effect=rpc_common.Timeout())
    def test_delete_recordset_timeout(self, _):
        url = ('/zones/%s/recordsets/ba751950-6193-11e3-949a-0800200c9a66' %
               (self.domain['id']))

        self.client.delete(url, status=504)

    @patch.object(central_service.Service,
                  'delete_recordset',
                  side_effect=exceptions.RecordSetNotFound())
    def test_delete_recordset_missing(self, _):
        url = ('/zones/%s/recordsets/ba751950-6193-11e3-949a-0800200c9a66' %
               (self.domain['id']))
        self.client.delete(url, status=404)

    def test_delete_recordset_invalid_id(self):
        self.skip('We don\'t guard against this in APIv2 yet')