Skip to content

Commit

Permalink
Merge branch 'master' into bug/NIOS-86124
Browse files Browse the repository at this point in the history
  • Loading branch information
Alex Chernevskiy committed Sep 15, 2022
2 parents 4e7a98e + 94a32c6 commit bf99a21
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 24 deletions.
8 changes: 8 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,14 @@ Create host record with Extensible Attributes (EA):
'Cloud API Owned': True})
host = objects.HostRecord.create(conn, name='new_host', ip=my_ip, extattrs=ea)
Create a host record with inherited Extensible Attributes (EA):

.. code:: python
my_ip = objects.IP.create(ip='192.168.1.25', mac='aa:bb:cc:11:22:33', use_for_ea_inheritance=True)
hr = objects.HostRecord.create(conn, view='my_dns_view',
name='my_host_record.my_zone.com', ip=my_ip)
Set the TTL to 30 minutes:

.. code:: python
Expand Down
65 changes: 64 additions & 1 deletion e2e_tests/test_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
import unittest

from e2e_tests.connector_facade import E2EConnectorFacade
from infoblox_client.objects import ARecord, DNSZone, AAAARecord
from infoblox_client.objects import ARecord, DNSZone, AAAARecord, Network, \
EADefinition, EA, HostRecord, IP


class TestObjectsE2E(unittest.TestCase):
Expand Down Expand Up @@ -109,3 +110,65 @@ def test_update_dns_zone(self):
zone.Comment = "Modified"
zone.update()

def test_host_record_ea_inheritance(self):
"""
Checks if EA inheritance for record:host object
works as expected
"""
# Create inheritable extensible attribute
EADefinition.create(
self.connector,
name="Test HostRecord EA Inheritance",
type="STRING",
flags="I",
)
# Create two networks with inheritable
# extensible attributes
Network.create(
self.connector,
network="192.170.1.0/24",
network_view="default",
extattrs=EA({
"Test HostRecord EA Inheritance": "Expected Value"
})
)
Network.create(
self.connector,
network="192.180.1.0/24",
network_view="default",
extattrs=EA({
"Test HostRecord EA Inheritance": "Second Value"
})
)

# Create DNS Zone for the host record
DNSZone.create(
self.connector,
view='default',
fqdn="e2e-test.com",
)

# Create two ips in both networks
# One IP will be used for EA inheritance
ip170net = IP.create(
ip="192.170.1.25",
mac="00:00:00:00:00:00",
use_for_ea_inheritance=True,
)
ip180net = IP.create(
ip="192.180.1.25",
mac="00:00:00:00:00:00",
)

hr = HostRecord.create(
self.connector,
view="default",
name="test_host_record_ea_inheritance.e2e-test.com",
ips=[ip170net, ip180net]
)

# Expect host record to inherit EAs from 192.170.1.0/24 network
self.assertEqual(
"Expected Value",
hr.extattrs.ea_dict["Test HostRecord EA Inheritance"]
)
44 changes: 33 additions & 11 deletions infoblox_client/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
def reraise_neutron_exception(func):
"""This decorator catches third-party exceptions and replaces them with
Infoblox exceptions"""

@functools.wraps(func)
def callee(*args, **kwargs):
try:
Expand Down Expand Up @@ -93,26 +94,44 @@ def __init__(self, options):
def _parse_options(self, options):
"""Copy needed options to self"""
attributes = ('host', 'wapi_version', 'username', 'password',
'ssl_verify', 'http_request_timeout', 'max_retries',
'http_pool_connections', 'http_pool_maxsize',
'silent_ssl_warnings', 'log_api_calls_as_info',
'max_results', 'paging')
'cert', 'key', 'ssl_verify', 'http_request_timeout',
'max_retries', 'http_pool_connections',
'http_pool_maxsize', 'silent_ssl_warnings',
'log_api_calls_as_info', 'max_results',
'paging')

creds_basic_auth = ['username', 'password']
creds_cert_auth = ['cert', 'key']
creds = creds_basic_auth + creds_cert_auth

