designate/designate/tests/unit/backend/test_dynect.py

500 lines
14 KiB
Python

# Copyright 2014 Hewlett-Packard Development Company, L.P.
#
# Author: Endre Karlson <endre.karlson@hpe.com>
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# 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.
from unittest import mock
from oslo_config import fixture as cfg_fixture
import oslotest.base
import requests_mock
from designate.backend import impl_dynect
import designate.conf
from designate import context
from designate import exceptions
from designate import objects
from designate.tests import base_fixtures
CONF = designate.conf.CONF
MASTERS = [
'192.0.2.1'
]
CONTACT = 'jdoe@example.org'
LOGIN_SUCCESS = {
'status': 'success',
'data': {
'token': 'foo',
'version': '3.5.6'
},
'job_id': 1,
'msgs': [
{
'INFO': 'login: Login successful',
'SOURCE': 'BLL',
'ERR_CD': None,
'LVL': 'INFO'
}
]
}
LOGOUT_SUCCESS = {
'status': 'success',
'data': {},
'job_id': 1345964647,
'msgs': [
{
'INFO': 'logout: Logout successful',
'SOURCE': 'BLL',
'ERR_CD': None,
'LVL': 'INFO'
}
]
}
ZONE_CREATE = {
'contact_nickname': 'owner',
'masters': ['192.0.2.1'],
'token': 'foo',
'zone': 'example.com',
}
ZONE_DELETE = {
'token': 'foo',
'zone': 'example.com',
}
INVALID_ZONE_DELETE_DATA = {
'status': 'failure',
'data': {},
'job_id': 1326038394,
'msgs': [
{
'INFO': 'delete: Zone not deleted',
'SOURCE': 'BLL',
'ERR_CD': None,
'LVL': 'INFO'
}
]
}
INVALID_MASTER_DATA = {
'status': 'failure',
'data': {},
'job_id': 1326038394,
'msgs': [
{
'INFO': 'master: IP address expected',
'SOURCE': 'DYN',
'ERR_CD': 'INVALID_DATA',
'LVL': 'ERROR'
},
{
'INFO': 'create: Zone not created',
'SOURCE': 'BLL',
'ERR_CD': None,
'LVL': 'INFO'
}
]
}
TARGET_EXISTS = {
'status': 'failure',
'data': {},
'job_id': 1345944906,
'msgs': [
{
'INFO': 'name: Name already exists',
'SOURCE': 'BLL',
'ERR_CD': 'TARGET_EXISTS',
'LVL': 'ERROR'
},
{
'INFO': 'create: You already have this zone.',
'SOURCE': 'BLL',
'ERR_CD': None,
'LVL': 'INFO'
}
]
}
ACTIVATE_SUCCESS = {
'status': 'success',
'data': {
'active': 'L',
'masters': MASTERS,
'contact_nickname': CONTACT,
'tsig_key_name': '',
'zone': 'example.com'
},
'job_id': 1345944927,
'msgs': [
{
'INFO': 'activate: Service activated',
'SOURCE': 'BLL',
'ERR_CD': None,
'LVL': 'INFO'
}
]
}
class DynClientTestsCase(oslotest.base.BaseTestCase):
def setUp(self):
super().setUp()
def test_dyn_client_auth_error(self):
client = impl_dynect.DynClient(
'customer_name', 'user_name', 'password', 'timeout', 'timings'
)
client.token = 'fake'
client._request = mock.Mock()
client._request.side_effect = impl_dynect.DynClientAuthError()
self.assertRaisesRegex(
impl_dynect.DynClientAuthError,
r'None \(HTTP None to None - None\) - None',
client.request, 'POST', '/Test/'
)
class DynECTTestsCase(oslotest.base.BaseTestCase):
def setUp(self):
super().setUp()
self.useFixture(cfg_fixture.Config(CONF))
self.stdlog = base_fixtures.StandardLogging()
self.useFixture(self.stdlog)
self.context = mock.Mock()
self.admin_context = mock.Mock()
mock.patch.object(
context.DesignateContext, 'get_admin_context',
return_value=self.admin_context).start()
self.base_address = 'https://api.dynect.net:443/REST'
self.zone = objects.Zone(
id='e2bed4dc-9d01-11e4-89d3-123b93f75cba',
name='example.com.',
email='example@example.com',
)
self.target = {
'id': '4588652b-50e7-46b9-b688-a9bad40a873e',
'type': 'dyndns',
'masters': [
{'host': '192.0.2.1', 'port': 53}
],
'options': [
{'key': 'username', 'value': 'example'},
{'key': 'password', 'value': 'secret'},
{'key': 'customer_name', 'value': 'customer'},
{'key': 'contact_nickname', 'value': 'customer'},
{'key': 'tsig_key_name', 'value': 'tsig'},
],
}
self.backend = impl_dynect.DynECTBackend(
objects.PoolTarget.from_dict(self.target)
)
def test_masters_only_allow_port_53(self):
self.target['masters'] = [
{'host': '192.0.2.1', 'port': 5354}
]
self.assertRaisesRegex(
exceptions.ConfigurationError,
'DynECT only supports mDNS instances on port 53',
impl_dynect.DynECTBackend,
objects.PoolTarget.from_dict(self.target)
)
@requests_mock.mock()
def test_create_zone(self, req_mock):
req_mock.post(
'%s/Session' % self.base_address,
json=LOGIN_SUCCESS,
)
req_mock.delete(
'%s/Session' % self.base_address,
json=LOGOUT_SUCCESS,
)
req_mock.post(
'%s/Secondary/example.com' % self.base_address,
json=ZONE_CREATE,
status_code=200,
)
req_mock.put(
'%s/Secondary/example.com' % self.base_address,
json=ACTIVATE_SUCCESS,
status_code=200,
)
self.backend.create_zone(self.context, self.zone)
@requests_mock.mock()
def test_create_zone_with_timings(self, req_mock):
CONF.set_override('timings', True, 'backend:dynect')
req_mock.post(
'%s/Session' % self.base_address,
json=LOGIN_SUCCESS,
)
req_mock.delete(
'%s/Session' % self.base_address,
json=LOGOUT_SUCCESS,
)
req_mock.post(
'%s/Secondary/example.com' % self.base_address,
json=ZONE_CREATE,
status_code=200,
)
req_mock.put(
'%s/Secondary/example.com' % self.base_address,
json=ACTIVATE_SUCCESS,
status_code=200,
)
self.backend.create_zone(self.context, self.zone)
@requests_mock.mock()
def test_create_zone_missing_contact_and_tsig(self, req_mock):
self.target['options'] = [
{'key': 'username', 'value': 'example'},
{'key': 'password', 'value': 'secret'},
]
backend = impl_dynect.DynECTBackend(
objects.PoolTarget.from_dict(self.target)
)
req_mock.post(
'%s/Session' % self.base_address,
json=LOGIN_SUCCESS,
)
req_mock.delete(
'%s/Session' % self.base_address,
json=LOGOUT_SUCCESS,
)
req_mock.post(
'%s/Secondary/example.com' % self.base_address,
json=ZONE_CREATE,
status_code=200,
)
req_mock.put(
'%s/Secondary/example.com' % self.base_address,
json=ACTIVATE_SUCCESS,
status_code=200,
)
backend.create_zone(self.context, self.zone)
@requests_mock.mock()
def test_create_zone_raise_dynclienterror(self, req_mock):
# https://api.dynect.net:443/REST/Session
req_mock.post(
'%s/Session' % self.base_address,
json=LOGIN_SUCCESS,
)
req_mock.post(
'%s/Secondary/example.com' % self.base_address,
json=INVALID_MASTER_DATA,
status_code=400,
)
self.assertRaisesRegex(
impl_dynect.DynClientError, 'Zone not created',
self.backend.create_zone, self.context, self.zone,
)
@requests_mock.mock()
def test_create_zone_duplicate_updates_existing(self, req_mock):
req_mock.post(
'%s/Session' % self.base_address,
json=LOGIN_SUCCESS,
)
req_mock.delete(
'%s/Session' % self.base_address,
json=LOGIN_SUCCESS,
)
req_mock.post(
'%s/Secondary/example.com' % self.base_address,
json=TARGET_EXISTS,
status_code=400,
)
req_mock.put(
'%s/Secondary/example.com' % self.base_address,
json=ACTIVATE_SUCCESS,
)
self.backend.create_zone(self.context, self.zone)
@requests_mock.mock()
def test_delete_zone(self, req_mock):
req_mock.post(
'%s/Session' % self.base_address,
json=LOGIN_SUCCESS,
)
req_mock.delete(
'%s/Session' % self.base_address,
json=LOGOUT_SUCCESS,
)
req_mock.delete(
'%s/Zone/example.com' % self.base_address,
json=ZONE_DELETE,
status_code=200,
)
req_mock.put(
'%s/Secondary/example.com' % self.base_address,
json=ACTIVATE_SUCCESS,
status_code=200,
)
self.backend.delete_zone(self.context, self.zone)
@requests_mock.mock()
def test_delete_zone_not_found(self, req_mock):
req_mock.post(
'%s/Session' % self.base_address,
json=LOGIN_SUCCESS,
)
req_mock.delete(
'%s/Session' % self.base_address,
json=LOGOUT_SUCCESS,
)
req_mock.delete(
'%s/Zone/example.com' % self.base_address,
json=INVALID_ZONE_DELETE_DATA,
status_code=404
)
req_mock.put(
'%s/Secondary/example.com' % self.base_address,
json=ACTIVATE_SUCCESS,
status_code=200,
)
self.backend.delete_zone(self.context, self.zone)
self.assertIn(
'Attempt to delete e2bed4dc-9d01-11e4-89d3-123b93f75cba / '
'example.com. caused 404, ignoring.',
self.stdlog.logger.output
)
@requests_mock.mock()
def test_delete_zone_raise_dynclienterror(self, req_mock):
req_mock.post(
'%s/Session' % self.base_address,
json=LOGIN_SUCCESS,
)
req_mock.delete(
'%s/Session' % self.base_address,
json=LOGOUT_SUCCESS,
)
req_mock.delete(
'%s/Zone/example.com' % self.base_address,
json=INVALID_ZONE_DELETE_DATA,
status_code=400,
)
req_mock.put(
'%s/Secondary/example.com' % self.base_address,
json=ACTIVATE_SUCCESS,
status_code=200,
)
self.assertRaisesRegex(
impl_dynect.DynClientError, 'Zone not deleted',
self.backend.delete_zone, self.context, self.zone,
)
@requests_mock.mock()
def test_request(self, req_mock):
req_mock.post(
'%s/Session' % self.base_address,
json=LOGIN_SUCCESS,
)
req_mock.post(
'https://api.dynect.net:443/'
)
req_mock.post(
'https://api.dynect.net:443/REST'
)
client = self.backend.get_client()
self.assertEqual(200,
client._request(
'POST',
'https://api.dynect.net:443/REST').status_code
)
self.assertEqual(200, client._request('POST', '').status_code)
def test_error_from_response(self):
error_data = dict(INVALID_ZONE_DELETE_DATA)
mock_response = mock.Mock()
mock_response.json.return_value = error_data
error = impl_dynect.DynClientError().from_response(mock_response)
self.assertEqual(error_data['job_id'], error.job_id)
def test_error_from_response_login_failed(self):
error_data = dict(INVALID_ZONE_DELETE_DATA)
error_data['msgs'] = [
{
'INFO': 'login: foo',
'SOURCE': 'BLL',
'ERR_CD': None,
'LVL': 'INFO'
}
]
mock_response = mock.Mock()
mock_response.json.return_value = error_data
self.assertRaisesRegex(
impl_dynect.DynClientAuthError,
'login: foo',
impl_dynect.DynClientError().from_response, mock_response
)
def test_error_from_response_operation_failed(self):
error_data = dict(INVALID_ZONE_DELETE_DATA)
error_data['msgs'] = [
{
'INFO': 'Operation blocked',
'SOURCE': 'BLL',
'ERR_CD': None,
'LVL': 'INFO'
}
]
mock_response = mock.Mock()
mock_response.json.return_value = error_data
self.assertRaisesRegex(
impl_dynect.DynClientOperationBlocked,
'Operation blocked',
impl_dynect.DynClientError().from_response, mock_response
)