# 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 oslo_utils import uuidutils from octavia.amphorae.backends.agent.api_server import util from octavia.amphorae.backends.utils import keepalivedlvs_query as lvs_query from octavia.common import constants from octavia.tests.common import utils as test_utils from octavia.tests.unit import base # Kernal_file_sample which is in /proc/net/ip_vs # The realservers and the listened ports are # 10.0.0.25:2222, 10.0.0.35:3333. # The virtual server and the listened port is # 10.0.0.37:7777. KERNAL_FILE_SAMPLE_V4 = ( "IP Virtual Server version 1.2.1 (size=4096)\n" "Prot LocalAddress:Port Scheduler Flags\n" " -> RemoteAddress:Port Forward Weight ActiveConn InActConn\n" "UDP 0A000025:1E61 rr\n" " -> 0A000023:0D05 Masq 2 0 0\n" " -> 0A000019:08AE Masq 3 0 0") # Kernal_file_sample which is in /proc/net/ip_vs # The realservers and the listened ports are # [fd79:35e2:9963:0:f816:3eff:feca:b7bf]:2222, # [fd79:35e2:9963:0:f816:3eff:fe9d:94df]:3333. # The virtual server and the listened port is # [fd79:35e2:9963:0:f816:3eff:fe6d:7a2a]:7777. KERNAL_FILE_SAMPLE_V6 = ( "IP Virtual Server version 1.2.1 (size=4096)\n" "Prot LocalAddress:Port Scheduler Flags\n" " -> RemoteAddress:Port Forward Weight ActiveConn InActConn\n" "UDP [fd79:35e2:9963:0000:f816:3eff:fe6d:7a2a]:1E61 rr\n" " -> [fd79:35e2:9963:0000:f816:3eff:feca:b7bf]:08AE " "Masq 3 0 0\n" " -> [fd79:35e2:9963:0000:f816:3eff:fe9d:94df]:0D05 " "Masq 2 0 0") CFG_FILE_TEMPLATE_v4 = ( "# Configuration for Listener %(listener_id)s\n\n" "net_namespace %(ns_name)s\n\n" "virtual_server 10.0.0.37 7777 {\n" " lb_algo rr\n" " lb_kind NAT\n" " protocol udp\n\n\n" " # Configuration for Pool %(pool_id)s\n" " # Configuration for Member %(member_id1)s\n" " real_server 10.0.0.25 2222 {\n" " weight 3\n" " persistence_timeout 5\n" " persistence_granularity 255.0.0.0\n\n" " }\n\n" " # Configuration for Member %(member_id2)s\n" " real_server 10.0.0.35 3333 {\n" " weight 2\n" " persistence_timeout 5\n" " persistence_granularity 255.0.0.0\n\n" " }\n\n" "}") CFG_FILE_TEMPLATE_v6 = ( "# Configuration for Listener %(listener_id)s\n\n" "net_namespace %(ns_name)s\n\n" "virtual_server fd79:35e2:9963:0:f816:3eff:fe6d:7a2a 7777 {\n" " lb_algo rr\n" " lb_kind NAT\n" " protocol udp\n\n\n" " # Configuration for Pool %(pool_id)s\n" " # Configuration for Member %(member_id1)s\n" " real_server fd79:35e2:9963:0:f816:3eff:feca:b7bf 2222 {\n" " weight 3\n" " }\n\n" " # Configuration for Member %(member_id2)s\n" " real_server fd79:35e2:9963:0:f816:3eff:fe9d:94df 3333 {\n" " weight 2\n" " }\n\n" "}") IPVSADM_OUTPUT_TEMPLATE = ( "IP Virtual Server version 1.2.1 (size=4096)\n" "Prot LocalAddress:Port Scheduler Flags\n" " -> RemoteAddress:Port Forward Weight ActiveConn InActConn\n" "UDP %(listener_ipport)s rr\n" " -> %(member1_ipport)s Masq 3 0 0\n" " -> %(member2_ipport)s Masq 2 0 0") IPVSADM_STATS_OUTPUT_TEMPLATE = ( "IP Virtual Server version 1.2.1 (size=4096)\n" "Prot LocalAddress:Port Conns InPkts OutPkts " "InBytes OutBytes\n" " -> RemoteAddress:Port\n" "UDP %(listener_ipport)s 5 4264 5" " 6387472 7490\n" " -> %(member1_ipport)s 2 1706 2" " 2555588 2996\n" " -> %(member2_ipport)s 3 2558 3" " 3831884 4494") class LvsQueryTestCase(base.TestCase): def setUp(self): super(LvsQueryTestCase, self).setUp() self.listener_id_v4 = uuidutils.generate_uuid() self.pool_id_v4 = uuidutils.generate_uuid() self.member_id1_v4 = uuidutils.generate_uuid() self.member_id2_v4 = uuidutils.generate_uuid() self.listener_id_v6 = uuidutils.generate_uuid() self.pool_id_v6 = uuidutils.generate_uuid() self.member_id1_v6 = uuidutils.generate_uuid() self.member_id2_v6 = uuidutils.generate_uuid() cfg_content_v4 = CFG_FILE_TEMPLATE_v4 % { 'listener_id': self.listener_id_v4, 'ns_name': constants.AMPHORA_NAMESPACE, 'pool_id': self.pool_id_v4, 'member_id1': self.member_id1_v4, 'member_id2': self.member_id2_v4 } cfg_content_v6 = CFG_FILE_TEMPLATE_v6 % { 'listener_id': self.listener_id_v6, 'ns_name': constants.AMPHORA_NAMESPACE, 'pool_id': self.pool_id_v6, 'member_id1': self.member_id1_v6, 'member_id2': self.member_id2_v6 } self.useFixture(test_utils.OpenFixture( util.keepalived_lvs_cfg_path(self.listener_id_v4), cfg_content_v4)) self.useFixture(test_utils.OpenFixture( util.keepalived_lvs_cfg_path(self.listener_id_v6), cfg_content_v6)) @mock.patch('subprocess.check_output') def test_get_listener_realserver_mapping(self, mock_check_output): # Ipv4 resolver input_listener_ip_port = '10.0.0.37:7777' target_ns = constants.AMPHORA_NAMESPACE mock_check_output.return_value = KERNAL_FILE_SAMPLE_V4 result = lvs_query.get_listener_realserver_mapping( target_ns, input_listener_ip_port) expected = {'10.0.0.25:2222': {'status': 'UP', 'Forward': 'Masq', 'Weight': '3', 'ActiveConn': '0', 'InActConn': '0'}, '10.0.0.35:3333': {'status': 'UP', 'Forward': 'Masq', 'Weight': '2', 'ActiveConn': '0', 'InActConn': '0'}} self.assertEqual((True, expected), result) # Ipv6 resolver input_listener_ip_port = '[fd79:35e2:9963:0:f816:3eff:fe6d:7a2a]:7777' mock_check_output.return_value = KERNAL_FILE_SAMPLE_V6 result = lvs_query.get_listener_realserver_mapping( target_ns, input_listener_ip_port) expected = {'[fd79:35e2:9963:0:f816:3eff:feca:b7bf]:2222': {'status': constants.UP, 'Forward': 'Masq', 'Weight': '3', 'ActiveConn': '0', 'InActConn': '0'}, '[fd79:35e2:9963:0:f816:3eff:fe9d:94df]:3333': {'status': constants.UP, 'Forward': 'Masq', 'Weight': '2', 'ActiveConn': '0', 'InActConn': '0'}} self.assertEqual((True, expected), result) # negetive cases mock_check_output.return_value = KERNAL_FILE_SAMPLE_V4 for listener_ip_port in ['10.0.0.37:7776', '10.0.0.31:7777']: result = lvs_query.get_listener_realserver_mapping( target_ns, listener_ip_port) self.assertEqual((False, {}), result) mock_check_output.return_value = KERNAL_FILE_SAMPLE_V6 for listener_ip_port in [ '[fd79:35e2:9963:0:f816:3eff:fe6d:7a2a]:7776', '[fd79:35e2:9973:0:f816:3eff:fe6d:7a2a]:7777']: result = lvs_query.get_listener_realserver_mapping( target_ns, listener_ip_port) self.assertEqual((False, {}), result) def test_get_udp_listener_resource_ipports_nsname(self): # ipv4 res = lvs_query.get_udp_listener_resource_ipports_nsname( self.listener_id_v4) expected = {'Listener': {'id': self.listener_id_v4, 'ipport': '10.0.0.37:7777'}, 'Pool': {'id': self.pool_id_v4}, 'Members': [{'id': self.member_id1_v4, 'ipport': '10.0.0.25:2222'}, {'id': self.member_id2_v4, 'ipport': '10.0.0.35:3333'}]} self.assertEqual((expected, constants.AMPHORA_NAMESPACE), res) # ipv6 res = lvs_query.get_udp_listener_resource_ipports_nsname( self.listener_id_v6) expected = {'Listener': { 'id': self.listener_id_v6, 'ipport': '[fd79:35e2:9963:0:f816:3eff:fe6d:7a2a]:7777'}, 'Pool': {'id': self.pool_id_v6}, 'Members': [ {'id': self.member_id1_v6, 'ipport': '[fd79:35e2:9963:0:f816:3eff:feca:b7bf]:2222'}, {'id': self.member_id2_v6, 'ipport': '[fd79:35e2:9963:0:f816:3eff:fe9d:94df]:3333'}]} self.assertEqual((expected, constants.AMPHORA_NAMESPACE), res) @mock.patch('subprocess.check_output') def test_get_udp_listener_pool_status(self, mock_check_output): # test with ipv4 and ipv6 mock_check_output.return_value = KERNAL_FILE_SAMPLE_V4 res = lvs_query.get_udp_listener_pool_status(self.listener_id_v4) expected = { 'lvs': {'uuid': self.pool_id_v4, 'status': constants.UP, 'members': {self.member_id1_v4: constants.UP, self.member_id2_v4: constants.UP}}} self.assertEqual(expected, res) mock_check_output.return_value = KERNAL_FILE_SAMPLE_V6 res = lvs_query.get_udp_listener_pool_status(self.listener_id_v6) expected = { 'lvs': {'uuid': self.pool_id_v6, 'status': constants.UP, 'members': {self.member_id1_v6: constants.UP, self.member_id2_v6: constants.UP}}} self.assertEqual(expected, res) @mock.patch('octavia.amphorae.backends.utils.keepalivedlvs_query.' 'get_udp_listener_resource_ipports_nsname') def test_get_udp_listener_pool_status_when_no_pool( self, mock_get_resource_ipports): # Just test with ipv4, ipv6 tests is same. # the returned resource_ipport_mapping doesn't contains the 'Pool' # resource, that means the listener doesn't have a pool resource, it # isn't usable at this moment, then the pool status will # return nothing. mock_get_resource_ipports.return_value = ( { 'Listener': { 'id': self.listener_id_v4, 'ipport': '10.0.0.37:7777'}}, constants.AMPHORA_NAMESPACE) res = lvs_query.get_udp_listener_pool_status(self.listener_id_v4) self.assertEqual({}, res) @mock.patch('octavia.amphorae.backends.utils.keepalivedlvs_query.' 'get_udp_listener_resource_ipports_nsname') def test_get_udp_listener_pool_status_when_no_members( self, mock_get_resource_ipports): # Just test with ipv4, ipv6 tests is same. # the returned resource_ipport_mapping doesn't contains the 'Members' # resources, that means the pool of listener doesn't have a enabled # pool resource, so the pool is not usable, then the pool status will # return DOWN. mock_get_resource_ipports.return_value = ( { 'Listener': {'id': self.listener_id_v4, 'ipport': '10.0.0.37:7777'}, 'Pool': {'id': self.pool_id_v4}}, constants.AMPHORA_NAMESPACE) res = lvs_query.get_udp_listener_pool_status(self.listener_id_v4) expected = {'lvs': { 'uuid': self.pool_id_v4, 'status': constants.DOWN, 'members': {} }} self.assertEqual(expected, res) @mock.patch('octavia.amphorae.backends.utils.keepalivedlvs_query.' 'get_listener_realserver_mapping') def test_get_udp_listener_pool_status_when_not_get_realserver_result( self, mock_get_mapping): # This will hit if the kernel lvs file (/proc/net/ip_vs) # lose its content. So at this moment, eventhough we configure the # pool and member into udp keepalived config file, we have to set # ths status of pool and its members to DOWN. mock_get_mapping.return_value = (False, {}) res = lvs_query.get_udp_listener_pool_status(self.listener_id_v4) expected = { 'lvs': {'uuid': self.pool_id_v4, 'status': constants.DOWN, 'members': {self.member_id1_v4: constants.DOWN, self.member_id2_v4: constants.DOWN}}} self.assertEqual(expected, res) @mock.patch('subprocess.check_output') def test_get_ipvsadm_info(self, mock_check_output): for ip_list in [["10.0.0.37:7777", "10.0.0.25:2222", "10.0.0.35:3333"], ["[fd79:35e2:9963:0:f816:3eff:fe6d:7a2a]:7777", "[fd79:35e2:9963:0:f816:3eff:feca:b7bf]:2222", "[fd79:35e2:9963:0:f816:3eff:fe9d:94df]:3333"]]: mock_check_output.return_value = IPVSADM_OUTPUT_TEMPLATE % { "listener_ipport": ip_list[0], "member1_ipport": ip_list[1], "member2_ipport": ip_list[2]} res = lvs_query.get_ipvsadm_info(constants.AMPHORA_NAMESPACE) # This expected result can referece on IPVSADM_OUTPUT_TEMPLATE, # that means the function can get every element of the virtual # server and the real servers. expected = { ip_list[0]: { 'Listener': [('Prot', 'UDP'), ('LocalAddress:Port', ip_list[0]), ('Scheduler', 'rr')], 'Members': [[('RemoteAddress:Port', ip_list[1]), ('Forward', 'Masq'), ('Weight', '3'), ('ActiveConn', '0'), ('InActConn', '0')], [('RemoteAddress:Port', ip_list[2]), ('Forward', 'Masq'), ('Weight', '2'), ('ActiveConn', '0'), ('InActConn', '0')]]}} self.assertEqual(expected, res) # ipvsadm stats mock_check_output.return_value = IPVSADM_STATS_OUTPUT_TEMPLATE % { "listener_ipport": ip_list[0], "member1_ipport": ip_list[1], "member2_ipport": ip_list[2]} res = lvs_query.get_ipvsadm_info(constants.AMPHORA_NAMESPACE, is_stats_cmd=True) expected = { ip_list[0]: {'Listener': [('Prot', 'UDP'), ('LocalAddress:Port', ip_list[0]), ('Conns', '5'), ('InPkts', '4264'), ('OutPkts', '5'), ('InBytes', '6387472'), ('OutBytes', '7490')], 'Members': [[('RemoteAddress:Port', ip_list[1]), ('Conns', '2'), ('InPkts', '1706'), ('OutPkts', '2'), ('InBytes', '2555588'), ('OutBytes', '2996')], [('RemoteAddress:Port', ip_list[2]), ('Conns', '3'), ('InPkts', '2558'), ('OutPkts', '3'), ('InBytes', '3831884'), ('OutBytes', '4494')]]}} self.assertEqual(expected, res) @mock.patch('subprocess.check_output') @mock.patch("octavia.amphorae.backends.agent.api_server.util." "is_udp_listener_running", return_value=True) @mock.patch("octavia.amphorae.backends.agent.api_server.util." "get_udp_listeners") def test_get_udp_listeners_stats( self, mock_get_listener, mock_is_running, mock_check_output): # The ipv6 test is same with ipv4, so just test ipv4 here mock_get_listener.return_value = [self.listener_id_v4] output_list = list() output_list.append(IPVSADM_OUTPUT_TEMPLATE % { "listener_ipport": "10.0.0.37:7777", "member1_ipport": "10.0.0.25:2222", "member2_ipport": "10.0.0.35:3333"}) output_list.append(IPVSADM_STATS_OUTPUT_TEMPLATE % { "listener_ipport": "10.0.0.37:7777", "member1_ipport": "10.0.0.25:2222", "member2_ipport": "10.0.0.35:3333"}) mock_check_output.side_effect = output_list res = lvs_query.get_udp_listeners_stats() # We can check the expected result referece the stats sample, # that means this func can compute the stats info of single listener. expected = {self.listener_id_v4: { 'status': constants.OPEN, 'stats': {'bin': 6387472, 'stot': 5, 'bout': 7490, 'ereq': 0, 'scur': 0}}} self.assertEqual(expected, res) # if no udp listener need to be collected. # Then this function will return nothing. mock_is_running.return_value = False res = lvs_query.get_udp_listeners_stats() self.assertIsNone(res)