for attr in attributes:
if isinstance(options, dict) and attr in options:
if isinstance(options, dict) and \
attr in options and \
options[attr] is not None:
setattr(self, attr, options[attr])
elif hasattr(options, attr):
value = getattr(options, attr)
setattr(self, attr, value)
elif attr in self.DEFAULT_OPTIONS:
setattr(self, attr, self.DEFAULT_OPTIONS[attr])
else:
elif attr not in creds:
msg = "WAPI config error. Option %s is not defined" % attr
raise ib_ex.InfobloxConfigException(msg=msg)

for attr in ('host', 'username', 'password'):
if not getattr(self, attr):
msg = "WAPI config error. Option %s can not be blank" % attr
raise ib_ex.InfobloxConfigException(msg=msg)
def check_creds(credentials):
for attr in credentials:
try:
getattr(self, attr)
except AttributeError:
return False
return True

# If no basic and cert creds are provided, return an error
if not check_creds(creds_basic_auth) and \
not check_creds(creds_cert_auth):
msg = "WAPI config error. Option either (username, password) " \
"or (cert, key) should be passed"
raise ib_ex.InfobloxConfigException(msg=msg)

self.wapi_url = "https://%s/wapi/v%s/" % (self.host,
self.wapi_version)
Expand All @@ -127,7 +146,10 @@ def _configure_session(self):
max_retries=self.max_retries)
self.session.mount('http://', adapter)
self.session.mount('https://', adapter)
self.session.auth = (self.username, self.password)
if hasattr(self, 'username') and hasattr(self, 'password'):
self.session.auth = (self.username, self.password)
else:
self.session.cert = (self.cert, self.key)
self.session.verify = utils.try_value_to_bool(self.ssl_verify,
strict_mode=False)

Expand Down
6 changes: 4 additions & 2 deletions infoblox_client/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -574,13 +574,15 @@ def ip(self, ip):


class IPv4(IP):
_fields = ['ipv4addr', 'configure_for_dhcp', 'mac']
_fields = ['ipv4addr', 'configure_for_dhcp',
'use_for_ea_inheritance', 'mac']
_remap = {'ipv4addr': 'ip'}
ip_version = 4


class IPv6(IP):
_fields = ['ipv6addr', 'configure_for_dhcp', 'duid']
_fields = ['ipv6addr', 'configure_for_dhcp',
'use_for_ea_inheritance', 'duid']
_remap = {'ipv6addr': 'ip'}
ip_version = 6

Expand Down
74 changes: 64 additions & 10 deletions tests/test_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
from mock import patch
from requests import exceptions as req_exc


try:
from oslo_serialization import jsonutils
except ImportError: # pragma: no cover
Expand Down Expand Up @@ -93,9 +92,12 @@ def test_create_object(self):

def test_create_object_with_extattrs(self):
objtype = 'network'
payload = {'extattrs':
{'Subnet ID': {'value': 'fake_subnet_id'}},
'ip': '0.0.0.0'}
payload = {
'extattrs': {
'Subnet ID': {'value': 'fake_subnet_id'}
},
'ip': '0.0.0.0',
}
with patch.object(requests.Session, 'post',
return_value=mock.Mock()) as patched_create:
patched_create.return_value.status_code = 201
Expand Down Expand Up @@ -337,8 +339,9 @@ def test_construct_url_with_query_params_and_extattrs(self):
url = self.connector._construct_url('network',
query_params=query_params,
extattrs=ext_attrs)
self.assertEqual('https://infoblox.example.org/wapi/v1.1/network?%2ASubnet+ID=fake_subnet_id&some_option=some_value', # noqa: E501
url)
self.assertEqual(
'https://infoblox.example.org/wapi/v1.1/network?%2ASubnet+ID=fake_subnet_id&some_option=some_value', # noqa: E501
url)

def test_construct_url_with_query_params_containing_array(self):
query_params = {'array_option': ['value1', 'value2']}
Expand Down Expand Up @@ -501,7 +504,7 @@ def test_call_func(self):
)

