diff --git a/neutron/agent/linux/utils.py b/neutron/agent/linux/utils.py index a69ad200fc4..1d2dde3a7e0 100644 --- a/neutron/agent/linux/utils.py +++ b/neutron/agent/linux/utils.py @@ -35,6 +35,7 @@ from oslo_utils import excutils from oslo_utils import fileutils import psutil +from neutron.common import utils from neutron.conf.agent import common as config from neutron.privileged.agent.linux import utils as priv_utils from neutron import wsgi @@ -400,6 +401,19 @@ def delete_if_exists(path, run_as_root=False): fileutils.delete_if_exists(path) +def read_if_exists(path: str, run_as_root=False) -> str: + """Return the content of a text file as a string + + The output includes the empty lines too. If the file does not exist, + returns an empty string. + It could be called with elevated permissions (root). + """ + if run_as_root: + return priv_utils.read_file(path) + else: + return utils.read_file(path) + + class UnixDomainHTTPConnection(httplib.HTTPConnection): """Connection class for HTTP over UNIX domain socket.""" def __init__(self, host, port=None, strict=None, timeout=None, diff --git a/neutron/agent/metadata/driver_base.py b/neutron/agent/metadata/driver_base.py index 8c8e8f46f72..6be9d129d39 100644 --- a/neutron/agent/metadata/driver_base.py +++ b/neutron/agent/metadata/driver_base.py @@ -46,7 +46,7 @@ listen listener """ -class HaproxyConfiguratorBase(object): +class HaproxyConfiguratorBase(object, metaclass=abc.ABCMeta): PROXY_CONFIG_DIR = None HEADER_CONFIG_TEMPLATE = None @@ -76,9 +76,27 @@ class HaproxyConfiguratorBase(object): # /var/log/haproxy.log on Debian distros, instead of to syslog. uuid = network_id or router_id self.log_tag = "haproxy-{}-{}".format(METADATA_SERVICE_NAME, uuid) + self._haproxy_cfg = '' + self._resource_id = None + self._create_config() - def create_config_file(self): - """Create the config file for haproxy.""" + @property + def haproxy_cfg(self) -> str: + return self._haproxy_cfg + + @property + def resource_id(self) -> str: + return self._resource_id + + def _create_config(self) -> None: + """Create the configuration for haproxy, stored locally + + This method creates a string with the HAProxy configuration, stored in + ``self._haproxy_cfg``. It also stores the resource ID (network, router) + in ``self._resource_id``. + + This method must be called once in the init method. + """ # Need to convert uid/gid into username/group try: username = pwd.getpwuid(int(self.user)).pw_name @@ -127,27 +145,49 @@ class HaproxyConfiguratorBase(object): cfg_info['res_type'] = 'Router' cfg_info['res_id'] = self.router_id cfg_info['res_type_del'] = 'Network' + self._resource_id = cfg_info['res_id'] + self._haproxy_cfg = comm_meta.get_haproxy_config( + cfg_info, self.rate_limiting_config, + self.HEADER_CONFIG_TEMPLATE, _UNLIMITED_CONFIG_TEMPLATE) - haproxy_cfg = comm_meta.get_haproxy_config(cfg_info, - self.rate_limiting_config, - self.HEADER_CONFIG_TEMPLATE, - _UNLIMITED_CONFIG_TEMPLATE) - - LOG.debug("haproxy_cfg = %s", haproxy_cfg) + def create_config_file(self): + """Read the configuration stored and write the configuration file""" + LOG.debug("haproxy_cfg = %s", self.haproxy_cfg) cfg_dir = self.get_config_path(self.state_path) # uuid has to be included somewhere in the command line so that it can # be tracked by process_monitor. - self.cfg_path = os.path.join(cfg_dir, "%s.conf" % cfg_info['res_id']) + self.cfg_path = os.path.join(cfg_dir, "%s.conf" % self.resource_id) if not os.path.exists(cfg_dir): os.makedirs(cfg_dir) with open(self.cfg_path, "w") as cfg_file: - cfg_file.write(haproxy_cfg) + cfg_file.write(self.haproxy_cfg) @classmethod def get_config_path(cls, state_path): return os.path.join(state_path or cfg.CONF.state_path, cls.PROXY_CONFIG_DIR) + def read_config_file(self) -> str: + """Return a string with the content of the configuration file""" + cfg_path = os.path.join(self.get_config_path(self.state_path), + '%s.conf' % self.resource_id) + return linux_utils.read_if_exists(str(cfg_path), run_as_root=True) + + def is_config_file_obsolete(self) -> bool: + """Compare the instance config and the config file content + + Returns False if both configurations match. This check skips the + "pidfile" line because that is provided just before the process is + started. + """ + def trim_config(haproxy_cfg: str) -> list[str]: + return [line for line in haproxy_cfg.split('\n') + if not line.lstrip().startswith('pidfile')] + + file_config = trim_config(self.read_config_file()) + current_config = trim_config(self.haproxy_cfg) + return file_config != current_config + @classmethod def cleanup_config_file(cls, uuid, state_path): """Delete config file created when metadata proxy was spawned.""" @@ -174,31 +214,39 @@ class MetadataDriverBase(object, metaclass=abc.ABCMeta): return user, group + @classmethod + def _get_haproxy_configurator(cls, bind_address, port, conf, + network_id=None, router_id=None, + bind_address_v6=None, + bind_interface=None, + pid_file=''): + metadata_proxy_socket = conf.metadata_proxy_socket + user, group = cls._get_metadata_proxy_user_group(conf) + configurator = cls.haproxy_configurator() + return configurator(network_id, + router_id, + metadata_proxy_socket, + bind_address, + port, + user, + group, + conf.state_path, + pid_file, + conf.metadata_rate_limiting, + bind_address_v6, + bind_interface) + @classmethod def _get_metadata_proxy_callback(cls, bind_address, port, conf, network_id=None, router_id=None, bind_address_v6=None, bind_interface=None): def callback(pid_file): - metadata_proxy_socket = conf.metadata_proxy_socket - user, group = cls._get_metadata_proxy_user_group(conf) - configurator = cls.haproxy_configurator() - haproxy = configurator(network_id, - router_id, - metadata_proxy_socket, - bind_address, - port, - user, - group, - conf.state_path, - pid_file, - conf.metadata_rate_limiting, - bind_address_v6, - bind_interface) + haproxy = cls._get_haproxy_configurator( + bind_address, port, conf, network_id, router_id, + bind_address_v6, bind_interface, pid_file) haproxy.create_config_file() - proxy_cmd = [HAPROXY_SERVICE, '-f', haproxy.cfg_path] - - return proxy_cmd + return [HAPROXY_SERVICE, '-f', haproxy.cfg_path] return callback @@ -238,6 +286,18 @@ class MetadataDriverBase(object, metaclass=abc.ABCMeta): # Do not use the address or interface when DAD fails bind_address_v6 = bind_interface = None + # If the HAProxy running instance configuration is different from + # the one passed in this call, the HAProxy is stopped. The new + # configuration will be written to the disk and a new instance + # started. + haproxy_cfg = cls._get_haproxy_configurator( + bind_address, port, conf, network_id=network_id, + router_id=router_id, bind_address_v6=bind_address_v6, + bind_interface=bind_interface) + if haproxy_cfg.is_config_file_obsolete(): + cls.destroy_monitored_metadata_proxy( + monitor, haproxy_cfg.resource_id, conf, ns_name) + uuid = network_id or router_id callback = cls._get_metadata_proxy_callback( bind_address, port, conf, diff --git a/neutron/agent/ovn/metadata/agent.py b/neutron/agent/ovn/metadata/agent.py index ba9589951a0..de48c64ef97 100644 --- a/neutron/agent/ovn/metadata/agent.py +++ b/neutron/agent/ovn/metadata/agent.py @@ -378,9 +378,6 @@ class MetadataAgent(object): resource_type='metadata') self._sb_idl = None self._post_fork_event = threading.Event() - # We'll restart all haproxy instances upon start so that they honor - # any potential changes in their configuration. - self.restarted_metadata_proxy_set = set() self._chassis = None @property @@ -834,11 +831,6 @@ class MetadataAgent(object): # Ensure the correct checksum in the metadata traffic. self._ensure_datapath_checksum(namespace) - if net_name not in self.restarted_metadata_proxy_set: - metadata_driver.MetadataDriver.destroy_monitored_metadata_proxy( - self._process_monitor, net_name, self.conf, namespace) - self.restarted_metadata_proxy_set.add(net_name) - # Spawn metadata proxy if it's not already running. metadata_driver.MetadataDriver.spawn_monitored_metadata_proxy( self._process_monitor, namespace, n_const.METADATA_PORT, diff --git a/neutron/common/utils.py b/neutron/common/utils.py index 3ac808ba5aa..6f32788f5a4 100644 --- a/neutron/common/utils.py +++ b/neutron/common/utils.py @@ -1103,3 +1103,16 @@ def parse_permitted_ethertypes(permitted_ethertypes): continue return ret + + +def read_file(path: str) -> str: + """Return the content of a text file as a string + + The output includes the empty lines too. If the file does not exist, + returns an empty string. + """ + try: + with open(path) as file: + return file.read() + except FileNotFoundError: + return '' diff --git a/neutron/privileged/agent/linux/utils.py b/neutron/privileged/agent/linux/utils.py index 1839103d1f4..33268494414 100644 --- a/neutron/privileged/agent/linux/utils.py +++ b/neutron/privileged/agent/linux/utils.py @@ -15,12 +15,14 @@ import os from os import path import re +import typing from eventlet.green import subprocess from neutron_lib.utils import helpers from oslo_concurrency import processutils from oslo_utils import fileutils +from neutron.common import utils from neutron import privileged @@ -52,6 +54,20 @@ def delete_if_exists(_path, remove=os.unlink): fileutils.delete_if_exists(_path, remove=remove) +@privileged.default.entrypoint +def read_file(_path: str) -> str: + return utils.read_file(_path) + + +@privileged.default.entrypoint +def write_to_tempfile(content: bytes, + _path: typing.Optional[str] = None, + suffix: str = '', + prefix: str = 'tmp'): + return fileutils.write_to_tempfile(content, path=_path, suffix=suffix, + prefix=prefix) + + @privileged.default.entrypoint def execute_process(cmd, _process_input, addl_env): obj, cmd = _create_process(cmd, addl_env=addl_env) diff --git a/neutron/tests/functional/agent/linux/test_utils.py b/neutron/tests/functional/agent/linux/test_utils.py index b65233efd58..2ce5891fc13 100644 --- a/neutron/tests/functional/agent/linux/test_utils.py +++ b/neutron/tests/functional/agent/linux/test_utils.py @@ -15,10 +15,15 @@ import functools import os import signal +import tempfile + +from oslo_utils import fileutils +import testscenarios from neutron.agent.common import async_process from neutron.agent.linux import utils from neutron.common import utils as common_utils +from neutron.privileged.agent.linux import utils as priv_utils from neutron.tests.functional.agent.linux import test_async_process from neutron.tests.functional import base as functional_base @@ -172,3 +177,39 @@ class TestFindChildPids(functional_base.BaseSudoTestCase): with open('/proc/sys/kernel/pid_max', 'r') as fd: pid_max = int(fd.readline().strip()) self.assertEqual([], utils.find_child_pids(pid_max)) + + +class ReadIfExists(testscenarios.WithScenarios, + functional_base.BaseSudoTestCase): + scenarios = [ + ('root', {'run_as_root': True}), + ('non-root', {'run_as_root': False})] + + FILE = """Test file +line 2 + +line 4 + +""" + + @classmethod + def _write_file(cls, path='/tmp', run_as_root=False): + content = cls.FILE.encode('ascii') + if run_as_root: + return priv_utils.write_to_tempfile(content, _path=path) + else: + return fileutils.write_to_tempfile(content, path=path) + + def test_read_if_exists(self): + test_file_path = self._write_file(run_as_root=self.run_as_root) + content = utils.read_if_exists(test_file_path, + run_as_root=self.run_as_root) + file = self.FILE + self.assertEqual(file, content) + + def test_read_if_exists_no_file(self): + temp_dir = tempfile.TemporaryDirectory() + content = utils.read_if_exists( + os.path.join(temp_dir.name, 'non-existing-file'), + run_as_root=self.run_as_root) + self.assertEqual('', content) diff --git a/neutron/tests/unit/agent/dhcp/test_agent.py b/neutron/tests/unit/agent/dhcp/test_agent.py index d53cb5cbd8b..f7aede9b873 100644 --- a/neutron/tests/unit/agent/dhcp/test_agent.py +++ b/neutron/tests/unit/agent/dhcp/test_agent.py @@ -37,6 +37,7 @@ from neutron.agent.linux import dhcp from neutron.agent.linux import interface from neutron.agent.linux import utils as linux_utils from neutron.agent.metadata import driver as metadata_driver +from neutron.agent.metadata import driver_base as metadata_driver_base from neutron.common import config as common_config from neutron.common.ovn import constants as ovn_const from neutron.common import utils @@ -841,6 +842,9 @@ class TestDhcpAgentEventHandler(base.BaseTestCase): mock.call(FAKE_NETWORK_UUID, cfg.CONF, ns_name=FAKE_NETWORK_DHCP_NS, callback=mock.ANY)) + mock.patch.object(metadata_driver_base.HaproxyConfiguratorBase, + 'is_config_file_obsolete', + return_value=False).start() self.plugin.get_network_info.return_value = network process_instance = mock.Mock(active=False) with mock.patch.object(metadata_driver.MetadataDriver, @@ -1036,7 +1040,9 @@ class TestDhcpAgentEventHandler(base.BaseTestCase): process_instance = mock.Mock(active=False) with mock.patch.object(metadata_driver.MetadataDriver, '_get_metadata_proxy_process_manager', - return_value=process_instance) as gmppm: + return_value=process_instance) as gmppm,\ + mock.patch.object(metadata_driver_base.MetadataDriverBase, + '_get_haproxy_configurator'): self.dhcp.enable_isolated_metadata_proxy(fake_network) gmppm.assert_called_with(FAKE_NETWORK_UUID, cfg.CONF, @@ -1135,7 +1141,9 @@ class TestDhcpAgentEventHandler(base.BaseTestCase): network.ports = [dhcp_port_this_host, dhcp_port_other_host] self._test_enable_isolated_metadata_proxy_ipv6(network) - def _test_disable_isolated_metadata_proxy(self, network): + @mock.patch.object(metadata_driver_base.HaproxyConfiguratorBase, + 'is_config_file_obsolete', return_value=False) + def _test_disable_isolated_metadata_proxy(self, network, *args): cfg.CONF.set_override('enable_metadata_network', True) method_path = ('neutron.agent.metadata.driver.MetadataDriver' '.destroy_monitored_metadata_proxy') diff --git a/neutron/tests/unit/agent/l3/test_agent.py b/neutron/tests/unit/agent/l3/test_agent.py index 0d51040a86e..b53014a4093 100644 --- a/neutron/tests/unit/agent/l3/test_agent.py +++ b/neutron/tests/unit/agent/l3/test_agent.py @@ -58,6 +58,7 @@ from neutron.agent.linux import pd from neutron.agent.linux import ra from neutron.agent.linux import utils as linux_utils from neutron.agent.metadata import driver as metadata_driver +from neutron.agent.metadata import driver_base as metadata_driver_base from neutron.agent import rpc as agent_rpc from neutron.conf.agent import common as agent_config from neutron.conf.agent.l3 import config as l3_config @@ -298,7 +299,9 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): eventlet.sleep(self.conf.ha_vrrp_advert_int + 2) self.assertFalse(agent._update_metadata_proxy.call_count) - def test_enqueue_state_change_l3_extension(self): + @mock.patch.object(metadata_driver_base.MetadataDriverBase, + '_get_haproxy_configurator') + def test_enqueue_state_change_l3_extension(self, mock_haproxy_conf): self.conf.set_override('ha_vrrp_advert_int', 1) agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) router_dict = {'id': 'router_id', 'enable_ndp_proxy': True} @@ -307,6 +310,9 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): router_info.router = router_dict agent.router_info['router_id'] = router_info agent.l3_ext_manager.ha_state_change = mock.Mock() + haproxy_cfg = mock.Mock() + haproxy_cfg.is_config_file_obsolete.return_value = False + mock_haproxy_conf.return_value = haproxy_cfg with mock.patch('neutron.agent.linux.ip_lib.' 'IpAddrCommand.wait_until_address_ready') as mock_wait: mock_wait.return_value = True diff --git a/neutron/tests/unit/agent/metadata/test_driver.py b/neutron/tests/unit/agent/metadata/test_driver.py index 0fe5ba44ffe..cf9606888c6 100644 --- a/neutron/tests/unit/agent/metadata/test_driver.py +++ b/neutron/tests/unit/agent/metadata/test_driver.py @@ -112,6 +112,9 @@ class TestMetadataDriverProcess(base.BaseTestCase): meta_conf.register_meta_conf_opts( meta_conf.METADATA_RATE_LIMITING_OPTS, cfg.CONF, group=meta_conf.RATE_LIMITING_GROUP) + self.mock_conf_obsolete = mock.patch.object( + driver_base.HaproxyConfiguratorBase, + 'is_config_file_obsolete').start() def test_after_router_updated_called_on_agent_process_update(self): with mock.patch.object(metadata_driver, 'after_router_updated') as f,\ @@ -153,7 +156,8 @@ class TestMetadataDriverProcess(base.BaseTestCase): agent._process_updated_router(router) f.assert_not_called() - def _test_spawn_metadata_proxy(self, dad_failed=False, rate_limited=False): + def _test_spawn_metadata_proxy(self, dad_failed=False, rate_limited=False, + is_config_file_obsolete=False): router_id = _uuid() router_ns = 'qrouter-%s' % router_id service_name = 'haproxy' @@ -163,6 +167,11 @@ class TestMetadataDriverProcess(base.BaseTestCase): cfg.CONF.set_override('metadata_proxy_group', self.EGNAME) cfg.CONF.set_override('metadata_proxy_socket', self.METADATA_SOCKET) cfg.CONF.set_override('debug', True) + self.mock_conf_obsolete.return_value = is_config_file_obsolete + if is_config_file_obsolete: + self.mock_destroy_haproxy = mock.patch.object( + driver_base.MetadataDriverBase, + 'destroy_monitored_metadata_proxy').start() with mock.patch(ip_class_path) as ip_mock,\ mock.patch( @@ -274,6 +283,10 @@ class TestMetadataDriverProcess(base.BaseTestCase): self.delete_if_exists.assert_called_once_with( mock.ANY, run_as_root=True) + if is_config_file_obsolete: + self.mock_destroy_haproxy.assert_called_once_with( + agent.process_monitor, router_id, agent.conf, router_ns) + def test_spawn_metadata_proxy(self): self._test_spawn_metadata_proxy() @@ -294,6 +307,9 @@ class TestMetadataDriverProcess(base.BaseTestCase): def test_spawn_metadata_proxy_dad_failed(self): self._test_spawn_metadata_proxy(dad_failed=True) + def test_spawn_metadata_proxy_no_matching_configurations(self): + self._test_spawn_metadata_proxy(is_config_file_obsolete=True) + @mock.patch.object(driver_base.LOG, 'error') def test_spawn_metadata_proxy_handles_process_exception(self, error_log): process_instance = mock.Mock(active=False) @@ -316,29 +332,21 @@ class TestMetadataDriverProcess(base.BaseTestCase): def test_create_config_file_wrong_user(self): with mock.patch('pwd.getpwnam', side_effect=KeyError): - config = metadata_driver.HaproxyConfigurator(_uuid(), - mock.ANY, mock.ANY, - mock.ANY, mock.ANY, - self.EUNAME, - self.EGNAME, - mock.ANY, mock.ANY, - mock.ANY) self.assertRaises(comm_meta.InvalidUserOrGroupException, - config.create_config_file) + metadata_driver.HaproxyConfigurator, _uuid(), + mock.ANY, mock.ANY, mock.ANY, mock.ANY, + self.EUNAME, self.EGNAME, mock.ANY, mock.ANY, + mock.ANY) def test_create_config_file_wrong_group(self): with mock.patch('grp.getgrnam', side_effect=KeyError),\ mock.patch('pwd.getpwnam', return_value=test_utils.FakeUser(self.EUNAME)): - config = metadata_driver.HaproxyConfigurator(_uuid(), - mock.ANY, mock.ANY, - mock.ANY, mock.ANY, - self.EUNAME, - self.EGNAME, - mock.ANY, mock.ANY, - mock.ANY) self.assertRaises(comm_meta.InvalidUserOrGroupException, - config.create_config_file) + metadata_driver.HaproxyConfigurator, _uuid(), + mock.ANY, mock.ANY, mock.ANY, mock.ANY, + self.EUNAME, self.EGNAME, mock.ANY, mock.ANY, + mock.ANY) def test_destroy_monitored_metadata_proxy(self): mproxy_process = mock.Mock(active=False) diff --git a/neutron/tests/unit/agent/ovn/metadata/test_agent.py b/neutron/tests/unit/agent/ovn/metadata/test_agent.py index 296ff640465..61f3f8c2b7a 100644 --- a/neutron/tests/unit/agent/ovn/metadata/test_agent.py +++ b/neutron/tests/unit/agent/ovn/metadata/test_agent.py @@ -446,8 +446,7 @@ class TestMetadataAgent(base.BaseTestCase): ip_wrap, 'add_veth', return_value=[ip_lib.IPDevice('ip1'), ip_lib.IPDevice('ip2')]) as add_veth,\ - mock.patch.object( - linux_utils, 'delete_if_exists') as mock_delete,\ + mock.patch.object(linux_utils, 'delete_if_exists'), \ mock.patch.object( driver.MetadataDriver, 'spawn_monitored_metadata_proxy') as spawn_mdp, \ @@ -488,7 +487,6 @@ class TestMetadataAgent(base.BaseTestCase): self.assertCountEqual(expected_call, ip_addr_add_multiple.call_args.args[0]) # Check that metadata proxy has been spawned - mock_delete.assert_called_once_with(mock.ANY, run_as_root=True) spawn_mdp.assert_called_once_with( mock.ANY, nemaspace_name, 80, mock.ANY, bind_address=n_const.METADATA_V4_IP, network_id=net_name, diff --git a/neutron/tests/unit/agent/ovn/metadata/test_driver.py b/neutron/tests/unit/agent/ovn/metadata/test_driver.py index 263c8daca32..c52fdd65a98 100644 --- a/neutron/tests/unit/agent/ovn/metadata/test_driver.py +++ b/neutron/tests/unit/agent/ovn/metadata/test_driver.py @@ -105,7 +105,10 @@ class TestMetadataDriverProcess(base.BaseTestCase): mock.patch( 'neutron.agent.linux.ip_lib.' 'IpAddrCommand.wait_until_address_ready', - return_value=True): + return_value=True),\ + mock.patch.object(driver_base.HaproxyConfiguratorBase, + 'is_config_file_obsolete', + return_value=False): cfg_file = os.path.join( metadata_driver.HaproxyConfigurator.get_config_path( agent.conf.state_path), @@ -184,7 +187,9 @@ class TestMetadataDriverProcess(base.BaseTestCase): with mock.patch.object(metadata_driver.MetadataDriver, '_get_metadata_proxy_process_manager', - return_value=process_instance): + return_value=process_instance),\ + mock.patch.object(driver_base.MetadataDriverBase, + '_get_haproxy_configurator'): process_monitor = mock.Mock() network_id = 123456 @@ -201,22 +206,18 @@ class TestMetadataDriverProcess(base.BaseTestCase): def test_create_config_file_wrong_user(self): with mock.patch('pwd.getpwnam', side_effect=KeyError): - config = metadata_driver.HaproxyConfigurator(mock.ANY, mock.ANY, - mock.ANY, mock.ANY, - mock.ANY, self.EUNAME, - self.EGNAME, mock.ANY, - mock.ANY, mock.ANY) self.assertRaises(comm_meta.InvalidUserOrGroupException, - config.create_config_file) + metadata_driver.HaproxyConfigurator, mock.ANY, + mock.ANY, mock.ANY, mock.ANY, mock.ANY, + self.EUNAME, self.EGNAME, mock.ANY, mock.ANY, + mock.ANY) def test_create_config_file_wrong_group(self): with mock.patch('grp.getgrnam', side_effect=KeyError),\ mock.patch('pwd.getpwnam', return_value=test_utils.FakeUser(self.EUNAME)): - config = metadata_driver.HaproxyConfigurator(mock.ANY, mock.ANY, - mock.ANY, mock.ANY, - mock.ANY, self.EUNAME, - self.EGNAME, mock.ANY, - mock.ANY, mock.ANY) self.assertRaises(comm_meta.InvalidUserOrGroupException, - config.create_config_file) + metadata_driver.HaproxyConfigurator, mock.ANY, + mock.ANY, mock.ANY, mock.ANY, mock.ANY, + self.EUNAME, self.EGNAME, mock.ANY, mock.ANY, + mock.ANY)