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:
Janonymous 2017-09-26 10:28:04 +00:00 committed by Michał Dulko
parent 62a3f72177
commit d688e6f6ec
6 changed files with 380 additions and 0 deletions

View File

@ -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())

View File

@ -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()

View File

@ -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)

View File

@ -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)

View File

@ -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()