def test_call_upload_file(self):
upload_file_path = '/http_direct_file_io/req_id-UPLOAD-0302163936014609/ibx_networks.csv'
upload_file_path = '/http_direct_file_io/req_id-UPLOAD-0302163936014609/ibx_networks.csv' # noqa: E501
upload_url = 'https://infoblox.example.org' + upload_file_path
self._create_infoblox_csv()
with open('tests/ibx_networks.csv', 'r') as fh:
Expand All @@ -518,7 +521,7 @@ def test_call_upload_file(self):
self._delete_infoblox_csv()

def test_call_upload_file_with_error_403(self):
upload_file_path = '/http_direct_file_io/req_id-UPLOAD-0302163936014609/ibx_networks.csv'
upload_file_path = '/http_direct_file_io/req_id-UPLOAD-0302163936014609/ibx_networks.csv' # noqa: E501
upload_url = 'https://infoblox.example.org' + upload_file_path
self._create_infoblox_csv()
with open('tests/ibx_networks.csv', 'r') as fh:
Expand All @@ -536,7 +539,7 @@ def test_call_upload_file_with_error_403(self):
self._delete_infoblox_csv()

def test_call_download_file(self):
download_file_path = '/http_direct_file_io/req_id-DOWNLOAD-0302163936014609/ibx_networks.csv'
download_file_path = '/http_direct_file_io/req_id-DOWNLOAD-0302163936014609/ibx_networks.csv' # noqa: E501
download_url = 'https://infoblox.example.org' + download_file_path
with patch.object(requests.Session, 'get',
return_value=mock.Mock()) as patched_get:
Expand All @@ -547,7 +550,7 @@ def test_call_download_file(self):
self.assertEqual(None, self.connector.session.auth)

def test_call_download_file_with_error_403(self):
download_file_path = '/http_direct_file_io/req_id-DOWNLOAD-0302163936014609/ibx_networks.csv'
download_file_path = '/http_direct_file_io/req_id-DOWNLOAD-0302163936014609/ibx_networks.csv' # noqa: E501
download_url = 'https://infoblox.example.org' + download_file_path
with patch.object(requests.Session, 'get',
return_value=mock.Mock()) as patched_get:
Expand Down Expand Up @@ -714,6 +717,21 @@ def test_blank_values_not_allowed(self):
self.assertRaises(exceptions.InfobloxConfigException,
connector.Connector, test_dict)

def test_blank_values_not_allowed_cert_auth(self):
"""
Checks if connector's _parse_options method raises
exception if one of the host/cert/key is not provided
"""
base_dict = {'host': '192.168.1.15',
'cert': 'cert',
'key': 'key'}
for field in base_dict:
test_dict = base_dict.copy()
test_dict[field] = None
self.assertRaises(exceptions.InfobloxConfigException,
connector.Connector, test_dict)

def test_is_cloud_wapi_raises_exception(self):
for value in (None, '', 0, 1, self, 1.2):
self.assertRaises(ValueError,
Expand Down Expand Up @@ -742,3 +760,39 @@ def test__parse_reply(self):

parsed_reply = connector.Connector._parse_reply(request)
self.assertEqual(expected_reply, parsed_reply)

def test_session_auth(self):
"""
Checks if connector's session is configured,
when username and password are provided
"""
# Case 1: Only username and password are provided
options = {'host': '192.168.1.15',
'username': 'admin',
'password': 'pass'}
conn = connector.Connector(options)
self.assertEqual(conn.session.auth, ('admin', 'pass'))
self.assertEqual(conn.session.cert, None)

# Case 2: Username, password, cert and key are
# provided. Connector should use username and password.
options = {'host': '192.168.1.15',
'username': 'admin',
'password': 'pass',
'cert': 'cert',
'key': 'key'}
conn = connector.Connector(options)
self.assertEqual(conn.session.auth, ('admin', 'pass'))
self.assertEqual(conn.session.cert, None)

def test_session_cert(self):
"""
Checks if connector's session is configured,
when cert and key are provided
"""
options = {'host': '192.168.1.15',
'cert': 'cert',
'key': 'key'}
conn = connector.Connector(options)
self.assertEqual(conn.session.auth, None)
self.assertEqual(conn.session.cert, ('cert', 'key'))

0 comments on commit bf99a21

Please sign in to comment.