Neutron integration with OVN
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1390 lines
69KB

  1. #
  2. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  3. # not use this file except in compliance with the License. You may obtain
  4. # a copy of the License at
  5. #
  6. # http://www.apache.org/licenses/LICENSE-2.0
  7. #
  8. # Unless required by applicable law or agreed to in writing, software
  9. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  10. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  11. # License for the specific language governing permissions and limitations
  12. # under the License.
  13. #
  14. import copy
  15. import mock
  16. from neutron.services.revisions import revision_plugin
  17. from neutron.tests.unit.api import test_extensions
  18. from neutron.tests.unit.extensions import test_extraroute
  19. from neutron.tests.unit.extensions import test_l3
  20. from neutron.tests.unit.extensions import test_l3_ext_gw_mode as test_l3_gw
  21. from neutron_lib.api.definitions import portbindings
  22. from neutron_lib.api.definitions import provider_net as pnet
  23. from neutron_lib.callbacks import events
  24. from neutron_lib.callbacks import resources
  25. from neutron_lib import constants
  26. from neutron_lib import exceptions as n_exc
  27. from neutron_lib.plugins import constants as plugin_constants
  28. from neutron_lib.plugins import directory
  29. from oslo_config import cfg
  30. from networking_ovn.common import config
  31. from networking_ovn.common import constants as ovn_const
  32. from networking_ovn.common import utils
  33. from networking_ovn.tests.unit import fakes
  34. from networking_ovn.tests.unit.ml2 import test_mech_driver
  35. class OVNL3RouterPlugin(test_mech_driver.OVNMechanismDriverTestCase):
  36. l3_plugin = 'networking_ovn.l3.l3_ovn.OVNL3RouterPlugin'
  37. def _start_mock(self, path, return_value, new_callable=None):
  38. patcher = mock.patch(path, return_value=return_value,
  39. new_callable=new_callable)
  40. patch = patcher.start()
  41. self.addCleanup(patcher.stop)
  42. return patch
  43. def setUp(self):
  44. super(OVNL3RouterPlugin, self).setUp()
  45. revision_plugin.RevisionPlugin()
  46. network_attrs = {'router:external': True}
  47. self.fake_network = \
  48. fakes.FakeNetwork.create_one_network(attrs=network_attrs).info()
  49. self.fake_router_port = {'device_id': '',
  50. 'network_id': self.fake_network['id'],
  51. 'device_owner': 'network:router_interface',
  52. 'mac_address': 'aa:aa:aa:aa:aa:aa',
  53. 'fixed_ips': [{'ip_address': '10.0.0.100',
  54. 'subnet_id': 'subnet-id'}],
  55. 'id': 'router-port-id'}
  56. self.fake_router_port_assert = {
  57. 'lrouter': 'neutron-router-id',
  58. 'mac': 'aa:aa:aa:aa:aa:aa',
  59. 'name': 'lrp-router-port-id',
  60. 'may_exist': True,
  61. 'networks': ['10.0.0.100/24'],
  62. 'external_ids': {
  63. ovn_const.OVN_SUBNET_EXT_IDS_KEY: 'subnet-id',
  64. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
  65. ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY:
  66. utils.ovn_name(self.fake_network['id'])}}
  67. self.fake_router_ports = [self.fake_router_port]
  68. self.fake_subnet = {'id': 'subnet-id',
  69. 'ip_version': 4,
  70. 'cidr': '10.0.0.0/24'}
  71. self.fake_router = {'id': 'router-id',
  72. 'name': 'router',
  73. 'admin_state_up': False,
  74. 'routes': [{'destination': '1.1.1.0/24',
  75. 'nexthop': '10.0.0.2'}]}
  76. self.fake_router_interface_info = {
  77. 'port_id': 'router-port-id',
  78. 'device_id': '',
  79. 'mac_address': 'aa:aa:aa:aa:aa:aa',
  80. 'subnet_id': 'subnet-id',
  81. 'subnet_ids': ['subnet-id'],
  82. 'fixed_ips': [{'ip_address': '10.0.0.100',
  83. 'subnet_id': 'subnet-id'}],
  84. 'id': 'router-port-id'}
  85. self.fake_external_fixed_ips = {
  86. 'network_id': 'ext-network-id',
  87. 'external_fixed_ips': [{'ip_address': '192.168.1.1',
  88. 'subnet_id': 'ext-subnet-id'}]}
  89. self.fake_router_with_ext_gw = {
  90. 'id': 'router-id',
  91. 'name': 'router',
  92. 'admin_state_up': True,
  93. 'external_gateway_info': self.fake_external_fixed_ips,
  94. 'gw_port_id': 'gw-port-id'
  95. }
  96. self.fake_router_without_ext_gw = {
  97. 'id': 'router-id',
  98. 'name': 'router',
  99. 'admin_state_up': True,
  100. }
  101. self.fake_ext_subnet = {'id': 'ext-subnet-id',
  102. 'ip_version': 4,
  103. 'cidr': '192.168.1.0/24',
  104. 'gateway_ip': '192.168.1.254'}
  105. self.fake_ext_gw_port = {'device_id': '',
  106. 'device_owner': 'network:router_gateway',
  107. 'fixed_ips': [{'ip_address': '192.168.1.1',
  108. 'subnet_id': 'ext-subnet-id'}],
  109. 'mac_address': '00:00:00:02:04:06',
  110. 'network_id': self.fake_network['id'],
  111. 'id': 'gw-port-id'}
  112. self.fake_ext_gw_port_assert = {
  113. 'lrouter': 'neutron-router-id',
  114. 'mac': '00:00:00:02:04:06',
  115. 'name': 'lrp-gw-port-id',
  116. 'networks': ['192.168.1.1/24'],
  117. 'may_exist': True,
  118. 'external_ids': {
  119. ovn_const.OVN_SUBNET_EXT_IDS_KEY: 'ext-subnet-id',
  120. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
  121. ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY:
  122. utils.ovn_name(self.fake_network['id'])},
  123. 'gateway_chassis': ['hv1']}
  124. self.fake_floating_ip_attrs = {'floating_ip_address': '192.168.0.10',
  125. 'fixed_ip_address': '10.0.0.10'}
  126. self.fake_floating_ip = fakes.FakeFloatingIp.create_one_fip(
  127. attrs=self.fake_floating_ip_attrs)
  128. self.fake_floating_ip_new_attrs = {
  129. 'router_id': 'new-router-id',
  130. 'floating_ip_address': '192.168.0.10',
  131. 'fixed_ip_address': '10.10.10.10',
  132. 'port_id': 'new-port_id'}
  133. self.fake_floating_ip_new = fakes.FakeFloatingIp.create_one_fip(
  134. attrs=self.fake_floating_ip_new_attrs)
  135. self.fake_ovn_nat_rule = {
  136. 'logical_ip': self.fake_floating_ip['fixed_ip_address'],
  137. 'external_ip': self.fake_floating_ip['floating_ip_address'],
  138. 'type': 'dnat_and_snat',
  139. 'external_ids': {
  140. ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip['id'],
  141. ovn_const.OVN_FIP_PORT_EXT_ID_KEY:
  142. self.fake_floating_ip['port_id'],
  143. ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: utils.ovn_name(
  144. self.fake_floating_ip['router_id'])}}
  145. self.l3_inst = directory.get_plugin(plugin_constants.L3)
  146. self.nb_idl = self._start_mock(
  147. 'networking_ovn.l3.l3_ovn.OVNL3RouterPlugin._ovn',
  148. new_callable=mock.PropertyMock,
  149. return_value=fakes.FakeOvsdbNbOvnIdl())
  150. self.sb_idl = self._start_mock(
  151. 'networking_ovn.l3.l3_ovn.OVNL3RouterPlugin._sb_ovn',
  152. new_callable=mock.PropertyMock,
  153. return_value=fakes.FakeOvsdbSbOvnIdl())
  154. self._start_mock(
  155. 'neutron.plugins.ml2.plugin.Ml2Plugin.get_network',
  156. return_value=self.fake_network)
  157. self._start_mock(
  158. 'neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_port',
  159. return_value=self.fake_router_port)
  160. self._start_mock(
  161. 'neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_subnet',
  162. return_value=self.fake_subnet)
  163. self._start_mock(
  164. 'neutron.db.l3_db.L3_NAT_dbonly_mixin.get_router',
  165. return_value=self.fake_router)
  166. self._start_mock(
  167. 'neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.update_router',
  168. return_value=self.fake_router)
  169. self._start_mock(
  170. 'neutron.db.l3_db.L3_NAT_dbonly_mixin.remove_router_interface',
  171. return_value=self.fake_router_interface_info)
  172. self._start_mock(
  173. 'neutron.db.l3_db.L3_NAT_dbonly_mixin.create_router',
  174. return_value=self.fake_router_with_ext_gw)
  175. self._start_mock(
  176. 'neutron.db.l3_db.L3_NAT_dbonly_mixin.delete_router',
  177. return_value={})
  178. self.mock_candidates = self._start_mock(
  179. 'networking_ovn.common.ovn_client.'
  180. 'OVNClient.get_candidates_for_scheduling',
  181. return_value=[])
  182. self.mock_schedule = self._start_mock(
  183. 'networking_ovn.l3.l3_ovn_scheduler.'
  184. 'OVNGatewayLeastLoadedScheduler._schedule_gateway',
  185. return_value=['hv1'])
  186. # FIXME(lucasagomes): We shouldn't be mocking the creation of
  187. # floating IPs here, that makes the FIP to not be registered in
  188. # the standardattributes table and therefore we also need to mock
  189. # bump_revision.
  190. self._start_mock(
  191. 'neutron.db.l3_db.L3_NAT_dbonly_mixin.create_floatingip',
  192. return_value=self.fake_floating_ip)
  193. self._start_mock(
  194. 'networking_ovn.db.revision.bump_revision',
  195. return_value=None)
  196. self._start_mock(
  197. 'neutron.db.l3_db.L3_NAT_dbonly_mixin._get_floatingip',
  198. return_value=self.fake_floating_ip)
  199. self._start_mock(
  200. 'networking_ovn.common.ovn_client.'
  201. 'OVNClient.update_floatingip_status',
  202. return_value=None)
  203. self.bump_rev_p = self._start_mock(
  204. 'networking_ovn.db.revision.bump_revision', return_value=None)
  205. self.del_rev_p = self._start_mock(
  206. 'networking_ovn.db.revision.delete_revision', return_value=None)
  207. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.add_router_interface')
  208. def test_add_router_interface(self, func):
  209. router_id = 'router-id'
  210. interface_info = {'port_id': 'router-port-id'}
  211. func.return_value = self.fake_router_interface_info
  212. self.l3_inst.add_router_interface(self.context, router_id,
  213. interface_info)
  214. self.l3_inst._ovn.add_lrouter_port.assert_called_once_with(
  215. **self.fake_router_port_assert)
  216. self.l3_inst._ovn.set_lrouter_port_in_lswitch_port.\
  217. assert_called_once_with(
  218. 'router-port-id', 'lrp-router-port-id', is_gw_port=False,
  219. lsp_address=ovn_const.DEFAULT_ADDR_FOR_LSP_WITH_PEER)
  220. self.bump_rev_p.assert_called_once_with(self.fake_router_port,
  221. ovn_const.TYPE_ROUTER_PORTS)
  222. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.add_router_interface')
  223. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_port')
  224. def test_add_router_interface_update_lrouter_port(self, getp, func):
  225. router_id = 'router-id'
  226. interface_info = {'port_id': 'router-port-id'}
  227. func.return_value = {'id': router_id,
  228. 'port_id': 'router-port-id',
  229. 'subnet_id': 'subnet-id1',
  230. 'subnet_ids': ['subnet-id1'],
  231. 'fixed_ips': [
  232. {'ip_address': '2001:db8::1',
  233. 'subnet_id': 'subnet-id1'},
  234. {'ip_address': '2001:dba::1',
  235. 'subnet_id': 'subnet-id2'}],
  236. 'mac_address': 'aa:aa:aa:aa:aa:aa'
  237. }
  238. getp.return_value = {
  239. 'id': 'router-port-id',
  240. 'fixed_ips': [
  241. {'ip_address': '2001:db8::1', 'subnet_id': 'subnet-id1'},
  242. {'ip_address': '2001:dba::1', 'subnet_id': 'subnet-id2'}],
  243. 'mac_address': 'aa:aa:aa:aa:aa:aa',
  244. 'network_id': 'network-id1'
  245. }
  246. fake_rtr_intf_networks = ['2001:db8::1/24', '2001:dba::1/24']
  247. self.l3_inst.add_router_interface(self.context, router_id,
  248. interface_info)
  249. called_args_dict = (
  250. self.l3_inst._ovn.update_lrouter_port.call_args_list[0][1])
  251. self.assertEqual(1, self.l3_inst._ovn.update_lrouter_port.call_count)
  252. self.assertItemsEqual(fake_rtr_intf_networks,
  253. called_args_dict.get('networks', []))
  254. self.l3_inst._ovn.set_lrouter_port_in_lswitch_port.\
  255. assert_called_once_with(
  256. 'router-port-id', 'lrp-router-port-id', is_gw_port=False,
  257. lsp_address=ovn_const.DEFAULT_ADDR_FOR_LSP_WITH_PEER)
  258. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_port')
  259. def test_remove_router_interface(self, getp):
  260. router_id = 'router-id'
  261. interface_info = {'port_id': 'router-port-id'}
  262. getp.side_effect = n_exc.PortNotFound(port_id='router-port-id')
  263. self.l3_inst.remove_router_interface(
  264. self.context, router_id, interface_info)
  265. self.l3_inst._ovn.lrp_del.assert_called_once_with(
  266. 'lrp-router-port-id', 'neutron-router-id', if_exists=True)
  267. self.del_rev_p.assert_called_once_with('router-port-id',
  268. ovn_const.TYPE_ROUTER_PORTS)
  269. def test_remove_router_interface_update_lrouter_port(self):
  270. router_id = 'router-id'
  271. interface_info = {'port_id': 'router-port-id'}
  272. self.l3_inst.remove_router_interface(
  273. self.context, router_id, interface_info)
  274. self.l3_inst._ovn.update_lrouter_port.assert_called_once_with(
  275. if_exists=False, name='lrp-router-port-id',
  276. ipv6_ra_configs={},
  277. networks=['10.0.0.100/24'],
  278. external_ids={
  279. ovn_const.OVN_SUBNET_EXT_IDS_KEY: 'subnet-id',
  280. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
  281. ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY:
  282. utils.ovn_name(self.fake_network['id'])})
  283. @mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.'
  284. 'update_router')
  285. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.get_router')
  286. @mock.patch('networking_ovn.common.ovn_client.OVNClient.'
  287. '_get_v4_network_of_all_router_ports')
  288. def test_update_router_admin_state_change(self, get_rps, get_r, func):
  289. router_id = 'router-id'
  290. get_r.return_value = self.fake_router
  291. new_router = self.fake_router.copy()
  292. updated_data = {'admin_state_up': True}
  293. new_router.update(updated_data)
  294. func.return_value = new_router
  295. self.l3_inst.update_router(self.context, router_id,
  296. {'router': updated_data})
  297. self.l3_inst._ovn.update_lrouter.assert_called_once_with(
  298. 'neutron-router-id', enabled=True, external_ids={
  299. ovn_const.OVN_GW_PORT_EXT_ID_KEY: '',
  300. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
  301. ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: 'router'})
  302. @mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.'
  303. 'update_router')
  304. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.get_router')
  305. @mock.patch('networking_ovn.common.ovn_client.OVNClient.'
  306. '_get_v4_network_of_all_router_ports')
  307. def test_update_router_name_change(self, get_rps, get_r, func):
  308. router_id = 'router-id'
  309. get_r.return_value = self.fake_router
  310. new_router = self.fake_router.copy()
  311. updated_data = {'name': 'test'}
  312. new_router.update(updated_data)
  313. func.return_value = new_router
  314. self.l3_inst.update_router(self.context, router_id,
  315. {'router': updated_data})
  316. self.l3_inst._ovn.update_lrouter.assert_called_once_with(
  317. 'neutron-router-id', enabled=False,
  318. external_ids={ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: 'test',
  319. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
  320. ovn_const.OVN_GW_PORT_EXT_ID_KEY: ''})
  321. @mock.patch.object(utils, 'get_lrouter_non_gw_routes')
  322. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.update_router')
  323. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin._get_router')
  324. @mock.patch('networking_ovn.common.ovn_client.OVNClient.'
  325. '_get_v4_network_of_all_router_ports')
  326. def test_update_router_static_route_no_change(self, get_rps, get_r, func,
  327. mock_routes):
  328. router_id = 'router-id'
  329. get_rps.return_value = [{'device_id': '',
  330. 'device_owner': 'network:router_interface',
  331. 'mac_address': 'aa:aa:aa:aa:aa:aa',
  332. 'fixed_ips': [{'ip_address': '10.0.0.100',
  333. 'subnet_id': 'subnet-id'}],
  334. 'id': 'router-port-id'}]
  335. mock_routes.return_value = self.fake_router['routes']
  336. update_data = {'router': {'routes': [{'destination': '1.1.1.0/24',
  337. 'nexthop': '10.0.0.2'}]}}
  338. self.l3_inst.update_router(self.context, router_id, update_data)
  339. self.assertFalse(self.l3_inst._ovn.add_static_route.called)
  340. self.assertFalse(self.l3_inst._ovn.delete_static_route.called)
  341. @mock.patch.object(utils, 'get_lrouter_non_gw_routes')
  342. @mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.'
  343. 'update_router')
  344. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.get_router')
  345. @mock.patch('networking_ovn.common.ovn_client.OVNClient.'
  346. '_get_v4_network_of_all_router_ports')
  347. def test_update_router_static_route_change(self, get_rps, get_r, func,
  348. mock_routes):
  349. router_id = 'router-id'
  350. get_rps.return_value = [{'device_id': '',
  351. 'device_owner': 'network:router_interface',
  352. 'mac_address': 'aa:aa:aa:aa:aa:aa',
  353. 'fixed_ips': [{'ip_address': '10.0.0.100',
  354. 'subnet_id': 'subnet-id'}],
  355. 'id': 'router-port-id'}]
  356. mock_routes.return_value = self.fake_router['routes']
  357. get_r.return_value = self.fake_router
  358. new_router = self.fake_router.copy()
  359. updated_data = {'routes': [{'destination': '2.2.2.0/24',
  360. 'nexthop': '10.0.0.3'}]}
  361. new_router.update(updated_data)
  362. func.return_value = new_router
  363. self.l3_inst.update_router(self.context, router_id,
  364. {'router': updated_data})
  365. self.l3_inst._ovn.add_static_route.assert_called_once_with(
  366. 'neutron-router-id',
  367. ip_prefix='2.2.2.0/24', nexthop='10.0.0.3')
  368. self.l3_inst._ovn.delete_static_route.assert_called_once_with(
  369. 'neutron-router-id',
  370. ip_prefix='1.1.1.0/24', nexthop='10.0.0.2')
  371. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_port')
  372. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_subnet')
  373. @mock.patch('networking_ovn.common.ovn_client.OVNClient.'
  374. '_get_v4_network_of_all_router_ports')
  375. def test_create_router_with_ext_gw(self, get_rps, get_subnet, get_port):
  376. self.l3_inst._ovn.is_col_present.return_value = True
  377. router = {'router': {'name': 'router'}}
  378. get_subnet.return_value = self.fake_ext_subnet
  379. get_port.return_value = self.fake_ext_gw_port
  380. get_rps.return_value = self.fake_ext_subnet['cidr']
  381. self.l3_inst.create_router(self.context, router)
  382. external_ids = {ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: 'router',
  383. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
  384. ovn_const.OVN_GW_PORT_EXT_ID_KEY: 'gw-port-id'}
  385. self.l3_inst._ovn.create_lrouter.assert_called_once_with(
  386. 'neutron-router-id', external_ids=external_ids,
  387. enabled=True, options={})
  388. self.l3_inst._ovn.add_lrouter_port.assert_called_once_with(
  389. **self.fake_ext_gw_port_assert)
  390. expected_calls = [
  391. mock.call('neutron-router-id', ip_prefix='0.0.0.0/0',
  392. nexthop='192.168.1.254',
  393. external_ids={
  394. ovn_const.OVN_ROUTER_IS_EXT_GW: 'true',
  395. ovn_const.OVN_SUBNET_EXT_ID_KEY: 'ext-subnet-id'})]
  396. self.l3_inst._ovn.set_lrouter_port_in_lswitch_port.\
  397. assert_called_once_with(
  398. 'gw-port-id', 'lrp-gw-port-id', is_gw_port=True,
  399. lsp_address=ovn_const.DEFAULT_ADDR_FOR_LSP_WITH_PEER)
  400. self.l3_inst._ovn.add_static_route.assert_has_calls(expected_calls)
  401. bump_rev_calls = [mock.call(self.fake_ext_gw_port,
  402. ovn_const.TYPE_ROUTER_PORTS),
  403. mock.call(self.fake_router_with_ext_gw,
  404. ovn_const.TYPE_ROUTERS),
  405. ]
  406. self.assertEqual(len(bump_rev_calls), self.bump_rev_p.call_count)
  407. self.bump_rev_p.assert_has_calls(bump_rev_calls, any_order=False)
  408. @mock.patch('networking_ovn.common.ovn_client.OVNClient._get_router_ports')
  409. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.get_router')
  410. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_subnet')
  411. def test_delete_router_with_ext_gw(self, gs, gr, gprs):
  412. gr.return_value = self.fake_router_with_ext_gw
  413. gs.return_value = self.fake_ext_subnet
  414. self.l3_inst.delete_router(self.context, 'router-id')
  415. self.l3_inst._ovn.delete_lrouter.assert_called_once_with(
  416. 'neutron-router-id')
  417. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_port')
  418. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_subnet')
  419. @mock.patch('networking_ovn.common.ovn_client.OVNClient._get_router_ports')
  420. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.get_router')
  421. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.add_router_interface')
  422. def test_add_router_interface_with_gateway_set(self, ari, gr, grps,
  423. gs, gp):
  424. router_id = 'router-id'
  425. interface_info = {'port_id': 'router-port-id'}
  426. ari.return_value = self.fake_router_interface_info
  427. gr.return_value = self.fake_router_with_ext_gw
  428. gs.return_value = self.fake_subnet
  429. gp.return_value = self.fake_router_port
  430. self.l3_inst.add_router_interface(self.context, router_id,
  431. interface_info)
  432. self.l3_inst._ovn.add_lrouter_port.assert_called_once_with(
  433. **self.fake_router_port_assert)
  434. self.l3_inst._ovn.set_lrouter_port_in_lswitch_port.\
  435. assert_called_once_with(
  436. 'router-port-id', 'lrp-router-port-id', is_gw_port=False,
  437. lsp_address=ovn_const.DEFAULT_ADDR_FOR_LSP_WITH_PEER)
  438. self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with(
  439. 'neutron-router-id', logical_ip='10.0.0.0/24',
  440. external_ip='192.168.1.1', type='snat')
  441. self.bump_rev_p.assert_called_with(self.fake_router_port,
  442. ovn_const.TYPE_ROUTER_PORTS)
  443. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_port')
  444. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_subnet')
  445. @mock.patch('networking_ovn.common.ovn_client.OVNClient._get_router_ports')
  446. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.get_router')
  447. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.add_router_interface')
  448. def test_add_router_interface_with_gateway_set_and_snat_disabled(
  449. self, ari, gr, grps, gs, gp):
  450. router_id = 'router-id'
  451. interface_info = {'port_id': 'router-port-id'}
  452. ari.return_value = self.fake_router_interface_info
  453. gr.return_value = self.fake_router_with_ext_gw
  454. gr.return_value['external_gateway_info']['enable_snat'] = False
  455. gs.return_value = self.fake_subnet
  456. gp.return_value = self.fake_router_port
  457. self.l3_inst.add_router_interface(self.context, router_id,
  458. interface_info)
  459. self.l3_inst._ovn.add_lrouter_port.assert_called_once_with(
  460. **self.fake_router_port_assert)
  461. self.l3_inst._ovn.set_lrouter_port_in_lswitch_port.\
  462. assert_called_once_with(
  463. 'router-port-id', 'lrp-router-port-id', is_gw_port=False,
  464. lsp_address=ovn_const.DEFAULT_ADDR_FOR_LSP_WITH_PEER)
  465. self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_not_called()
  466. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_network')
  467. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_port')
  468. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_subnet')
  469. @mock.patch('networking_ovn.common.ovn_client.OVNClient._get_router_ports')
  470. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.get_router')
  471. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.add_router_interface')
  472. def test_add_router_interface_vlan_network(self, ari, gr, grps, gs,
  473. gp, gn):
  474. router_id = 'router-id'
  475. interface_info = {'port_id': 'router-port-id'}
  476. ari.return_value = self.fake_router_interface_info
  477. gr.return_value = self.fake_router_with_ext_gw
  478. gs.return_value = self.fake_subnet
  479. gp.return_value = self.fake_router_port
  480. # Set the type to be VLAN
  481. fake_network_vlan = self.fake_network
  482. fake_network_vlan[pnet.NETWORK_TYPE] = constants.TYPE_VLAN
  483. gn.return_value = fake_network_vlan
  484. self.l3_inst.add_router_interface(self.context, router_id,
  485. interface_info)
  486. # Make sure that the "reside-on-redirect-chassis" option was
  487. # set to the new router port
  488. fake_router_port_assert = self.fake_router_port_assert
  489. fake_router_port_assert['options'] = {
  490. 'reside-on-redirect-chassis': 'true'}
  491. self.l3_inst._ovn.add_lrouter_port.assert_called_once_with(
  492. **fake_router_port_assert)
  493. self.l3_inst._ovn.set_lrouter_port_in_lswitch_port.\
  494. assert_called_once_with(
  495. 'router-port-id', 'lrp-router-port-id', is_gw_port=False,
  496. lsp_address=ovn_const.DEFAULT_ADDR_FOR_LSP_WITH_PEER)
  497. self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with(
  498. 'neutron-router-id', logical_ip='10.0.0.0/24',
  499. external_ip='192.168.1.1', type='snat')
  500. self.bump_rev_p.assert_called_with(self.fake_router_port,
  501. ovn_const.TYPE_ROUTER_PORTS)
  502. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_port')
  503. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_subnet')
  504. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.get_router')
  505. def test_remove_router_interface_with_gateway_set(self, gr, gs, gp):
  506. router_id = 'router-id'
  507. interface_info = {'port_id': 'router-port-id',
  508. 'subnet_id': 'subnet-id'}
  509. gr.return_value = self.fake_router_with_ext_gw
  510. gs.return_value = self.fake_subnet
  511. gp.side_effect = n_exc.PortNotFound(port_id='router-port-id')
  512. self.l3_inst.remove_router_interface(
  513. self.context, router_id, interface_info)
  514. self.l3_inst._ovn.lrp_del.assert_called_once_with(
  515. 'lrp-router-port-id', 'neutron-router-id', if_exists=True)
  516. self.l3_inst._ovn.delete_nat_rule_in_lrouter.assert_called_once_with(
  517. 'neutron-router-id', logical_ip='10.0.0.0/24',
  518. external_ip='192.168.1.1', type='snat')
  519. self.del_rev_p.assert_called_with('router-port-id',
  520. ovn_const.TYPE_ROUTER_PORTS)
  521. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_port')
  522. @mock.patch('networking_ovn.common.ovn_client.OVNClient._get_router_ports')
  523. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_subnet')
  524. @mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.'
  525. 'update_router')
  526. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.get_router')
  527. def test_update_router_with_ext_gw(self, gr, ur, gs, grps, gp):
  528. self.l3_inst._ovn.is_col_present.return_value = True
  529. router = {'router': {'name': 'router'}}
  530. gr.return_value = self.fake_router_without_ext_gw
  531. ur.return_value = self.fake_router_with_ext_gw
  532. gs.side_effect = lambda ctx, sid: {
  533. 'ext-subnet-id': self.fake_ext_subnet}.get(sid, self.fake_subnet)
  534. gp.return_value = self.fake_ext_gw_port
  535. grps.return_value = self.fake_router_ports
  536. self.l3_inst.update_router(self.context, 'router-id', router)
  537. self.l3_inst._ovn.add_lrouter_port.assert_called_once_with(
  538. **self.fake_ext_gw_port_assert)
  539. self.l3_inst._ovn.set_lrouter_port_in_lswitch_port.\
  540. assert_called_once_with(
  541. 'gw-port-id', 'lrp-gw-port-id', is_gw_port=True,
  542. lsp_address=ovn_const.DEFAULT_ADDR_FOR_LSP_WITH_PEER)
  543. self.l3_inst._ovn.add_static_route.assert_called_once_with(
  544. 'neutron-router-id', ip_prefix='0.0.0.0/0',
  545. external_ids={ovn_const.OVN_ROUTER_IS_EXT_GW: 'true',
  546. ovn_const.OVN_SUBNET_EXT_ID_KEY: 'ext-subnet-id'},
  547. nexthop='192.168.1.254')
  548. self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with(
  549. 'neutron-router-id', type='snat',
  550. logical_ip='10.0.0.0/24', external_ip='192.168.1.1')
  551. self.bump_rev_p.assert_called_with(self.fake_ext_gw_port,
  552. ovn_const.TYPE_ROUTER_PORTS)
  553. @mock.patch.object(utils, 'get_lrouter_ext_gw_static_route')
  554. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_port')
  555. @mock.patch('networking_ovn.common.ovn_client.OVNClient._get_router_ports')
  556. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_subnet')
  557. @mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.'
  558. 'update_router')
  559. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.get_router')
  560. def test_update_router_ext_gw_change_subnet(self, gr, ur, gs,
  561. grps, gp, mock_get_gw):
  562. self.l3_inst._ovn.is_col_present.return_value = True
  563. mock_get_gw.return_value = [mock.sentinel.GwRoute]
  564. router = {'router': {'name': 'router'}}
  565. fake_old_ext_subnet = {'id': 'old-ext-subnet-id',
  566. 'ip_version': 4,
  567. 'cidr': '192.168.2.0/24',
  568. 'gateway_ip': '192.168.2.254'}
  569. # Old gateway info with same network and different subnet
  570. gr.return_value = copy.copy(self.fake_router_with_ext_gw)
  571. gr.return_value['external_gateway_info'] = {
  572. 'network_id': 'ext-network-id',
  573. 'external_fixed_ips': [{'ip_address': '192.168.2.1',
  574. 'subnet_id': 'old-ext-subnet-id'}]}
  575. gr.return_value['gw_port_id'] = 'old-gw-port-id'
  576. ur.return_value = self.fake_router_with_ext_gw
  577. gs.side_effect = lambda ctx, sid: {
  578. 'ext-subnet-id': self.fake_ext_subnet,
  579. 'old-ext-subnet-id': fake_old_ext_subnet}.get(sid,
  580. self.fake_subnet)
  581. gp.return_value = self.fake_ext_gw_port
  582. grps.return_value = self.fake_router_ports
  583. self.l3_inst.update_router(self.context, 'router-id', router)
  584. # Check deleting old router gateway
  585. self.l3_inst._ovn.delete_lrouter_ext_gw.assert_called_once_with(
  586. 'neutron-router-id')
  587. # Check adding new router gateway
  588. self.l3_inst._ovn.add_lrouter_port.assert_called_once_with(
  589. **self.fake_ext_gw_port_assert)
  590. self.l3_inst._ovn.set_lrouter_port_in_lswitch_port.\
  591. assert_called_once_with(
  592. 'gw-port-id', 'lrp-gw-port-id', is_gw_port=True,
  593. lsp_address=ovn_const.DEFAULT_ADDR_FOR_LSP_WITH_PEER)
  594. self.l3_inst._ovn.add_static_route.assert_called_once_with(
  595. 'neutron-router-id', ip_prefix='0.0.0.0/0',
  596. nexthop='192.168.1.254',
  597. external_ids={ovn_const.OVN_ROUTER_IS_EXT_GW: 'true',
  598. ovn_const.OVN_SUBNET_EXT_ID_KEY: 'ext-subnet-id'})
  599. self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with(
  600. 'neutron-router-id', type='snat', logical_ip='10.0.0.0/24',
  601. external_ip='192.168.1.1')
  602. self.bump_rev_p.assert_called_with(self.fake_ext_gw_port,
  603. ovn_const.TYPE_ROUTER_PORTS)
  604. self.del_rev_p.assert_called_once_with('old-gw-port-id',
  605. ovn_const.TYPE_ROUTER_PORTS)
  606. @mock.patch.object(utils, 'get_lrouter_ext_gw_static_route')
  607. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_port')
  608. @mock.patch('networking_ovn.common.ovn_client.OVNClient._get_router_ports')
  609. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_subnet')
  610. @mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.'
  611. 'update_router')
  612. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.get_router')
  613. def test_update_router_ext_gw_change_ip_address(self, gr, ur, gs,
  614. grps, gp, mock_get_gw):
  615. self.l3_inst._ovn.is_col_present.return_value = True
  616. mock_get_gw.return_value = [mock.sentinel.GwRoute]
  617. router = {'router': {'name': 'router'}}
  618. # Old gateway info with same subnet and different ip address
  619. gr_value = copy.deepcopy(self.fake_router_with_ext_gw)
  620. gr_value['external_gateway_info'][
  621. 'external_fixed_ips'][0]['ip_address'] = '192.168.1.2'
  622. gr_value['gw_port_id'] = 'old-gw-port-id'
  623. gr.return_value = gr_value
  624. ur.return_value = self.fake_router_with_ext_gw
  625. gs.side_effect = lambda ctx, sid: {
  626. 'ext-subnet-id': self.fake_ext_subnet}.get(sid, self.fake_subnet)
  627. gp.return_value = self.fake_ext_gw_port
  628. grps.return_value = self.fake_router_ports
  629. self.l3_inst.update_router(self.context, 'router-id', router)
  630. # Check deleting old router gateway
  631. self.l3_inst._ovn.delete_lrouter_ext_gw.assert_called_once_with(
  632. 'neutron-router-id')
  633. # Check adding new router gateway
  634. self.l3_inst._ovn.add_lrouter_port.assert_called_once_with(
  635. **self.fake_ext_gw_port_assert)
  636. self.l3_inst._ovn.set_lrouter_port_in_lswitch_port.\
  637. assert_called_once_with(
  638. 'gw-port-id', 'lrp-gw-port-id', is_gw_port=True,
  639. lsp_address=ovn_const.DEFAULT_ADDR_FOR_LSP_WITH_PEER)
  640. self.l3_inst._ovn.add_static_route.assert_called_once_with(
  641. 'neutron-router-id', ip_prefix='0.0.0.0/0',
  642. nexthop='192.168.1.254',
  643. external_ids={ovn_const.OVN_ROUTER_IS_EXT_GW: 'true',
  644. ovn_const.OVN_SUBNET_EXT_ID_KEY: 'ext-subnet-id'})
  645. self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with(
  646. 'neutron-router-id', type='snat', logical_ip='10.0.0.0/24',
  647. external_ip='192.168.1.1')
  648. @mock.patch('networking_ovn.common.ovn_client.OVNClient.'
  649. '_get_v4_network_of_all_router_ports')
  650. @mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.'
  651. 'update_router')
  652. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.get_router')
  653. def test_update_router_ext_gw_no_change(self, gr, ur, get_rps):
  654. router = {'router': {'name': 'router'}}
  655. gr.return_value = self.fake_router_with_ext_gw
  656. ur.return_value = self.fake_router_with_ext_gw
  657. self.l3_inst._ovn.get_lrouter.return_value = (
  658. fakes.FakeOVNRouter.from_neutron_router(
  659. self.fake_router_with_ext_gw))
  660. self.l3_inst.update_router(self.context, 'router-id', router)
  661. self.l3_inst._ovn.lrp_del.assert_not_called()
  662. self.l3_inst._ovn.delete_static_route.assert_not_called()
  663. self.l3_inst._ovn.delete_nat_rule_in_lrouter.assert_not_called()
  664. self.l3_inst._ovn.add_lrouter_port.assert_not_called()
  665. self.l3_inst._ovn.set_lrouter_port_in_lswitch_port.assert_not_called()
  666. self.l3_inst._ovn.add_static_route.assert_not_called()
  667. self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_not_called()
  668. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_port')
  669. @mock.patch('networking_ovn.common.ovn_client.OVNClient.'
  670. '_get_v4_network_of_all_router_ports')
  671. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_subnet')
  672. @mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.'
  673. 'update_router')
  674. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.get_router')
  675. def test_update_router_with_ext_gw_and_disabled_snat(self, gr, ur,
  676. gs, grps, gp):
  677. self.l3_inst._ovn.is_col_present.return_value = True
  678. router = {'router': {'name': 'router'}}
  679. gr.return_value = self.fake_router_without_ext_gw
  680. ur.return_value = self.fake_router_with_ext_gw
  681. ur.return_value['external_gateway_info']['enable_snat'] = False
  682. gs.side_effect = lambda ctx, sid: {
  683. 'ext-subnet-id': self.fake_ext_subnet}.get(sid, self.fake_subnet)
  684. gp.return_value = self.fake_ext_gw_port
  685. grps.return_value = self.fake_router_ports
  686. self.l3_inst.update_router(self.context, 'router-id', router)
  687. # Need not check lsp and lrp here, it has been tested in other cases
  688. self.l3_inst._ovn.add_static_route.assert_called_once_with(
  689. 'neutron-router-id', ip_prefix='0.0.0.0/0',
  690. external_ids={ovn_const.OVN_ROUTER_IS_EXT_GW: 'true',
  691. ovn_const.OVN_SUBNET_EXT_ID_KEY: 'ext-subnet-id'},
  692. nexthop='192.168.1.254')
  693. self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_not_called()
  694. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_port')
  695. @mock.patch('networking_ovn.common.ovn_client.OVNClient._get_router_ports')
  696. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_subnet')
  697. @mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.'
  698. 'update_router')
  699. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.get_router')
  700. def test_enable_snat(self, gr, ur, gs, grps, gp):
  701. router = {'router': {'name': 'router'}}
  702. gr.return_value = copy.deepcopy(self.fake_router_with_ext_gw)
  703. gr.return_value['external_gateway_info']['enable_snat'] = False
  704. ur.return_value = self.fake_router_with_ext_gw
  705. self.l3_inst._ovn.get_lrouter.return_value = (
  706. fakes.FakeOVNRouter.from_neutron_router(
  707. self.fake_router_with_ext_gw))
  708. gs.side_effect = lambda ctx, sid: {
  709. 'ext-subnet-id': self.fake_ext_subnet}.get(sid, self.fake_subnet)
  710. gp.return_value = self.fake_ext_gw_port
  711. grps.return_value = self.fake_router_ports
  712. self.l3_inst.update_router(self.context, 'router-id', router)
  713. self.l3_inst._ovn.delete_static_route.assert_not_called()
  714. self.l3_inst._ovn.delete_nat_rule_in_lrouter.assert_not_called()
  715. self.l3_inst._ovn.add_static_route.assert_not_called()
  716. self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with(
  717. 'neutron-router-id', type='snat', logical_ip='10.0.0.0/24',
  718. external_ip='192.168.1.1')
  719. @mock.patch('networking_ovn.common.ovn_client.OVNClient.'
  720. '_check_external_ips_changed')
  721. @mock.patch.object(utils, 'get_lrouter_snats')
  722. @mock.patch.object(utils, 'get_lrouter_ext_gw_static_route')
  723. @mock.patch('networking_ovn.common.utils.is_snat_enabled',
  724. mock.Mock(return_value=True))
  725. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_port')
  726. @mock.patch('networking_ovn.common.ovn_client.OVNClient.'
  727. '_get_router_ports')
  728. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_subnet')
  729. @mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.'
  730. 'update_router')
  731. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.get_router')
  732. def test_disable_snat(self, gr, ur, gs, grps, gp, mock_get_gw, mock_snats,
  733. mock_ext_ips):
  734. mock_get_gw.return_value = [mock.sentinel.GwRoute]
  735. mock_snats.return_value = [mock.sentinel.NAT]
  736. mock_ext_ips.return_value = False
  737. router = {'router': {'name': 'router'}}
  738. gr.return_value = self.fake_router_with_ext_gw
  739. ur.return_value = copy.deepcopy(self.fake_router_with_ext_gw)
  740. ur.return_value['external_gateway_info']['enable_snat'] = False
  741. gs.side_effect = lambda ctx, sid: {
  742. 'ext-subnet-id': self.fake_ext_subnet}.get(sid, self.fake_subnet)
  743. gp.return_value = self.fake_ext_gw_port
  744. grps.return_value = self.fake_router_ports
  745. self.l3_inst.update_router(self.context, 'router-id', router)
  746. self.l3_inst._ovn.delete_static_route.assert_not_called()
  747. self.l3_inst._ovn.delete_nat_rule_in_lrouter.assert_called_once_with(
  748. 'neutron-router-id', type='snat', logical_ip='10.0.0.0/24',
  749. external_ip='192.168.1.1')
  750. self.l3_inst._ovn.add_static_route.assert_not_called()
  751. self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_not_called()
  752. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin._get_floatingip')
  753. def test_create_floatingip(self, gf):
  754. self.l3_inst._ovn.is_col_present.return_value = True
  755. gf.return_value = {'floating_port_id': 'fip-port-id'}
  756. self.l3_inst.create_floatingip(self.context, 'floatingip')
  757. expected_ext_ids = {
  758. ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip['id'],
  759. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
  760. ovn_const.OVN_FIP_PORT_EXT_ID_KEY:
  761. self.fake_floating_ip['port_id'],
  762. ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: utils.ovn_name(
  763. self.fake_floating_ip['router_id'])}
  764. self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with(
  765. 'neutron-router-id',
  766. type='dnat_and_snat',
  767. logical_ip='10.0.0.10',
  768. external_ip='192.168.0.10',
  769. external_ids=expected_ext_ids)
  770. self.l3_inst._ovn.delete_lswitch_port.assert_called_once_with(
  771. 'fip-port-id', 'neutron-fip-net-id')
  772. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_port')
  773. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin._get_floatingip')
  774. def test_create_floatingip_distributed(self, gf, gp):
  775. self.l3_inst._ovn.is_col_present.return_value = True
  776. gp.return_value = {'mac_address': '00:01:02:03:04:05',
  777. 'network_id': 'port-network-id'}
  778. gf.return_value = {'floating_port_id': 'fip-port-id'}
  779. config.cfg.CONF.set_override(
  780. 'enable_distributed_floating_ip', True, group='ovn')
  781. self.l3_inst.create_floatingip(self.context, 'floatingip')
  782. expected_ext_ids = {
  783. ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip['id'],
  784. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
  785. ovn_const.OVN_FIP_PORT_EXT_ID_KEY:
  786. self.fake_floating_ip['port_id'],
  787. ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: utils.ovn_name(
  788. self.fake_floating_ip['router_id']),
  789. ovn_const.OVN_FIP_EXT_MAC_KEY: '00:01:02:03:04:05'}
  790. self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with(
  791. 'neutron-router-id', type='dnat_and_snat', logical_ip='10.0.0.10',
  792. external_ip='192.168.0.10', external_mac='00:01:02:03:04:05',
  793. logical_port='port_id',
  794. external_ids=expected_ext_ids)
  795. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_port')
  796. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin._get_floatingip')
  797. def test_create_floatingip_distributed_logical_port_down(self, gf, gp):
  798. # Check that when the port is down, the external_mac field is not
  799. # populated. This falls back to centralized routing for ports that
  800. # are not bound to a chassis.
  801. self.l3_inst._ovn.is_col_present.return_value = True
  802. self.l3_inst._ovn.lsp_get_up.return_value.execute.return_value = (
  803. False)
  804. gp.return_value = {'mac_address': '00:01:02:03:04:05'}
  805. gf.return_value = {'floating_port_id': 'fip-port-id'}
  806. config.cfg.CONF.set_override(
  807. 'enable_distributed_floating_ip', True, group='ovn')
  808. self.l3_inst.create_floatingip(self.context, 'floatingip')
  809. expected_ext_ids = {
  810. ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip['id'],
  811. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
  812. ovn_const.OVN_FIP_PORT_EXT_ID_KEY:
  813. self.fake_floating_ip['port_id'],
  814. ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: utils.ovn_name(
  815. self.fake_floating_ip['router_id']),
  816. ovn_const.OVN_FIP_EXT_MAC_KEY: '00:01:02:03:04:05'}
  817. self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with(
  818. 'neutron-router-id', type='dnat_and_snat', logical_ip='10.0.0.10',
  819. external_ip='192.168.0.10',
  820. logical_port='port_id',
  821. external_ids=expected_ext_ids)
  822. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin._get_floatingip')
  823. def test_create_floatingip_external_ip_present_in_nat_rule(self, gf):
  824. self.l3_inst._ovn.is_col_present.return_value = True
  825. gf.return_value = {'floating_port_id': 'fip-port-id'}
  826. self.l3_inst._ovn.get_lrouter_nat_rules.return_value = [
  827. {'external_ip': '192.168.0.10', 'logical_ip': '10.0.0.6',
  828. 'type': 'dnat_and_snat', 'uuid': 'uuid1'}]
  829. self.l3_inst.create_floatingip(self.context, 'floatingip')
  830. expected_ext_ids = {
  831. ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip['id'],
  832. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
  833. ovn_const.OVN_FIP_PORT_EXT_ID_KEY:
  834. self.fake_floating_ip['port_id'],
  835. ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: utils.ovn_name(
  836. self.fake_floating_ip['router_id'])}
  837. self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with(
  838. 'neutron-router-id',
  839. type='dnat_and_snat',
  840. logical_ip='10.0.0.10',
  841. external_ip='192.168.0.10',
  842. external_ids=expected_ext_ids)
  843. self.l3_inst._ovn.delete_lswitch_port.assert_called_once_with(
  844. 'fip-port-id', 'neutron-fip-net-id')
  845. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin._get_floatingip')
  846. def test_create_floatingip_external_ip_present_type_snat(self, gf):
  847. self.l3_inst._ovn.is_col_present.return_value = True
  848. gf.return_value = {'floating_port_id': 'fip-port-id'}
  849. self.l3_inst._ovn.get_lrouter_nat_rules.return_value = [
  850. {'external_ip': '192.168.0.10', 'logical_ip': '10.0.0.0/24',
  851. 'type': 'snat', 'uuid': 'uuid1'}]
  852. self.l3_inst.create_floatingip(self.context, 'floatingip')
  853. self.l3_inst._ovn.set_nat_rule_in_lrouter.assert_not_called()
  854. expected_ext_ids = {
  855. ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip['id'],
  856. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
  857. ovn_const.OVN_FIP_PORT_EXT_ID_KEY:
  858. self.fake_floating_ip['port_id'],
  859. ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: utils.ovn_name(
  860. self.fake_floating_ip['router_id'])}
  861. self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with(
  862. 'neutron-router-id',
  863. type='dnat_and_snat',
  864. logical_ip='10.0.0.10',
  865. external_ip='192.168.0.10',
  866. external_ids=expected_ext_ids)
  867. self.l3_inst._ovn.delete_lswitch_port.assert_called_once_with(
  868. 'fip-port-id', 'neutron-fip-net-id')
  869. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin._get_floatingip')
  870. def test_create_floatingip_lsp_external_id(self, gf):
  871. foo_lport = fakes.FakeOvsdbRow.create_one_ovsdb_row()
  872. foo_lport.uuid = 'foo-port'
  873. self.l3_inst._ovn.get_lswitch_port.return_value = foo_lport
  874. self.l3_inst.create_floatingip(self.context, 'floatingip')
  875. calls = [mock.call(
  876. 'Logical_Switch_Port',
  877. 'foo-port',
  878. ('external_ids', {ovn_const.OVN_PORT_FIP_EXT_ID_KEY:
  879. '192.168.0.10'}))]
  880. self.l3_inst._ovn.db_set.assert_has_calls(calls)
  881. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.delete_floatingip')
  882. def test_delete_floatingip(self, df):
  883. self.l3_inst._ovn.get_floatingip.return_value = (
  884. self.fake_ovn_nat_rule)
  885. self.l3_inst.delete_floatingip(self.context, 'floatingip-id')
  886. self.l3_inst._ovn.delete_nat_rule_in_lrouter.assert_called_once_with(
  887. 'neutron-router-id',
  888. type='dnat_and_snat',
  889. logical_ip='10.0.0.10',
  890. external_ip='192.168.0.10')
  891. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin._get_floatingip')
  892. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.delete_floatingip')
  893. def test_delete_floatingip_lsp_external_id(self, df, gf):
  894. gf.return_value = self.fake_floating_ip
  895. self.l3_inst._ovn.get_floatingip.return_value = (
  896. self.fake_ovn_nat_rule)
  897. foo_lport = fakes.FakeOvsdbRow.create_one_ovsdb_row()
  898. foo_lport.uuid = 'foo-port'
  899. foo_lport.external_ids = {
  900. ovn_const.OVN_PORT_FIP_EXT_ID_KEY: 'foo-port'}
  901. self.l3_inst._ovn.get_lswitch_port.return_value = foo_lport
  902. self.l3_inst.delete_floatingip(self.context, 'floatingip-id')
  903. calls = [mock.call(
  904. 'Logical_Switch_Port', 'foo-port',
  905. 'external_ids', ovn_const.OVN_PORT_FIP_EXT_ID_KEY)]
  906. self.l3_inst._ovn.db_remove.assert_has_calls(calls)
  907. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin._get_floatingip')
  908. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.delete_floatingip')
  909. def test_delete_floatingip_no_lsp_external_id(self, df, gf):
  910. gf.return_value = self.fake_floating_ip
  911. self.l3_inst._ovn.get_floatingip.return_value = (
  912. self.fake_ovn_nat_rule)
  913. self.l3_inst._ovn.get_lswitch_port.return_value = None
  914. self.l3_inst.delete_floatingip(self.context, 'floatingip-id')
  915. self.l3_inst._ovn.db_remove.assert_not_called()
  916. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin._get_floatingip')
  917. @mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.'
  918. 'update_floatingip')
  919. def test_update_floatingip(self, uf, gf):
  920. self.l3_inst._ovn.is_col_present.return_value = True
  921. gf.return_value = self.fake_floating_ip
  922. uf.return_value = self.fake_floating_ip_new
  923. self.l3_inst._ovn.get_floatingip.return_value = (
  924. self.fake_ovn_nat_rule)
  925. self.l3_inst.update_floatingip(self.context, 'id', 'floatingip')
  926. self.l3_inst._ovn.delete_nat_rule_in_lrouter.assert_called_once_with(
  927. 'neutron-router-id',
  928. type='dnat_and_snat',
  929. logical_ip='10.0.0.10',
  930. external_ip='192.168.0.10')
  931. expected_ext_ids = {
  932. ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip_new['id'],
  933. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
  934. ovn_const.OVN_FIP_PORT_EXT_ID_KEY:
  935. self.fake_floating_ip_new['port_id'],
  936. ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: utils.ovn_name(
  937. self.fake_floating_ip_new['router_id'])}
  938. self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with(
  939. 'neutron-new-router-id',
  940. type='dnat_and_snat',
  941. logical_ip='10.10.10.10',
  942. external_ip='192.168.0.10',
  943. external_ids=expected_ext_ids)
  944. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin._get_floatingip')
  945. @mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.'
  946. 'update_floatingip')
  947. def test_update_floatingip_associate(self, uf, gf):
  948. self.l3_inst._ovn.is_col_present.return_value = True
  949. self.fake_floating_ip.update({'fixed_port_id': None})
  950. gf.return_value = self.fake_floating_ip
  951. uf.return_value = self.fake_floating_ip_new
  952. self.l3_inst.update_floatingip(self.context, 'id', 'floatingip')
  953. self.l3_inst._ovn.delete_nat_rule_in_lrouter.assert_not_called()
  954. expected_ext_ids = {
  955. ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip_new['id'],
  956. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
  957. ovn_const.OVN_FIP_PORT_EXT_ID_KEY:
  958. self.fake_floating_ip_new['port_id'],
  959. ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: utils.ovn_name(
  960. self.fake_floating_ip_new['router_id'])}
  961. self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with(
  962. 'neutron-new-router-id',
  963. type='dnat_and_snat',
  964. logical_ip='10.10.10.10',
  965. external_ip='192.168.0.10',
  966. external_ids=expected_ext_ids)
  967. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_network')
  968. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_port')
  969. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin._get_floatingip')
  970. @mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.'
  971. 'update_floatingip')
  972. def _test_update_floatingip_associate_distributed(self, network_type,
  973. uf, gf, gp, gn):
  974. self.l3_inst._ovn.is_col_present.return_value = True
  975. self.fake_floating_ip.update({'fixed_port_id': None})
  976. gp.return_value = {'mac_address': '00:01:02:03:04:05',
  977. 'network_id': 'port-network-id'}
  978. gf.return_value = self.fake_floating_ip
  979. uf.return_value = self.fake_floating_ip_new
  980. fake_network_vlan = self.fake_network
  981. fake_network_vlan[pnet.NETWORK_TYPE] = network_type
  982. gn.return_value = fake_network_vlan
  983. config.cfg.CONF.set_override(
  984. 'enable_distributed_floating_ip', True, group='ovn')
  985. self.l3_inst.update_floatingip(self.context, 'id', 'floatingip')
  986. self.l3_inst._ovn.delete_nat_rule_in_lrouter.assert_not_called()
  987. expected_ext_ids = {
  988. ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip_new['id'],
  989. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
  990. ovn_const.OVN_FIP_PORT_EXT_ID_KEY:
  991. self.fake_floating_ip_new['port_id'],
  992. ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: utils.ovn_name(
  993. self.fake_floating_ip_new['router_id']),
  994. ovn_const.OVN_FIP_EXT_MAC_KEY: '00:01:02:03:04:05'}
  995. if network_type == constants.TYPE_VLAN:
  996. self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with(
  997. 'neutron-new-router-id', type='dnat_and_snat',
  998. logical_ip='10.10.10.10', external_ip='192.168.0.10',
  999. logical_port='new-port_id', external_ids=expected_ext_ids)
  1000. else:
  1001. self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with(
  1002. 'neutron-new-router-id', type='dnat_and_snat',
  1003. logical_ip='10.10.10.10', external_ip='192.168.0.10',
  1004. external_mac='00:01:02:03:04:05', logical_port='new-port_id',
  1005. external_ids=expected_ext_ids)
  1006. def test_update_floatingip_associate_distributed_flat(self):
  1007. self._test_update_floatingip_associate_distributed(constants.TYPE_FLAT)
  1008. def test_update_floatingip_associate_distributed_vlan(self):
  1009. self._test_update_floatingip_associate_distributed(constants.TYPE_VLAN)
  1010. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin._get_floatingip')
  1011. @mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.'
  1012. 'update_floatingip')
  1013. def test_update_floatingip_association_empty_update(self, uf, gf):
  1014. self.l3_inst._ovn.is_col_present.return_value = True
  1015. self.l3_inst._ovn.get_floatingip.return_value = (
  1016. self.fake_ovn_nat_rule)
  1017. self.fake_floating_ip.update({'fixed_port_id': 'foo'})
  1018. self.fake_floating_ip_new.update({'port_id': 'foo'})
  1019. gf.return_value = self.fake_floating_ip
  1020. uf.return_value = self.fake_floating_ip_new
  1021. self.l3_inst.update_floatingip(self.context, 'id', 'floatingip')
  1022. self.l3_inst._ovn.delete_nat_rule_in_lrouter.assert_called_once_with(
  1023. 'neutron-router-id',
  1024. type='dnat_and_snat',
  1025. logical_ip='10.0.0.10',
  1026. external_ip='192.168.0.10')
  1027. expected_ext_ids = {
  1028. ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip_new['id'],
  1029. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
  1030. ovn_const.OVN_FIP_PORT_EXT_ID_KEY:
  1031. self.fake_floating_ip_new['port_id'],
  1032. ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: utils.ovn_name(
  1033. self.fake_floating_ip_new['router_id'])}
  1034. self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with(
  1035. 'neutron-new-router-id',
  1036. type='dnat_and_snat',
  1037. logical_ip='10.10.10.10',
  1038. external_ip='192.168.0.10',
  1039. external_ids=expected_ext_ids)
  1040. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin._get_floatingip')
  1041. @mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.'
  1042. 'update_floatingip')
  1043. def test_update_floatingip_reassociate_to_same_port_diff_fixed_ip(
  1044. self, uf, gf):
  1045. self.l3_inst._ovn.is_col_present.return_value = True
  1046. self.l3_inst._ovn.get_floatingip.return_value = (
  1047. self.fake_ovn_nat_rule)
  1048. self.fake_floating_ip_new.update({'port_id': 'port_id',
  1049. 'fixed_port_id': 'port_id'})
  1050. gf.return_value = self.fake_floating_ip
  1051. uf.return_value = self.fake_floating_ip_new
  1052. self.l3_inst.update_floatingip(self.context, 'id', 'floatingip')
  1053. self.l3_inst._ovn.delete_nat_rule_in_lrouter.assert_called_once_with(
  1054. 'neutron-router-id',
  1055. type='dnat_and_snat',
  1056. logical_ip='10.0.0.10',
  1057. external_ip='192.168.0.10')
  1058. expected_ext_ids = {
  1059. ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip_new['id'],
  1060. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
  1061. ovn_const.OVN_FIP_PORT_EXT_ID_KEY:
  1062. self.fake_floating_ip_new['port_id'],
  1063. ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: utils.ovn_name(
  1064. self.fake_floating_ip_new['router_id'])}
  1065. self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with(
  1066. 'neutron-new-router-id',
  1067. type='dnat_and_snat',
  1068. logical_ip='10.10.10.10',
  1069. external_ip='192.168.0.10',
  1070. external_ids=expected_ext_ids)
  1071. @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.get_floatingips')
  1072. def test_disassociate_floatingips(self, gfs):
  1073. gfs.return_value = [{'id': 'fip-id1',
  1074. 'floating_ip_address': '192.168.0.10',
  1075. 'router_id': 'router-id',
  1076. 'port_id': 'port_id',
  1077. 'floating_port_id': 'fip-port-id1',
  1078. 'fixed_ip_address': '10.0.0.10'},
  1079. {'id': 'fip-id2',
  1080. 'floating_ip_address': '192.167.0.10',
  1081. 'router_id': 'router-id',
  1082. 'port_id': 'port_id',
  1083. 'floating_port_id': 'fip-port-id2',
  1084. 'fixed_ip_address': '10.0.0.11'}]
  1085. self.l3_inst.disassociate_floatingips(self.context, 'port_id',
  1086. do_notify=False)
  1087. delete_nat_calls = [mock.call('neutron-router-id',
  1088. type='dnat_and_snat',
  1089. logical_ip=fip['fixed_ip_address'],
  1090. external_ip=fip['floating_ip_address'])
  1091. for fip in gfs.return_value]
  1092. self.assertEqual(
  1093. len(delete_nat_calls),
  1094. self.l3_inst._ovn.delete_nat_rule_in_lrouter.call_count)
  1095. self.l3_inst._ovn.delete_nat_rule_in_lrouter.assert_has_calls(
  1096. delete_nat_calls, any_order=True)
  1097. @mock.patch('networking_ovn.common.ovn_client.OVNClient'
  1098. '.update_router_port')
  1099. def test_port_update_postcommit(self, update_rp_mock):
  1100. kwargs = {'port': {'device_owner': 'foo'}}
  1101. self.l3_inst._port_update(resources.PORT, events.AFTER_UPDATE, None,
  1102. **kwargs)
  1103. update_rp_mock.assert_not_called()
  1104. kwargs = {'port': {'device_owner': constants.DEVICE_OWNER_ROUTER_INTF}}
  1105. self.l3_inst._port_update(resources.PORT, events.AFTER_UPDATE, None,
  1106. **kwargs)
  1107. update_rp_mock.assert_called_once_with(kwargs['port'], if_exists=True)
  1108. @mock.patch('neutron.plugins.ml2.plugin.Ml2Plugin.update_port_status')
  1109. @mock.patch('neutron.plugins.ml2.plugin.Ml2Plugin.update_port')
  1110. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_ports')
  1111. def test_update_router_gateway_port_bindings_active(
  1112. self, mock_get_port, mock_updt_port, mock_updt_status):
  1113. fake_host = 'fake-host'
  1114. fake_router = 'fake-router'
  1115. fake_port_id = 'fake-port-id'
  1116. mock_get_port.return_value = [{
  1117. 'id': fake_port_id,
  1118. 'status': constants.PORT_STATUS_DOWN}]
  1119. self.l3_inst.update_router_gateway_port_bindings(
  1120. fake_router, fake_host)
  1121. # Assert that the port is being bound
  1122. expected_update = {'port': {portbindings.HOST_ID: fake_host}}
  1123. mock_updt_port.assert_called_once_with(
  1124. mock.ANY, fake_port_id, expected_update)
  1125. # Assert that the port status is being set to ACTIVE
  1126. mock_updt_status.assert_called_once_with(
  1127. mock.ANY, fake_port_id, constants.PORT_STATUS_ACTIVE)
  1128. @mock.patch('neutron.plugins.ml2.plugin.Ml2Plugin.update_port_status')
  1129. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_ports')
  1130. def test_update_router_gateway_port_bindings_down(
  1131. self, mock_get_port, mock_updt_status):
  1132. fake_port_id = 'fake-port-id'
  1133. mock_get_port.return_value = [{
  1134. 'id': fake_port_id,
  1135. 'status': constants.PORT_STATUS_ACTIVE}]
  1136. self.l3_inst.update_router_gateway_port_bindings(None, None)
  1137. # Assert that the port status is being set to DOWN
  1138. mock_updt_status.assert_called_once_with(
  1139. mock.ANY, fake_port_id, constants.PORT_STATUS_DOWN)
  1140. def test_schedule_unhosted_gateways_no_gateways(self):
  1141. self.nb_idl().get_unhosted_gateways.return_value = []
  1142. self.l3_inst.schedule_unhosted_gateways()
  1143. self.nb_idl().update_lrouter_port.assert_not_called()
  1144. def test_schedule_unhosted_gateways(self):
  1145. unhosted_gws = ['lrp-foo-1', 'lrp-foo-2', 'lrp-foo-3']
  1146. chassis_mappings = {
  1147. 'chassis1': ['physnet1'],
  1148. 'chassis2': ['physnet1'],
  1149. 'chassis3': ['physnet1']}
  1150. chassis = ['chassis1', 'chassis2', 'chassis3']
  1151. self.sb_idl().get_chassis_and_physnets.return_value = (
  1152. chassis_mappings)
  1153. self.sb_idl().get_gateway_chassis_from_cms_options.return_value = (
  1154. chassis)
  1155. self.nb_idl().get_unhosted_gateways.return_value = unhosted_gws
  1156. # 1. port has 2 gateway chassis
  1157. # 2. port has only chassis2
  1158. # 3. port is not bound
  1159. existing_port_bindings = [
  1160. ['chassis1', 'chassis2'],
  1161. ['chassis2'],
  1162. []]
  1163. self.nb_idl().get_gateway_chassis_binding.side_effect = (
  1164. existing_port_bindings)
  1165. # for 1. port schedule untouched, add only 3'rd chassis
  1166. # for 2. port master scheduler somewhere else
  1167. # for 3. port schedule all
  1168. self.mock_schedule.side_effect = [
  1169. ['chassis1', 'chassis2', 'chassis3'],
  1170. ['chassis1', 'chassis2', 'chassis3'],
  1171. ['chassis3', 'chassis2', 'chassis1']]
  1172. self.l3_inst.schedule_unhosted_gateways()
  1173. self.mock_candidates.assert_has_calls([
  1174. mock.call(mock.ANY,
  1175. chassis_physnets=chassis_mappings,
  1176. cms=chassis)] * 3)
  1177. self.mock_schedule.assert_has_calls([
  1178. mock.call(self.nb_idl(), self.sb_idl(),
  1179. 'lrp-foo-1', [], ['chassis1', 'chassis2']),
  1180. mock.call(self.nb_idl(), self.sb_idl(),
  1181. 'lrp-foo-2', [], ['chassis2']),
  1182. mock.call(self.nb_idl(), self.sb_idl(),
  1183. 'lrp-foo-3', [], [])])
  1184. # make sure that for second port master chassis stays untouched
  1185. self.nb_idl().update_lrouter_port.assert_has_calls([
  1186. mock.call('lrp-foo-1',
  1187. gateway_chassis=['chassis1', 'chassis2', 'chassis3']),
  1188. mock.call('lrp-foo-2',
  1189. gateway_chassis=['chassis2', 'chassis1', 'chassis3']),
  1190. mock.call('lrp-foo-3',
  1191. gateway_chassis=['chassis3', 'chassis2', 'chassis1'])])
  1192. class OVNL3ExtrarouteTests(test_l3_gw.ExtGwModeIntTestCase,
  1193. test_l3.L3NatDBIntTestCase,
  1194. test_extraroute.ExtraRouteDBTestCaseBase):
  1195. # TODO(lucasagomes): Ideally, this method should be moved to a base
  1196. # class which all tests classes in networking-ovn inherits from but,
  1197. # this base class doesn't seem to exist for now so we need to duplicate
  1198. # it here
  1199. def _start_mock(self, path, return_value, new_callable=None):
  1200. patcher = mock.patch(path, return_value=return_value,
  1201. new_callable=new_callable)
  1202. patcher.start()
  1203. self.addCleanup(patcher.stop)
  1204. def setUp(self):
  1205. plugin = 'neutron.tests.unit.extensions.test_l3.TestNoL3NatPlugin'
  1206. l3_plugin = ('networking_ovn.l3.l3_ovn.OVNL3RouterPlugin')
  1207. service_plugins = {'l3_plugin_name': l3_plugin}
  1208. # For these tests we need to enable overlapping ips
  1209. cfg.CONF.set_default('allow_overlapping_ips', True)
  1210. cfg.CONF.set_default('max_routes', 3)
  1211. ext_mgr = test_extraroute.ExtraRouteTestExtensionManager()
  1212. super(test_l3.L3BaseForIntTests, self).setUp(
  1213. plugin=plugin, ext_mgr=ext_mgr,
  1214. service_plugins=service_plugins)
  1215. revision_plugin.RevisionPlugin()
  1216. l3_gw_mgr = test_l3_gw.TestExtensionManager()
  1217. test_extensions.setup_extensions_middleware(l3_gw_mgr)
  1218. self.l3_inst = directory.get_plugin(plugin_constants.L3)
  1219. self._start_mock(
  1220. 'networking_ovn.l3.l3_ovn.OVNL3RouterPlugin._ovn',
  1221. new_callable=mock.PropertyMock,
  1222. return_value=fakes.FakeOvsdbNbOvnIdl())
  1223. self._start_mock(
  1224. 'networking_ovn.l3.l3_ovn.OVNL3RouterPlugin._sb_ovn',
  1225. new_callable=mock.PropertyMock,
  1226. return_value=fakes.FakeOvsdbSbOvnIdl())
  1227. self._start_mock(
  1228. 'networking_ovn.l3.l3_ovn_scheduler.'
  1229. 'OVNGatewayScheduler._schedule_gateway',
  1230. return_value='hv1')
  1231. self._start_mock(
  1232. 'networking_ovn.common.ovn_client.'
  1233. 'OVNClient.get_candidates_for_scheduling',
  1234. return_value=[])
  1235. self._start_mock(
  1236. 'networking_ovn.common.ovn_client.OVNClient.'
  1237. '_get_v4_network_of_all_router_ports',
  1238. return_value=[])
  1239. self._start_mock(
  1240. 'networking_ovn.common.ovn_client.'
  1241. 'OVNClient.update_floatingip_status',
  1242. return_value=None)
  1243. self._start_mock(
  1244. 'networking_ovn.common.utils.get_revision_number',
  1245. return_value=1)
  1246. self.setup_notification_driver()
  1247. # Note(dongj): According to bug #1657693, status of an unassociated
  1248. # floating IP is set to DOWN. Revise expected_status to DOWN for related
  1249. # test cases.
  1250. def test_floatingip_update(
  1251. self, expected_status=constants.FLOATINGIP_STATUS_DOWN):
  1252. super(OVNL3ExtrarouteTests, self).test_floatingip_update(
  1253. expected_status)
  1254. def test_floatingip_update_to_same_port_id_twice(
  1255. self, expected_status=constants.FLOATINGIP_STATUS_DOWN):
  1256. super(OVNL3ExtrarouteTests, self).\
  1257. test_floatingip_update_to_same_port_id_twice(expected_status)
  1258. def test_floatingip_update_subnet_gateway_disabled(
  1259. self, expected_status=constants.FLOATINGIP_STATUS_DOWN):
  1260. super(OVNL3ExtrarouteTests, self).\
  1261. test_floatingip_update_subnet_gateway_disabled(expected_status)
  1262. # Test function _subnet_update of L3 OVN plugin.
  1263. def test_update_subnet_gateway_for_external_net(self):
  1264. super(OVNL3ExtrarouteTests, self). \
  1265. test_update_subnet_gateway_for_external_net()
  1266. self.l3_inst._ovn.add_static_route.assert_called_once_with(
  1267. 'neutron-fake_device', ip_prefix='0.0.0.0/0', nexthop='120.0.0.2')
  1268. self.l3_inst._ovn.delete_static_route.assert_called_once_with(
  1269. 'neutron-fake_device', ip_prefix='0.0.0.0/0', nexthop='120.0.0.1')
  1270. def test_router_update_gateway_upon_subnet_create_max_ips_ipv6(self):
  1271. super(OVNL3ExtrarouteTests, self). \
  1272. test_router_update_gateway_upon_subnet_create_max_ips_ipv6()
  1273. add_static_route_calls = [
  1274. mock.call(mock.ANY, ip_prefix='0.0.0.0/0', nexthop='10.0.0.1'),
  1275. mock.call(mock.ANY, ip_prefix='::/0', nexthop='2001:db8::1')]
  1276. self.l3_inst._ovn.add_static_route.assert_has_calls(
  1277. add_static_route_calls, any_order=True)