369 lines
16 KiB
Python
369 lines
16 KiB
Python
import subprocess
|
|
import unittest
|
|
|
|
from mock import patch, MagicMock
|
|
|
|
import charmhelpers.contrib.network.ovs as ovs
|
|
|
|
from tests.helpers import patch_open
|
|
|
|
|
|
# NOTE(fnordahl): some functions drectly under the ``contrib.network.ovs``
|
|
# module have their unit tests in the ``test_ovs.py`` module in the
|
|
# ``tests.contrib.network.ovs`` package.
|
|
|
|
|
|
GOOD_CERT = '''Certificate:
|
|
Data:
|
|
Version: 1 (0x0)
|
|
Serial Number: 13798680962510501282 (0xbf7ec33a136235a2)
|
|
Signature Algorithm: sha1WithRSAEncryption
|
|
Issuer: C=US, ST=CA, L=Palo Alto, O=Open vSwitch, OU=Open vSwitch
|
|
Validity
|
|
Not Before: Jun 28 17:02:19 2013 GMT
|
|
Not After : Jun 28 17:02:19 2019 GMT
|
|
Subject: C=US, ST=CA, L=Palo Alto, O=Open vSwitch, OU=Open vSwitch
|
|
Subject Public Key Info:
|
|
Public Key Algorithm: rsaEncryption
|
|
Public-Key: (2048 bit)
|
|
Modulus:
|
|
00:e8:a7:db:0a:6d:c0:16:4a:14:96:1d:74:91:15:
|
|
64:3f:ae:2a:54:be:2a:fe:10:14:9a:73:39:d8:58:
|
|
74:7f:ab:d5:f2:39:aa:9a:27:7c:31:82:f8:74:42:
|
|
46:8d:c5:3b:42:55:52:be:75:7f:a5:b1:ec:d5:29:
|
|
9f:62:0e:de:31:27:2b:95:1f:24:0d:ca:8c:48:30:
|
|
96:9f:ba:b7:9d:eb:c1:bd:93:05:e3:d8:ca:66:5a:
|
|
e9:cb:a5:7a:3a:8d:27:e2:05:9d:88:fc:a9:ef:af:
|
|
47:4c:66:ce:c6:43:73:1a:85:f4:5f:b9:53:5b:29:
|
|
f3:c3:23:1f:0c:20:95:11:50:71:b2:f6:01:23:3f:
|
|
66:0f:5c:43:c2:90:fb:e5:98:73:98:e9:38:bb:1f:
|
|
1b:89:97:1e:dc:d7:98:07:68:32:ec:da:1d:69:0b:
|
|
e2:df:40:fb:64:52:e5:e9:40:27:b0:ca:73:21:51:
|
|
f6:8f:00:20:c0:2b:1a:d4:01:c2:32:38:9d:d1:8d:
|
|
88:71:46:a9:42:0d:ee:3b:1c:88:db:27:69:49:f9:
|
|
60:34:70:61:3d:60:df:7e:e4:e1:1d:c6:16:89:05:
|
|
ba:31:06:eb:88:b5:78:94:5d:8c:9d:88:fe:f2:c2:
|
|
80:a1:04:15:d3:84:85:d3:aa:5a:1d:53:5c:f8:57:
|
|
ae:61
|
|
Exponent: 65537 (0x10001)
|
|
Signature Algorithm: sha1WithRSAEncryption
|
|
14:7e:ca:c3:fc:93:60:9f:80:e0:65:2e:ef:41:2d:f9:af:77:
|
|
da:6d:e2:e0:11:70:17:fb:e5:67:4c:f0:ad:39:ec:96:ef:fe:
|
|
d5:95:94:70:e5:52:31:68:63:8c:ea:b3:a1:8e:02:e2:91:4b:
|
|
a8:8c:07:86:fd:80:98:a2:b1:90:2b:9c:2e:ab:f4:73:9d:8f:
|
|
fd:31:b9:8f:fe:6c:af:d6:bf:72:44:89:08:93:19:ef:2b:c3:
|
|
7c:ab:ba:bc:57:ca:f1:17:e4:e8:81:40:ca:65:df:84:be:10:
|
|
2c:42:46:af:d2:e0:0d:df:5d:56:53:65:13:e0:20:55:b4:ee:
|
|
cd:5e:b5:c4:97:1d:3e:a6:c1:9c:7e:b8:87:ee:64:78:a5:59:
|
|
e5:b2:79:47:9a:8e:59:fa:c4:18:ea:27:fd:a2:d5:76:d0:ae:
|
|
d9:05:f6:0e:23:ca:7d:66:a1:ba:18:67:f5:6d:bb:51:5a:f5:
|
|
52:e9:17:bb:63:15:24:b4:61:25:9f:d9:9c:89:58:93:9a:c3:
|
|
74:55:72:3e:f9:ff:ef:54:7d:e8:28:78:ba:3c:c7:15:ba:b9:
|
|
c6:e3:8c:61:cb:a9:ed:8d:07:16:0d:8d:f6:1c:36:11:69:08:
|
|
b8:45:7d:fc:fd:d1:ab:2d:9b:4e:9c:dd:11:78:50:c7:87:9f:
|
|
4a:24:9c:a0
|
|
-----BEGIN CERTIFICATE-----
|
|
MIIDwjCCAqoCCQC/fsM6E2I1ojANBgkqhkiG9w0BAQUFADCBojELMAkGA1UEBhMC
|
|
VVMxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlQYWxvIEFsdG8xFTATBgNVBAoTDE9w
|
|
ZW4gdlN3aXRjaDEfMB0GA1UECxMWT3BlbiB2U3dpdGNoIGNlcnRpZmllcjE6MDgG
|
|
A1UEAxMxb3ZzY2xpZW50IGlkOjU4MTQ5N2E1LWJjMDAtNGVjYy1iNzkwLTU3NTZj
|
|
ZWUxNmE0ODAeFw0xMzA2MjgxNzAyMTlaFw0xOTA2MjgxNzAyMTlaMIGiMQswCQYD
|
|
VQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVBhbG8gQWx0bzEVMBMGA1UE
|
|
ChMMT3BlbiB2U3dpdGNoMR8wHQYDVQQLExZPcGVuIHZTd2l0Y2ggY2VydGlmaWVy
|
|
MTowOAYDVQQDEzFvdnNjbGllbnQgaWQ6NTgxNDk3YTUtYmMwMC00ZWNjLWI3OTAt
|
|
NTc1NmNlZTE2YTQ4MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6Kfb
|
|
Cm3AFkoUlh10kRVkP64qVL4q/hAUmnM52Fh0f6vV8jmqmid8MYL4dEJGjcU7QlVS
|
|
vnV/pbHs1SmfYg7eMScrlR8kDcqMSDCWn7q3nevBvZMF49jKZlrpy6V6Oo0n4gWd
|
|
iPyp769HTGbOxkNzGoX0X7lTWynzwyMfDCCVEVBxsvYBIz9mD1xDwpD75ZhzmOk4
|
|
ux8biZce3NeYB2gy7NodaQvi30D7ZFLl6UAnsMpzIVH2jwAgwCsa1AHCMjid0Y2I
|
|
cUapQg3uOxyI2ydpSflgNHBhPWDffuThHcYWiQW6MQbriLV4lF2MnYj+8sKAoQQV
|
|
04SF06paHVNc+FeuYQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQAUfsrD/JNgn4Dg
|
|
ZS7vQS35r3fabeLgEXAX++VnTPCtOeyW7/7VlZRw5VIxaGOM6rOhjgLikUuojAeG
|
|
/YCYorGQK5wuq/RznY/9MbmP/myv1r9yRIkIkxnvK8N8q7q8V8rxF+TogUDKZd+E
|
|
vhAsQkav0uAN311WU2UT4CBVtO7NXrXElx0+psGcfriH7mR4pVnlsnlHmo5Z+sQY
|
|
6if9otV20K7ZBfYOI8p9ZqG6GGf1bbtRWvVS6Re7YxUktGEln9mciViTmsN0VXI+
|
|
+f/vVH3oKHi6PMcVurnG44xhy6ntjQcWDY32HDYRaQi4RX38/dGrLZtOnN0ReFDH
|
|
h59KJJyg
|
|
-----END CERTIFICATE-----
|
|
'''
|
|
|
|
PEM_ENCODED = '''-----BEGIN CERTIFICATE-----
|
|
MIIDwjCCAqoCCQC/fsM6E2I1ojANBgkqhkiG9w0BAQUFADCBojELMAkGA1UEBhMC
|
|
VVMxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlQYWxvIEFsdG8xFTATBgNVBAoTDE9w
|
|
ZW4gdlN3aXRjaDEfMB0GA1UECxMWT3BlbiB2U3dpdGNoIGNlcnRpZmllcjE6MDgG
|
|
A1UEAxMxb3ZzY2xpZW50IGlkOjU4MTQ5N2E1LWJjMDAtNGVjYy1iNzkwLTU3NTZj
|
|
ZWUxNmE0ODAeFw0xMzA2MjgxNzAyMTlaFw0xOTA2MjgxNzAyMTlaMIGiMQswCQYD
|
|
VQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVBhbG8gQWx0bzEVMBMGA1UE
|
|
ChMMT3BlbiB2U3dpdGNoMR8wHQYDVQQLExZPcGVuIHZTd2l0Y2ggY2VydGlmaWVy
|
|
MTowOAYDVQQDEzFvdnNjbGllbnQgaWQ6NTgxNDk3YTUtYmMwMC00ZWNjLWI3OTAt
|
|
NTc1NmNlZTE2YTQ4MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6Kfb
|
|
Cm3AFkoUlh10kRVkP64qVL4q/hAUmnM52Fh0f6vV8jmqmid8MYL4dEJGjcU7QlVS
|
|
vnV/pbHs1SmfYg7eMScrlR8kDcqMSDCWn7q3nevBvZMF49jKZlrpy6V6Oo0n4gWd
|
|
iPyp769HTGbOxkNzGoX0X7lTWynzwyMfDCCVEVBxsvYBIz9mD1xDwpD75ZhzmOk4
|
|
ux8biZce3NeYB2gy7NodaQvi30D7ZFLl6UAnsMpzIVH2jwAgwCsa1AHCMjid0Y2I
|
|
cUapQg3uOxyI2ydpSflgNHBhPWDffuThHcYWiQW6MQbriLV4lF2MnYj+8sKAoQQV
|
|
04SF06paHVNc+FeuYQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQAUfsrD/JNgn4Dg
|
|
ZS7vQS35r3fabeLgEXAX++VnTPCtOeyW7/7VlZRw5VIxaGOM6rOhjgLikUuojAeG
|
|
/YCYorGQK5wuq/RznY/9MbmP/myv1r9yRIkIkxnvK8N8q7q8V8rxF+TogUDKZd+E
|
|
vhAsQkav0uAN311WU2UT4CBVtO7NXrXElx0+psGcfriH7mR4pVnlsnlHmo5Z+sQY
|
|
6if9otV20K7ZBfYOI8p9ZqG6GGf1bbtRWvVS6Re7YxUktGEln9mciViTmsN0VXI+
|
|
+f/vVH3oKHi6PMcVurnG44xhy6ntjQcWDY32HDYRaQi4RX38/dGrLZtOnN0ReFDH
|
|
h59KJJyg
|
|
-----END CERTIFICATE-----'''
|
|
|
|
BAD_CERT = ''' NO MARKERS '''
|
|
TO_PATCH = [
|
|
"apt_install",
|
|
"log",
|
|
"hashlib",
|
|
]
|
|
|
|
|
|
class OVSHelpersTest(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
for m in TO_PATCH:
|
|
setattr(self, m, self._patch(m))
|
|
|
|
def _patch(self, method):
|
|
_m = patch('charmhelpers.contrib.network.ovs.' + method)
|
|
mock = _m.start()
|
|
self.addCleanup(_m.stop)
|
|
return mock
|
|
|
|
@patch('subprocess.check_output')
|
|
def test_get_bridges(self, check_output):
|
|
check_output.return_value = b"br1\n br2 "
|
|
self.assertEqual(ovs.get_bridges(), ['br1', 'br2'])
|
|
check_output.assert_called_once_with(['ovs-vsctl', 'list-br'])
|
|
|
|
@patch('subprocess.check_output')
|
|
def test_get_bridge_ports(self, check_output):
|
|
check_output.return_value = b"p1\n p2 \np3"
|
|
self.assertEqual(ovs.get_bridge_ports('br1'), ['p1', 'p2', 'p3'])
|
|
check_output.assert_called_once_with(
|
|
['ovs-vsctl', '--', 'list-ports', 'br1'])
|
|
|
|
@patch.object(ovs, 'get_bridges')
|
|
@patch.object(ovs, 'get_bridge_ports')
|
|
def test_get_bridges_and_ports_map(self, get_bridge_ports, get_bridges):
|
|
get_bridges.return_value = ['br1', 'br2']
|
|
get_bridge_ports.side_effect = [
|
|
['p1', 'p2'],
|
|
['p3']]
|
|
self.assertEqual(ovs.get_bridges_and_ports_map(), {
|
|
'br1': ['p1', 'p2'],
|
|
'br2': ['p3'],
|
|
})
|
|
|
|
@patch('subprocess.check_call')
|
|
def test_del_bridge(self, check_call):
|
|
ovs.del_bridge('test')
|
|
check_call.assert_called_with(["ovs-vsctl", "--", "--if-exists",
|
|
"del-br", 'test'])
|
|
self.assertTrue(self.log.call_count == 1)
|
|
|
|
@patch.object(ovs, 'port_to_br')
|
|
@patch.object(ovs, 'add_bridge_port')
|
|
@patch.object(ovs, 'lsb_release')
|
|
@patch('os.path.exists')
|
|
@patch('subprocess.check_call')
|
|
def test_add_ovsbridge_linuxbridge(self, check_call, exists, lsb_release,
|
|
add_bridge_port,
|
|
port_to_br):
|
|
exists.return_value = True
|
|
lsb_release.return_value = {'DISTRIB_CODENAME': 'bionic'}
|
|
port_to_br.return_value = None
|
|
if_and_port_data = {
|
|
'external-ids': {'mycharm': 'br-ex'}
|
|
}
|
|
with patch_open() as (mock_open, mock_file):
|
|
ovs.add_ovsbridge_linuxbridge(
|
|
'br-ex', 'br-eno1', ifdata=if_and_port_data,
|
|
portdata=if_and_port_data)
|
|
|
|
check_call.assert_called_with(['ifup', 'veth-br-eno1'])
|
|
add_bridge_port.assert_called_with(
|
|
'br-ex', 'veth-br-eno1', ifdata=if_and_port_data, exclusive=False,
|
|
portdata=if_and_port_data)
|
|
|
|
@patch.object(ovs, 'port_to_br')
|
|
@patch.object(ovs, 'add_bridge_port')
|
|
@patch.object(ovs, 'lsb_release')
|
|
@patch('os.path.exists')
|
|
@patch('subprocess.check_call')
|
|
def test_add_ovsbridge_linuxbridge_already_direct_wired(
|
|
self, check_call, exists, lsb_release, add_bridge_port, port_to_br):
|
|
exists.return_value = True
|
|
lsb_release.return_value = {'DISTRIB_CODENAME': 'bionic'}
|
|
port_to_br.return_value = 'br-ex'
|
|
ovs.add_ovsbridge_linuxbridge('br-ex', 'br-eno1')
|
|
check_call.assert_not_called()
|
|
add_bridge_port.assert_not_called()
|
|
|
|
@patch.object(ovs, 'port_to_br')
|
|
@patch.object(ovs, 'add_bridge_port')
|
|
@patch.object(ovs, 'lsb_release')
|
|
@patch('os.path.exists')
|
|
@patch('subprocess.check_call')
|
|
def test_add_ovsbridge_linuxbridge_longname(self, check_call, exists,
|
|
lsb_release, add_bridge_port,
|
|
port_to_br):
|
|
exists.return_value = True
|
|
lsb_release.return_value = {'DISTRIB_CODENAME': 'bionic'}
|
|
port_to_br.return_value = None
|
|
mock_hasher = MagicMock()
|
|
mock_hasher.hexdigest.return_value = '12345678901234578910'
|
|
self.hashlib.sha256.return_value = mock_hasher
|
|
with patch_open() as (mock_open, mock_file):
|
|
ovs.add_ovsbridge_linuxbridge('br-ex', 'br-reallylongname')
|
|
|
|
check_call.assert_called_with(['ifup', 'cvb12345678-10'])
|
|
add_bridge_port.assert_called_with(
|
|
'br-ex', 'cvb12345678-10', ifdata=None, exclusive=False,
|
|
portdata=None)
|
|
|
|
@patch('os.path.exists')
|
|
def test_is_linuxbridge_interface_false(self, exists):
|
|
exists.return_value = False
|
|
result = ovs.is_linuxbridge_interface('eno1')
|
|
self.assertFalse(result)
|
|
|
|
@patch('os.path.exists')
|
|
def test_is_linuxbridge_interface_true(self, exists):
|
|
exists.return_value = True
|
|
result = ovs.is_linuxbridge_interface('eno1')
|
|
self.assertTrue(result)
|
|
|
|
@patch('subprocess.check_call')
|
|
def test_set_manager(self, check_call):
|
|
ovs.set_manager('manager')
|
|
check_call.assert_called_with(['ovs-vsctl', 'set-manager',
|
|
'ssl:manager'])
|
|
self.assertTrue(self.log.call_count == 1)
|
|
|
|
@patch('subprocess.check_call')
|
|
def test_set_Open_vSwitch_column_value(self, check_call):
|
|
ovs.set_Open_vSwitch_column_value('other_config:foo=bar')
|
|
check_call.assert_called_with(['ovs-vsctl', 'set',
|
|
'Open_vSwitch', '.', 'other_config:foo=bar'])
|
|
self.assertTrue(self.log.call_count == 1)
|
|
|
|
@patch('os.path.exists')
|
|
def test_get_certificate_good_cert(self, exists):
|
|
exists.return_value = True
|
|
with patch_open() as (mock_open, mock_file):
|
|
mock_file.read.return_value = GOOD_CERT
|
|
self.assertEqual(ovs.get_certificate(), PEM_ENCODED)
|
|
self.assertTrue(self.log.call_count == 1)
|
|
|
|
@patch('os.path.exists')
|
|
def test_get_certificate_bad_cert(self, exists):
|
|
exists.return_value = True
|
|
with patch_open() as (mock_open, mock_file):
|
|
mock_file.read.return_value = BAD_CERT
|
|
self.assertRaises(RuntimeError, ovs.get_certificate)
|
|
self.assertTrue(self.log.call_count == 1)
|
|
|
|
@patch('os.path.exists')
|
|
def test_get_certificate_missing(self, exists):
|
|
exists.return_value = False
|
|
self.assertIsNone(ovs.get_certificate())
|
|
self.assertTrue(self.log.call_count == 1)
|
|
|
|
@patch('os.path.exists')
|
|
@patch.object(ovs, 'service')
|
|
def test_full_restart(self, service, exists):
|
|
exists.return_value = False
|
|
ovs.full_restart()
|
|
service.assert_called_with('force-reload-kmod', 'openvswitch-switch')
|
|
|
|
@patch('os.path.exists')
|
|
@patch.object(ovs, 'service')
|
|
def test_full_restart_upstart(self, service, exists):
|
|
exists.return_value = True
|
|
ovs.full_restart()
|
|
service.assert_called_with('start', 'openvswitch-force-reload-kmod')
|
|
|
|
@patch('subprocess.check_output')
|
|
def test_port_to_br(self, check_output):
|
|
check_output.return_value = b'br-ex'
|
|
self.assertEqual(ovs.port_to_br('br-lb'),
|
|
'br-ex')
|
|
|
|
@patch('subprocess.check_output')
|
|
def test_port_to_br_not_found(self, check_output):
|
|
check_output.side_effect = subprocess.CalledProcessError(1, 'not found')
|
|
self.assertEqual(ovs.port_to_br('br-lb'), None)
|
|
|
|
@patch('subprocess.check_call')
|
|
def test_enable_ipfix_defaults(self, check_call):
|
|
ovs.enable_ipfix('br-int',
|
|
'10.5.0.10:4739')
|
|
check_call.assert_called_once_with([
|
|
'ovs-vsctl', 'set', 'Bridge', 'br-int', 'ipfix=@i', '--',
|
|
'--id=@i', 'create', 'IPFIX',
|
|
'targets="10.5.0.10:4739"',
|
|
'sampling=64',
|
|
'cache_active_timeout=60',
|
|
'cache_max_flows=128',
|
|
])
|
|
|
|
@patch('subprocess.check_call')
|
|
def test_enable_ipfix_values(self, check_call):
|
|
ovs.enable_ipfix('br-int',
|
|
'10.5.0.10:4739',
|
|
sampling=120,
|
|
cache_max_flows=24,
|
|
cache_active_timeout=120)
|
|
check_call.assert_called_once_with([
|
|
'ovs-vsctl', 'set', 'Bridge', 'br-int', 'ipfix=@i', '--',
|
|
'--id=@i', 'create', 'IPFIX',
|
|
'targets="10.5.0.10:4739"',
|
|
'sampling=120',
|
|
'cache_active_timeout=120',
|
|
'cache_max_flows=24',
|
|
])
|
|
|
|
@patch('subprocess.check_call')
|
|
def test_disable_ipfix(self, check_call):
|
|
ovs.disable_ipfix('br-int')
|
|
check_call.assert_called_once_with(
|
|
['ovs-vsctl', 'clear', 'Bridge', 'br-int', 'ipfix']
|
|
)
|
|
|
|
@patch.object(ovs, 'lsb_release')
|
|
@patch('os.path.exists')
|
|
def test_setup_eni_sources_eni_folder(self, exists, lsb_release):
|
|
exists.return_value = True
|
|
lsb_release.return_value = {'DISTRIB_CODENAME': 'bionic'}
|
|
with patch_open() as (_, mock_file):
|
|
# Mocked initial /etc/network/interfaces file content:
|
|
mock_file.__iter__.return_value = [
|
|
'some line',
|
|
'some other line']
|
|
|
|
ovs.setup_eni()
|
|
mock_file.write.assert_called_once_with(
|
|
'\nsource /etc/network/interfaces.d/*')
|
|
|
|
@patch.object(ovs, 'lsb_release')
|
|
@patch('os.path.exists')
|
|
def test_setup_eni_wont_source_eni_folder_twice(self, exists, lsb_release):
|
|
exists.return_value = True
|
|
lsb_release.return_value = {'DISTRIB_CODENAME': 'bionic'}
|
|
with patch_open() as (_, mock_file):
|
|
# Mocked initial /etc/network/interfaces file content:
|
|
mock_file.__iter__.return_value = [
|
|
'some line',
|
|
' source /etc/network/interfaces.d/* ',
|
|
'some other line']
|
|
|
|
ovs.setup_eni()
|
|
self.assertFalse(mock_file.write.called)
|
|
|
|
@patch.object(ovs, 'lsb_release')
|
|
def test_setup_eni_raises_on_focal(self, lsb_release):
|
|
lsb_release.return_value = {'DISTRIB_CODENAME': 'focal'}
|
|
self.assertRaises(RuntimeError, ovs.setup_eni)
|