CNI daemon unit tests
This patch adds unit tests related to CNI daemon and CNI in general (turns out some tests were missing). I think for the sake of simplicity of modifying the base patch this should get squashed into the base. Co-Authored-By: Michał Dulko <mdulko@redhat.com> Implements: blueprint cni-split-exec-daemon Change-Id: I6215d3b0fed4195f02b2805f0757e9df0da21b5b
This commit is contained in:
parent
62a3f72177
commit
d688e6f6ec
|
@ -0,0 +1,39 @@
|
|||
# Copyright (c) 2017 Red Hat.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import uuid
|
||||
|
||||
from os_vif import objects as osv_objects
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
|
||||
def _fake_vif():
|
||||
vif = osv_objects.vif.VIFOpenVSwitch(
|
||||
id=uuid.uuid4())
|
||||
vif.network = osv_objects.network.Network(id=uuid.uuid4())
|
||||
subnet = osv_objects.subnet.Subnet(
|
||||
uuid=uuid.uuid4(), dns=['192.168.0.1'], cidr='192.168.0.0/24',
|
||||
gateway='192.168.0.1',
|
||||
routes=osv_objects.route.RouteList(objects=[]))
|
||||
subnet.ips = osv_objects.fixed_ip.FixedIPList(objects=[])
|
||||
subnet.ips.objects.append(
|
||||
osv_objects.fixed_ip.FixedIP(address='192.168.0.2'))
|
||||
vif.network.subnets.objects.append(subnet)
|
||||
|
||||
return vif
|
||||
|
||||
|
||||
def _fake_vif_string():
|
||||
return jsonutils.dumps(_fake_vif().obj_to_primitive())
|
|
@ -0,0 +1,27 @@
|
|||
# Copyright (c) 2017 NEC Corporation.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
|
||||
from kuryr_kubernetes.cmd import daemon
|
||||
from kuryr_kubernetes.tests import base as test_base
|
||||
|
||||
|
||||
class TestCniCmd(test_base.TestCase):
|
||||
|
||||
@mock.patch('kuryr_kubernetes.cmd.daemon.run_daemon')
|
||||
def test_start(self, m_daemon):
|
||||
daemon.run_daemon()
|
||||
m_daemon.assert_called()
|
|
@ -0,0 +1,124 @@
|
|||
# Copyright (c) 2017 NEC Corporation.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
from six import StringIO
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from kuryr_kubernetes.cni import api
|
||||
from kuryr_kubernetes.cni import cni_daemon
|
||||
from kuryr_kubernetes.tests import base as test_base
|
||||
from kuryr_kubernetes.tests import fake
|
||||
|
||||
|
||||
class TestCNIRunnerMixin(object):
|
||||
def test_run_invalid(self, *args):
|
||||
m_fin = StringIO()
|
||||
m_fout = StringIO()
|
||||
code = self.runner.run(
|
||||
{'CNI_COMMAND': 'INVALID', 'CNI_ARGS': 'foo=bar'}, m_fin, m_fout)
|
||||
|
||||
self.assertEqual(1, code)
|
||||
|
||||
def test_run_write_version(self, *args):
|
||||
m_fin = StringIO()
|
||||
m_fout = StringIO()
|
||||
code = self.runner.run(
|
||||
{'CNI_COMMAND': 'VERSION', 'CNI_ARGS': 'foo=bar'}, m_fin, m_fout)
|
||||
result = jsonutils.loads(m_fout.getvalue())
|
||||
|
||||
self.assertEqual(0, code)
|
||||
self.assertEqual(api.CNIRunner.SUPPORTED_VERSIONS,
|
||||
result['supportedVersions'])
|
||||
self.assertEqual(api.CNIRunner.VERSION, result['cniVersion'])
|
||||
|
||||
|
||||
class TestCNIStandaloneRunner(test_base.TestCase, TestCNIRunnerMixin):
|
||||
def setUp(self):
|
||||
super(TestCNIStandaloneRunner, self).setUp()
|
||||
self.runner = api.CNIStandaloneRunner(cni_daemon.K8sCNIPlugin())
|
||||
|
||||
@mock.patch('kuryr_kubernetes.cni.cni_daemon.K8sCNIPlugin.add')
|
||||
def test_run_add(self, m_k8s_add):
|
||||
vif = fake._fake_vif()
|
||||
m_k8s_add.return_value = vif
|
||||
m_fin = StringIO()
|
||||
m_fout = StringIO()
|
||||
env = {
|
||||
'CNI_COMMAND': 'ADD',
|
||||
'CNI_ARGS': 'foo=bar',
|
||||
}
|
||||
self.runner.run(env, m_fin, m_fout)
|
||||
self.assertTrue(m_k8s_add.called)
|
||||
self.assertEqual('foo=bar', m_k8s_add.call_args[0][0].CNI_ARGS)
|
||||
result = jsonutils.loads(m_fout.getvalue())
|
||||
self.assertDictEqual(
|
||||
{"cniVersion": "0.3.0",
|
||||
"dns": {"nameservers": ["192.168.0.1"]},
|
||||
"ip4": {"gateway": "192.168.0.1", "ip": "192.168.0.2/24"}},
|
||||
result)
|
||||
|
||||
@mock.patch('kuryr_kubernetes.cni.cni_daemon.K8sCNIPlugin.delete')
|
||||
def test_run_del(self, m_k8s_delete):
|
||||
vif = fake._fake_vif()
|
||||
m_k8s_delete.return_value = vif
|
||||
m_fin = StringIO()
|
||||
m_fout = StringIO()
|
||||
env = {
|
||||
'CNI_COMMAND': 'DEL',
|
||||
'CNI_ARGS': 'foo=bar',
|
||||
}
|
||||
self.runner.run(env, m_fin, m_fout)
|
||||
self.assertTrue(m_k8s_delete.called)
|
||||
self.assertEqual('foo=bar', m_k8s_delete.call_args[0][0].CNI_ARGS)
|
||||
|
||||
|
||||
@mock.patch('socket.socket')
|
||||
@mock.patch('kuryr_kubernetes.cni.api.UnixDomainHttpConnection.request')
|
||||
@mock.patch('kuryr_kubernetes.cni.api.UnixDomainHttpConnection.getresponse')
|
||||
class TestCNIDaemonizedRunner(test_base.TestCase, TestCNIRunnerMixin):
|
||||
def setUp(self):
|
||||
super(TestCNIDaemonizedRunner, self).setUp()
|
||||
self.runner = api.CNIDaemonizedRunner()
|
||||
|
||||
def _test_run(self, cni_cmd, path, m_getresponse, m_request, m_socket):
|
||||
m_fin = StringIO()
|
||||
m_fout = StringIO()
|
||||
env = {
|
||||
'CNI_COMMAND': cni_cmd,
|
||||
'CNI_ARGS': 'foo=bar',
|
||||
}
|
||||
result = self.runner.run(env, m_fin, m_fout)
|
||||
m_request.assert_called_with('POST', 'http://localhost/%s' % path,
|
||||
body=mock.ANY, headers=mock.ANY)
|
||||
body = m_request.call_args[1]['body']
|
||||
length = m_request.call_args[1]['headers']['Content-Length']
|
||||
self.assertEqual(len(body), length)
|
||||
return result
|
||||
|
||||
def test_run_add(self, m_getresponse, m_request, m_socket):
|
||||
m_response = mock.Mock(status=201)
|
||||
m_response.read = mock.Mock(return_value=fake._fake_vif_string())
|
||||
m_getresponse.return_value = m_response
|
||||
result = self._test_run('ADD', 'addNetwork', m_getresponse, m_request,
|
||||
m_socket)
|
||||
self.assertEqual(0, result)
|
||||
|
||||
def test_run_del(self, m_getresponse, m_request, m_socket):
|
||||
m_getresponse.return_value = mock.Mock(status=204)
|
||||
result = self._test_run('DEL', 'delNetwork', m_getresponse, m_request,
|
||||
m_socket)
|
||||
self.assertEqual(0, result)
|
|
@ -0,0 +1,115 @@
|
|||
# Copyright (c) 2017 NEC Corporation.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 io import BytesIO as IO
|
||||
import mock
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from kuryr_kubernetes.cni import cni_daemon
|
||||
from kuryr_kubernetes import constants
|
||||
from kuryr_kubernetes.tests import base as test_base
|
||||
from kuryr_kubernetes.tests import fake
|
||||
|
||||
|
||||
class TestK8sCNIPlugin(test_base.TestCase):
|
||||
@mock.patch('kuryr_kubernetes.watcher.Watcher')
|
||||
@mock.patch('kuryr_kubernetes.cni.handlers.CNIPipeline')
|
||||
@mock.patch('kuryr_kubernetes.cni.handlers.DelHandler')
|
||||
@mock.patch('kuryr_kubernetes.cni.handlers.AddHandler')
|
||||
def _test_method(self, method, m_add_handler, m_del_handler, m_cni_pipe,
|
||||
m_watcher_class):
|
||||
self.passed_handler = None
|
||||
|
||||
def _save_handler(params, handler):
|
||||
self.passed_handler = handler
|
||||
|
||||
def _call_handler(*args):
|
||||
self.passed_handler(mock.sentinel.vif)
|
||||
|
||||
m_add_handler.side_effect = _save_handler
|
||||
m_del_handler.side_effect = _save_handler
|
||||
|
||||
m_watcher = mock.MagicMock(
|
||||
add=mock.MagicMock(),
|
||||
start=mock.MagicMock(side_effect=_call_handler))
|
||||
m_watcher_class.return_value = m_watcher
|
||||
|
||||
m_params = mock.MagicMock()
|
||||
m_params.args.K8S_POD_NAMESPACE = 'k8s_pod_namespace'
|
||||
m_params.args.K8S_POD_NAME = 'k8s_pod'
|
||||
|
||||
cni_plugin = cni_daemon.K8sCNIPlugin()
|
||||
result = getattr(cni_plugin, method)(m_params)
|
||||
self.assertEqual(mock.sentinel.vif, cni_plugin._vif)
|
||||
m_watcher.add.assert_called_with(
|
||||
"%(base)s/namespaces/%(namespace)s/pods"
|
||||
"?fieldSelector=metadata.name=%(pod)s" % {
|
||||
'base': constants.K8S_API_BASE,
|
||||
'namespace': m_params.args.K8S_POD_NAMESPACE,
|
||||
'pod': m_params.args.K8S_POD_NAME})
|
||||
|
||||
return result
|
||||
|
||||
def test_add(self):
|
||||
result = self._test_method('add')
|
||||
self.assertEqual(result, mock.sentinel.vif)
|
||||
|
||||
def test_delete(self):
|
||||
self._test_method('delete')
|
||||
|
||||
|
||||
class TestRequestHandler(test_base.TestCase):
|
||||
path = None
|
||||
params = {'config_kuryr': {}, 'CNI_ARGS': 'foo=bar'}
|
||||
|
||||
@staticmethod
|
||||
def makefile(self, *args, **kwargs):
|
||||
params_str = jsonutils.dumps(TestRequestHandler.params)
|
||||
body = ('POST /%(path)s HTTP/1.1\n'
|
||||
'Content-Length: %(params_len)d\n\n'
|
||||
'%(params)s' % {'path': TestRequestHandler.path,
|
||||
'params_len': len(params_str),
|
||||
'params': params_str})
|
||||
# noop in py27, required in py3
|
||||
body = body.encode()
|
||||
return IO(body)
|
||||
|
||||
class MockServer(object):
|
||||
def __init__(self, ip_port, handler_class):
|
||||
mock_request = mock.MagicMock
|
||||
mock_request.makefile = mock.MagicMock(
|
||||
side_effect=TestRequestHandler.makefile)
|
||||
mock_request.sendall = mock.MagicMock()
|
||||
handler_class(mock_request, ip_port, self)
|
||||
|
||||
def _test_path(self, path, mock_method):
|
||||
mock_method.return_value = fake._fake_vif()
|
||||
|
||||
TestRequestHandler.path = path
|
||||
|
||||
TestRequestHandler.MockServer(('0.0.0.0', 8888),
|
||||
cni_daemon.RequestHandler)
|
||||
self.assertTrue(mock_method.called)
|
||||
self.assertEqual(TestRequestHandler.params['CNI_ARGS'],
|
||||
mock_method.call_args[0][0].CNI_ARGS)
|
||||
|
||||
@mock.patch('kuryr_kubernetes.cni.cni_daemon.K8sCNIPlugin.add')
|
||||
def test_addNetwork(self, m_add):
|
||||
self._test_path('addNetwork', m_add)
|
||||
|
||||
@mock.patch('kuryr_kubernetes.cni.cni_daemon.K8sCNIPlugin.delete')
|
||||
def test_delNetwork(self, m_delete):
|
||||
self._test_path('delNetwork', m_delete)
|
|
@ -0,0 +1,75 @@
|
|||
# Copyright (c) 2017 NEC Corporation.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
|
||||
from kuryr_kubernetes.cni import main
|
||||
from kuryr_kubernetes.tests import base as test_base
|
||||
|
||||
|
||||
class TestCNIMain(test_base.TestCase):
|
||||
@mock.patch('kuryr_kubernetes.cni.main.jsonutils.load')
|
||||
@mock.patch('sys.exit')
|
||||
@mock.patch('sys.stdin')
|
||||
@mock.patch('kuryr_kubernetes.cni.utils.CNIConfig')
|
||||
@mock.patch('kuryr_kubernetes.cni.api')
|
||||
@mock.patch('kuryr_kubernetes.config.CONF')
|
||||
@mock.patch('kuryr_kubernetes.config.init')
|
||||
@mock.patch('kuryr_kubernetes.config.setup_logging')
|
||||
@mock.patch('kuryr_kubernetes.cni.api.CNIDaemonizedRunner')
|
||||
def test_daemonized_run(
|
||||
self, m_cni_dr, m_setup_logging,
|
||||
m_config_init, m_config, m_api, m_conf, m_sys,
|
||||
m_sysexit, m_json):
|
||||
m_conf.debug = mock.Mock()
|
||||
m_conf.debug.return_value = True
|
||||
m_cni_dr.return_value = mock.MagicMock()
|
||||
m_cni_daemon = m_cni_dr.return_value
|
||||
|
||||
m_config.cni_daemon = True
|
||||
|
||||
main.run()
|
||||
|
||||
m_config_init.assert_called()
|
||||
m_setup_logging.assert_called()
|
||||
m_cni_daemon.run.assert_called()
|
||||
m_sysexit.assert_called()
|
||||
|
||||
@mock.patch('kuryr_kubernetes.cni.main.jsonutils.load')
|
||||
@mock.patch('sys.exit')
|
||||
@mock.patch('sys.stdin')
|
||||
@mock.patch('kuryr_kubernetes.cni.utils.CNIConfig')
|
||||
@mock.patch('kuryr_kubernetes.cni.api')
|
||||
@mock.patch('kuryr_kubernetes.config.CONF')
|
||||
@mock.patch('kuryr_kubernetes.config.init')
|
||||
@mock.patch('kuryr_kubernetes.config.setup_logging')
|
||||
@mock.patch('kuryr_kubernetes.cni.api.CNIStandaloneRunner')
|
||||
def test_standalone_run(
|
||||
self, m_cni_sr, m_setup_logging,
|
||||
m_config_init, m_config, m_api, m_conf, m_sys,
|
||||
m_sysexit, m_json):
|
||||
m_conf.debug = mock.Mock()
|
||||
m_conf.debug.return_value = True
|
||||
m_cni_sr.return_value = mock.MagicMock()
|
||||
m_cni_daemon = m_cni_sr.return_value
|
||||
|
||||
m_config.cni_daemon = False
|
||||
|
||||
main.run()
|
||||
|
||||
m_config_init.assert_called()
|
||||
m_setup_logging.assert_called()
|
||||
m_cni_daemon.run.assert_called()
|
||||
m_sysexit.assert_called()
|
Loading…
Reference in New Issue