Fixes failover flow with namespace driver
This patch updates the haproxy service scripts to handle the case where the network interfaces have not yet been plugged. This can occur in a failover situation. This patch also makes sure we don't move the management lan interface into the network namespace. Closes-Bug: #1509706 Closes-Bug: #1577963 Change-Id: I04d267bd3cdedca11f0350c5255086233cba14ec
This commit is contained in:
@@ -72,7 +72,7 @@ class TestPlug(base.TestCase):
|
||||
mock_flask.jsonify.assert_any_call({
|
||||
'message': 'OK',
|
||||
'details': 'VIP {vip} plugged on interface {interface}'.format(
|
||||
vip=FAKE_IP_IPV4, interface=FAKE_INTERFACE)
|
||||
vip=FAKE_IP_IPV4, interface='eth1')
|
||||
})
|
||||
|
||||
@mock.patch.object(plug, "flask")
|
||||
@@ -96,7 +96,7 @@ class TestPlug(base.TestCase):
|
||||
mock_flask.jsonify.assert_any_call({
|
||||
'message': 'OK',
|
||||
'details': 'VIP {vip} plugged on interface {interface}'.format(
|
||||
vip=FAKE_IP_IPV6_EXPANDED, interface=FAKE_INTERFACE)
|
||||
vip=FAKE_IP_IPV6_EXPANDED, interface='eth1')
|
||||
})
|
||||
|
||||
@mock.patch.object(plug, "flask")
|
||||
@@ -118,3 +118,17 @@ class TestPlug(base.TestCase):
|
||||
mac_address=FAKE_MAC_ADDRESS
|
||||
)
|
||||
mock_flask.jsonify.assert_any_call({'message': 'Invalid VIP'})
|
||||
|
||||
@mock.patch('pyroute2.NetNS')
|
||||
def test__netns_interface_exists(self, mock_netns):
|
||||
|
||||
netns_handle = mock_netns.return_value.__enter__.return_value
|
||||
|
||||
netns_handle.get_links.return_value = [{
|
||||
'attrs': [['IFLA_ADDRESS', '123']]}]
|
||||
|
||||
# Interface is found in netns
|
||||
self.assertTrue(plug._netns_interface_exists('123'))
|
||||
|
||||
# Interface is not found in netns
|
||||
self.assertFalse(plug._netns_interface_exists('321'))
|
||||
|
||||
@@ -159,7 +159,7 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
|
||||
amphorae_network_config.get().vip_subnet.cidr = FAKE_CIDR
|
||||
amphorae_network_config.get().vip_subnet.gateway_ip = FAKE_GATEWAY
|
||||
amphorae_network_config.get().vrrp_port = self.port
|
||||
self.driver.post_vip_plug(self.lb, amphorae_network_config)
|
||||
self.driver.post_vip_plug(self.amp, self.lb, amphorae_network_config)
|
||||
self.driver.client.plug_vip.assert_called_once_with(
|
||||
self.amp, self.lb.vip.ip_address, self.subnet_info)
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ class TestNoopAmphoraLoadBalancerDriver(base.TestCase):
|
||||
self.amphora.id, self.port.id)])
|
||||
|
||||
def test_post_vip_plug(self):
|
||||
self.driver.post_vip_plug(self.load_balancer,
|
||||
self.driver.post_vip_plug(self.amphora, self.load_balancer,
|
||||
self.amphorae_net_configs)
|
||||
expected_method_and_args = (self.load_balancer.id,
|
||||
self.amphorae_net_configs,
|
||||
|
||||
@@ -264,12 +264,9 @@ class TestAmphoraFlows(base.TestCase):
|
||||
self.assertIn(constants.COMPUTE_OBJ, amp_flow.provides)
|
||||
self.assertIn(constants.LISTENERS, amp_flow.provides)
|
||||
self.assertIn(constants.LOADBALANCER, amp_flow.provides)
|
||||
self.assertIn(constants.MEMBER_PORTS, amp_flow.provides)
|
||||
self.assertIn(constants.PORTS, amp_flow.provides)
|
||||
self.assertIn(constants.VIP, amp_flow.provides)
|
||||
|
||||
self.assertEqual(2, len(amp_flow.requires))
|
||||
self.assertEqual(12, len(amp_flow.provides))
|
||||
self.assertEqual(11, len(amp_flow.provides))
|
||||
|
||||
amp_flow = self.AmpFlow.get_failover_flow(role=constants.ROLE_MASTER)
|
||||
|
||||
@@ -286,12 +283,9 @@ class TestAmphoraFlows(base.TestCase):
|
||||
self.assertIn(constants.COMPUTE_OBJ, amp_flow.provides)
|
||||
self.assertIn(constants.LISTENERS, amp_flow.provides)
|
||||
self.assertIn(constants.LOADBALANCER, amp_flow.provides)
|
||||
self.assertIn(constants.MEMBER_PORTS, amp_flow.provides)
|
||||
self.assertIn(constants.PORTS, amp_flow.provides)
|
||||
self.assertIn(constants.VIP, amp_flow.provides)
|
||||
|
||||
self.assertEqual(2, len(amp_flow.requires))
|
||||
self.assertEqual(12, len(amp_flow.provides))
|
||||
self.assertEqual(11, len(amp_flow.provides))
|
||||
|
||||
amp_flow = self.AmpFlow.get_failover_flow(role=constants.ROLE_BACKUP)
|
||||
|
||||
@@ -308,12 +302,9 @@ class TestAmphoraFlows(base.TestCase):
|
||||
self.assertIn(constants.COMPUTE_OBJ, amp_flow.provides)
|
||||
self.assertIn(constants.LISTENERS, amp_flow.provides)
|
||||
self.assertIn(constants.LOADBALANCER, amp_flow.provides)
|
||||
self.assertIn(constants.MEMBER_PORTS, amp_flow.provides)
|
||||
self.assertIn(constants.PORTS, amp_flow.provides)
|
||||
self.assertIn(constants.VIP, amp_flow.provides)
|
||||
|
||||
self.assertEqual(2, len(amp_flow.requires))
|
||||
self.assertEqual(12, len(amp_flow.provides))
|
||||
self.assertEqual(11, len(amp_flow.provides))
|
||||
|
||||
amp_flow = self.AmpFlow.get_failover_flow(role='BOGUSROLE')
|
||||
|
||||
@@ -330,12 +321,9 @@ class TestAmphoraFlows(base.TestCase):
|
||||
self.assertIn(constants.COMPUTE_OBJ, amp_flow.provides)
|
||||
self.assertIn(constants.LISTENERS, amp_flow.provides)
|
||||
self.assertIn(constants.LOADBALANCER, amp_flow.provides)
|
||||
self.assertIn(constants.MEMBER_PORTS, amp_flow.provides)
|
||||
self.assertIn(constants.PORTS, amp_flow.provides)
|
||||
self.assertIn(constants.VIP, amp_flow.provides)
|
||||
|
||||
self.assertEqual(2, len(amp_flow.requires))
|
||||
self.assertEqual(12, len(amp_flow.provides))
|
||||
self.assertEqual(11, len(amp_flow.provides))
|
||||
|
||||
def test_cert_rotate_amphora_flow(self, mock_get_net_driver):
|
||||
self.AmpFlow = amphora_flows.AmphoraFlows()
|
||||
|
||||
@@ -59,7 +59,7 @@ class TestAmphoraDriverTasks(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
_LB_mock.amphorae = _amphora_mock
|
||||
_LB_mock.amphorae = [_amphora_mock]
|
||||
_LB_mock.id = LB_ID
|
||||
super(TestAmphoraDriverTasks, self).setUp()
|
||||
|
||||
@@ -307,10 +307,40 @@ class TestAmphoraDriverTasks(base.TestCase):
|
||||
|
||||
amphorae_net_config_mock = mock.Mock()
|
||||
amphora_post_vip_plug_obj = amphora_driver_tasks.AmphoraPostVIPPlug()
|
||||
amphora_post_vip_plug_obj.execute(_LB_mock, amphorae_net_config_mock)
|
||||
amphora_post_vip_plug_obj.execute(_amphora_mock,
|
||||
_LB_mock,
|
||||
amphorae_net_config_mock)
|
||||
|
||||
mock_driver.post_vip_plug.assert_called_once_with(
|
||||
_LB_mock, amphorae_net_config_mock)
|
||||
_amphora_mock, _LB_mock, amphorae_net_config_mock)
|
||||
|
||||
# Test revert
|
||||
amp = amphora_post_vip_plug_obj.revert(None, _amphora_mock, _LB_mock)
|
||||
repo.LoadBalancerRepository.update.assert_called_once_with(
|
||||
_session_mock,
|
||||
id=LB_ID,
|
||||
provisioning_status=constants.ERROR)
|
||||
|
||||
self.assertIsNone(amp)
|
||||
|
||||
@mock.patch('octavia.db.repositories.LoadBalancerRepository.update')
|
||||
def test_amphorae_post_vip_plug(self,
|
||||
mock_loadbalancer_repo_update,
|
||||
mock_driver,
|
||||
mock_generate_uuid,
|
||||
mock_log,
|
||||
mock_get_session,
|
||||
mock_listener_repo_get,
|
||||
mock_listener_repo_update,
|
||||
mock_amphora_repo_update):
|
||||
|
||||
amphorae_net_config_mock = mock.Mock()
|
||||
amphora_post_vip_plug_obj = amphora_driver_tasks.AmphoraePostVIPPlug()
|
||||
amphora_post_vip_plug_obj.execute(_LB_mock,
|
||||
amphorae_net_config_mock)
|
||||
|
||||
mock_driver.post_vip_plug.assert_called_once_with(
|
||||
_amphora_mock, _LB_mock, amphorae_net_config_mock)
|
||||
|
||||
# Test revert
|
||||
amp = amphora_post_vip_plug_obj.revert(None, _LB_mock)
|
||||
|
||||
@@ -490,3 +490,35 @@ class TestNetworkTasks(base.TestCase):
|
||||
|
||||
mock_driver.plug_port.assert_any_call(amphora, port1)
|
||||
mock_driver.plug_port.assert_any_call(amphora, port2)
|
||||
|
||||
def test_plug_vip_port(self, mock_get_net_driver):
|
||||
mock_driver = mock.MagicMock()
|
||||
mock_get_net_driver.return_value = mock_driver
|
||||
vip_port = mock.MagicMock()
|
||||
vrrp_port = mock.MagicMock()
|
||||
|
||||
amphorae_network_config = mock.MagicMock()
|
||||
amphorae_network_config.get().vip_port = vip_port
|
||||
amphorae_network_config.get().vrrp_port = vrrp_port
|
||||
|
||||
plugvipport = network_tasks.PlugVIPPort()
|
||||
plugvipport.execute(self.amphora_mock, amphorae_network_config)
|
||||
mock_driver.plug_port.assert_any_call(self.amphora_mock, vip_port)
|
||||
mock_driver.plug_port.assert_any_call(self.amphora_mock, vrrp_port)
|
||||
|
||||
# test revert
|
||||
plugvipport.revert(None, self.amphora_mock, amphorae_network_config)
|
||||
mock_driver.unplug_port.assert_any_call(self.amphora_mock, vip_port)
|
||||
mock_driver.unplug_port.assert_any_call(self.amphora_mock, vrrp_port)
|
||||
|
||||
def test_wait_for_port_detach(self, mock_get_net_driver):
|
||||
mock_driver = mock.MagicMock()
|
||||
mock_get_net_driver.return_value = mock_driver
|
||||
|
||||
amphora = o_data_models.Amphora(id=AMPHORA_ID,
|
||||
lb_network_ip=IP_ADDRESS)
|
||||
|
||||
waitforportdetach = network_tasks.WaitForPortDetach()
|
||||
waitforportdetach.execute(amphora)
|
||||
|
||||
mock_driver.wait_for_port_detach.assert_called_once_with(amphora)
|
||||
|
||||
@@ -17,6 +17,8 @@ import copy
|
||||
import mock
|
||||
from neutronclient.common import exceptions as neutron_exceptions
|
||||
from novaclient.client import exceptions as nova_exceptions
|
||||
from oslo_config import cfg
|
||||
from oslo_config import fixture as oslo_fixture
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from octavia.common import clients
|
||||
@@ -52,6 +54,7 @@ class TestAllowedAddressPairsDriver(base.TestCase):
|
||||
HA_PORT_ID = "8"
|
||||
HA_IP = "12.0.0.2"
|
||||
PORT_ID = uuidutils.generate_uuid()
|
||||
DEVICE_ID = uuidutils.generate_uuid()
|
||||
|
||||
def setUp(self):
|
||||
super(TestAllowedAddressPairsDriver, self).setUp()
|
||||
@@ -586,8 +589,7 @@ class TestAllowedAddressPairsDriver(base.TestCase):
|
||||
lb_network_ip=self.LB_NET_IP, ha_port_id=self.HA_PORT_ID,
|
||||
ha_ip=self.HA_IP)
|
||||
self.driver.failover_preparation(amphora)
|
||||
port_update.assert_called_once_with(ports['ports'][1].get('id'),
|
||||
{'port': {'device_id': ''}})
|
||||
self.assertFalse(port_update.called)
|
||||
self.driver.dns_integration_enabled = original_dns_integration_state
|
||||
|
||||
def test_failover_preparation_dns_integration(self):
|
||||
@@ -611,8 +613,7 @@ class TestAllowedAddressPairsDriver(base.TestCase):
|
||||
ha_ip=self.HA_IP)
|
||||
self.driver.failover_preparation(amphora)
|
||||
port_update.assert_called_once_with(ports['ports'][1].get('id'),
|
||||
{'port': {'dns_name': '',
|
||||
'device_id': ''}})
|
||||
{'port': {'dns_name': ''}})
|
||||
self.driver.dns_integration_enabled = original_dns_integration_state
|
||||
|
||||
def _failover_show_port_side_effect(self, port_id):
|
||||
@@ -712,3 +713,71 @@ class TestAllowedAddressPairsDriver(base.TestCase):
|
||||
expected_subnet_id = fake_subnet['subnet']['id']
|
||||
self.assertEqual(expected_subnet_id, config.ha_subnet.id)
|
||||
self.assertEqual(expected_subnet_id, config.vrrp_subnet.id)
|
||||
|
||||
@mock.patch('time.sleep')
|
||||
def test_wait_for_port_detach(self, mock_sleep):
|
||||
amphora = data_models.Amphora(
|
||||
id=self.AMPHORA_ID, load_balancer_id=self.LB_ID,
|
||||
compute_id=self.COMPUTE_ID, status=self.ACTIVE,
|
||||
lb_network_ip=self.LB_NET_IP, ha_port_id=self.HA_PORT_ID,
|
||||
ha_ip=self.HA_IP)
|
||||
ports = {"ports": [
|
||||
{"fixed_ips": [{"subnet_id": self.SUBNET_ID_1,
|
||||
"ip_address": self.IP_ADDRESS_1}],
|
||||
"id": self.FIXED_IP_ID_1, "network_id": self.NETWORK_ID_1},
|
||||
{"fixed_ips": [{"subnet_id": self.SUBNET_ID_2,
|
||||
"ip_address": self.IP_ADDRESS_2}],
|
||||
"id": self.FIXED_IP_ID_2, "network_id": self.NETWORK_ID_2}]}
|
||||
show_port_1_without_device_id = {"fixed_ips": [
|
||||
{"subnet_id": self.SUBNET_ID_1, "ip_address": self.IP_ADDRESS_1}],
|
||||
"id": self.FIXED_IP_ID_1, "network_id": self.NETWORK_ID_1,
|
||||
"device_id": ''}
|
||||
show_port_2_with_device_id = {"fixed_ips": [
|
||||
{"subnet_id": self.SUBNET_ID_2, "ip_address": self.IP_ADDRESS_2}],
|
||||
"id": self.FIXED_IP_ID_2, "network_id": self.NETWORK_ID_2,
|
||||
"device_id": self.DEVICE_ID}
|
||||
show_port_2_without_device_id = {"fixed_ips": [
|
||||
{"subnet_id": self.SUBNET_ID_2, "ip_address": self.IP_ADDRESS_2}],
|
||||
"id": self.FIXED_IP_ID_2, "network_id": self.NETWORK_ID_2,
|
||||
"device_id": None}
|
||||
self.driver.neutron_client.list_ports.return_value = ports
|
||||
port_mock = mock.MagicMock()
|
||||
port_mock.get = mock.Mock(
|
||||
side_effect=[show_port_1_without_device_id,
|
||||
show_port_2_with_device_id,
|
||||
show_port_2_with_device_id,
|
||||
show_port_2_without_device_id])
|
||||
self.driver.neutron_client.show_port.return_value = port_mock
|
||||
self.driver.wait_for_port_detach(amphora)
|
||||
self.assertEqual(1, mock_sleep.call_count)
|
||||
|
||||
@mock.patch('time.time')
|
||||
@mock.patch('time.sleep')
|
||||
def test_wait_for_port_detach_timeout(self, mock_sleep, mock_time):
|
||||
mock_time.side_effect = [1, 2, 6]
|
||||
conf = oslo_fixture.Config(cfg.CONF)
|
||||
conf.config(group="networking", port_detach_timeout=5)
|
||||
amphora = data_models.Amphora(
|
||||
id=self.AMPHORA_ID, load_balancer_id=self.LB_ID,
|
||||
compute_id=self.COMPUTE_ID, status=self.ACTIVE,
|
||||
lb_network_ip=self.LB_NET_IP, ha_port_id=self.HA_PORT_ID,
|
||||
ha_ip=self.HA_IP)
|
||||
ports = {"ports": [
|
||||
{"fixed_ips": [{"subnet_id": self.SUBNET_ID_1,
|
||||
"ip_address": self.IP_ADDRESS_1}],
|
||||
"id": self.FIXED_IP_ID_1, "network_id": self.NETWORK_ID_1},
|
||||
{"fixed_ips": [{"subnet_id": self.SUBNET_ID_2,
|
||||
"ip_address": self.IP_ADDRESS_2}],
|
||||
"id": self.FIXED_IP_ID_2, "network_id": self.NETWORK_ID_2}]}
|
||||
show_port_1_with_device_id = {"fixed_ips": [
|
||||
{"subnet_id": self.SUBNET_ID_2, "ip_address": self.IP_ADDRESS_2}],
|
||||
"id": self.FIXED_IP_ID_2, "network_id": self.NETWORK_ID_2,
|
||||
"device_id": self.DEVICE_ID}
|
||||
self.driver.neutron_client.list_ports.return_value = ports
|
||||
port_mock = mock.MagicMock()
|
||||
port_mock.get = mock.Mock(
|
||||
return_value=show_port_1_with_device_id)
|
||||
self.driver.neutron_client.show_port.return_value = port_mock
|
||||
self.assertRaises(network_base.TimeoutException,
|
||||
self.driver.wait_for_port_detach,
|
||||
amphora)
|
||||
|
||||
Reference in New Issue
Block a user