diff --git a/neutron/plugins/ml2/drivers/arista/arista_l3_driver.py b/neutron/plugins/ml2/drivers/arista/arista_l3_driver.py index d5315fa8c62..353de01fe84 100644 --- a/neutron/plugins/ml2/drivers/arista/arista_l3_driver.py +++ b/neutron/plugins/ml2/drivers/arista/arista_l3_driver.py @@ -243,16 +243,38 @@ class AristaL3Driver(object): rdm = str(int(hashlib.sha256(router_name).hexdigest(), 16) % 6553) + mlag_peer_failed = False for s in self._servers: - self.create_router_on_eos(router_name, rdm, s) + try: + self.create_router_on_eos(router_name, rdm, s) + mlag_peer_failed = False + except Exception: + if self.mlag_configured and not mlag_peer_failed: + mlag_peer_failed = True + else: + msg = (_('Failed to create router %s on EOS') % + router_name) + LOG.exception(msg) + raise arista_exc.AristaServicePluginRpcError(msg=msg) def delete_router(self, context, tenant_id, router_id, router): """Deletes a router from Arista Switch.""" if router: + router_name = self._arista_router_name(tenant_id, router['name']) + mlag_peer_failed = False for s in self._servers: - self.delete_router_from_eos(self._arista_router_name( - tenant_id, router['name']), s) + try: + self.delete_router_from_eos(router_name, s) + mlag_peer_failed = False + except Exception: + if self.mlag_configured and not mlag_peer_failed: + mlag_peer_failed = True + else: + msg = (_LE('Failed to delete router %s from EOS') % + router_name) + LOG.exception(msg) + raise arista_exc.AristaServicePluginRpcError(msg=msg) def update_router(self, context, router_id, original_router, new_router): """Updates a router which is already created on Arista Switch. @@ -275,15 +297,27 @@ class AristaL3Driver(object): if self.mlag_configured: # For MLAG, we send a specific IP address as opposed to cidr # For now, we are using x.x.x.253 and x.x.x.254 as virtual IP + mlag_peer_failed = False for i, server in enumerate(self._servers): #get appropriate virtual IP address for this router router_ip = self._get_router_ip(cidr, i, router_info['ip_version']) - self.add_interface_to_router(router_info['seg_id'], - router_name, - router_info['gip'], - router_ip, subnet_mask, - server) + try: + self.add_interface_to_router(router_info['seg_id'], + router_name, + router_info['gip'], + router_ip, subnet_mask, + server) + mlag_peer_failed = False + except Exception: + if not mlag_peer_failed: + mlag_peer_failed = True + else: + msg = (_('Failed to add interface to router ' + '%s on EOS') % router_name) + LOG.exception(msg) + raise arista_exc.AristaServicePluginRpcError( + msg=msg) else: for s in self._servers: @@ -300,9 +334,21 @@ class AristaL3Driver(object): if router_info: router_name = self._arista_router_name(router_info['tenant_id'], router_info['name']) + mlag_peer_failed = False for s in self._servers: - self.delete_interface_from_router(router_info['seg_id'], - router_name, s) + try: + self.delete_interface_from_router(router_info['seg_id'], + router_name, s) + if self.mlag_configured: + mlag_peer_failed = False + except Exception: + if self.mlag_configured and not mlag_peer_failed: + mlag_peer_failed = True + else: + msg = (_LE('Failed to remove interface from router ' + '%s on EOS') % router_name) + LOG.exception(msg) + raise arista_exc.AristaServicePluginRpcError(msg=msg) def _run_openstack_l3_cmds(self, commands, server): """Execute/sends a CAPI (Command API) command to EOS. diff --git a/neutron/tests/unit/ml2/drivers/arista/test_arista_l3_driver.py b/neutron/tests/unit/ml2/drivers/arista/test_arista_l3_driver.py index b2a2989b644..4cc4db1e28f 100644 --- a/neutron/tests/unit/ml2/drivers/arista/test_arista_l3_driver.py +++ b/neutron/tests/unit/ml2/drivers/arista/test_arista_l3_driver.py @@ -391,3 +391,66 @@ class AristaL3DriverTestCases_MLAG_v6(base.BaseTestCase): 'ip_version': 6} self.assertFalse(self.drv.remove_router_interface(None, router)) + + +class AristaL3DriverTestCasesMlag_one_switch_failed(base.BaseTestCase): + """Test cases to test with non redundant hardare in redundancy mode. + + In the following test cases, the driver is configured in MLAG (redundancy + mode) but, one of the switches is mocked to throw exceptoin to mimic + failure of the switch. Ensure that the the operation does not fail when + one of the switches fails. + """ + + def setUp(self): + super(AristaL3DriverTestCasesMlag_one_switch_failed, self).setUp() + setup_arista_config('value', mlag=True) + self.drv = arista.AristaL3Driver() + self.drv._servers = [] + self.drv._servers.append(mock.MagicMock()) + self.drv._servers.append(mock.MagicMock()) + + def test_create_router_when_one_switch_fails(self): + router = {} + router['name'] = 'test-router-1' + tenant = '123' + + # Make one of the switches throw an exception - i.e. fail + self.drv._servers[0].runCmds = mock.Mock(side_effect = Exception()) + self.drv.create_router(None, tenant, router) + + def test_delete_router_when_one_switch_fails(self): + router = {} + router['name'] = 'test-router-1' + tenant = '123' + router_id = '345' + + # Make one of the switches throw an exception - i.e. fail + self.drv._servers[1].runCmds = mock.Mock(side_effect = Exception()) + self.drv.delete_router(None, tenant, router_id, router) + + def test_add_router_interface_when_one_switch_fails(self): + router = {} + router['name'] = 'test-router-1' + router['tenant_id'] = 'ten-1' + router['seg_id'] = '100' + router['ip_version'] = 4 + router['cidr'] = '10.10.10.0/24' + router['gip'] = '10.10.10.1' + + # Make one of the switches throw an exception - i.e. fail + self.drv._servers[1].runCmds = mock.Mock(side_effect = Exception()) + self.drv.add_router_interface(None, router) + + def test_remove_router_interface_when_one_switch_fails(self): + router = {} + router['name'] = 'test-router-1' + router['tenant_id'] = 'ten-1' + router['seg_id'] = '100' + router['ip_version'] = 4 + router['cidr'] = '10.10.10.0/24' + router['gip'] = '10.10.10.1' + + # Make one of the switches throw an exception - i.e. fail + self.drv._servers[0].runCmds = mock.Mock(side_effect = Exception()) + self.drv.remove_router_interface(None, router)