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.

2672 lines
125KB

  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 datetime
  16. import uuid
  17. import mock
  18. from webob import exc
  19. from neutron.services.revisions import revision_plugin
  20. from neutron_lib.api.definitions import portbindings
  21. from neutron_lib.api.definitions import provider_net as pnet
  22. from neutron_lib.callbacks import events
  23. from neutron_lib.callbacks import registry
  24. from neutron_lib.callbacks import resources
  25. from neutron_lib import constants as const
  26. from neutron_lib import context
  27. from neutron_lib import exceptions as n_exc
  28. from neutron_lib.plugins import directory
  29. from neutron_lib.tests import tools
  30. from neutron_lib.utils import net as n_net
  31. from oslo_config import cfg
  32. from oslo_db import exception as os_db_exc
  33. from oslo_serialization import jsonutils
  34. from oslo_utils import timeutils
  35. from oslo_utils import uuidutils
  36. from neutron.common import utils as n_utils
  37. from neutron.db import provisioning_blocks
  38. from neutron.plugins.ml2.drivers import type_geneve # noqa
  39. from neutron.tests.unit.extensions import test_segment
  40. from neutron.tests.unit.plugins.ml2 import test_ext_portsecurity
  41. from neutron.tests.unit.plugins.ml2 import test_plugin
  42. from neutron.tests.unit.plugins.ml2 import test_security_group
  43. from networking_ovn.agent import stats
  44. from networking_ovn.common import acl as ovn_acl
  45. from networking_ovn.common import config as ovn_config
  46. from networking_ovn.common import constants as ovn_const
  47. from networking_ovn.common import ovn_client
  48. from networking_ovn.common import utils as ovn_utils
  49. from networking_ovn.db import revision as db_rev
  50. from networking_ovn.ml2 import mech_driver
  51. from networking_ovn.tests.unit import fakes
  52. class TestOVNMechanismDriver(test_plugin.Ml2PluginV2TestCase):
  53. _mechanism_drivers = ['logger', 'ovn']
  54. _extension_drivers = ['port_security', 'dns']
  55. def setUp(self):
  56. cfg.CONF.set_override('extension_drivers',
  57. self._extension_drivers,
  58. group='ml2')
  59. cfg.CONF.set_override('tenant_network_types',
  60. ['geneve'],
  61. group='ml2')
  62. cfg.CONF.set_override('vni_ranges',
  63. ['1:65536'],
  64. group='ml2_type_geneve')
  65. ovn_config.cfg.CONF.set_override('ovn_metadata_enabled',
  66. False,
  67. group='ovn')
  68. ovn_config.cfg.CONF.set_override('dns_servers', ['8.8.8.8'],
  69. group='ovn')
  70. super(TestOVNMechanismDriver, self).setUp()
  71. mm = directory.get_plugin().mechanism_manager
  72. self.mech_driver = mm.mech_drivers['ovn'].obj
  73. self.mech_driver._nb_ovn = fakes.FakeOvsdbNbOvnIdl()
  74. self.mech_driver._sb_ovn = fakes.FakeOvsdbSbOvnIdl()
  75. self.nb_ovn = self.mech_driver._nb_ovn
  76. self.sb_ovn = self.mech_driver._sb_ovn
  77. self.fake_subnet = fakes.FakeSubnet.create_one_subnet().info()
  78. self.fake_sg_rule = \
  79. fakes.FakeSecurityGroupRule.create_one_security_group_rule().info()
  80. self.fake_sg = fakes.FakeSecurityGroup.create_one_security_group(
  81. attrs={'security_group_rules': [self.fake_sg_rule]}
  82. ).info()
  83. self.sg_cache = {self.fake_sg['id']: self.fake_sg}
  84. self.subnet_cache = {self.fake_subnet['id']: self.fake_subnet}
  85. mock.patch(
  86. "networking_ovn.common.acl._acl_columns_name_severity_supported",
  87. return_value=True
  88. ).start()
  89. revision_plugin.RevisionPlugin()
  90. p = mock.patch.object(ovn_utils, 'get_revision_number', return_value=1)
  91. p.start()
  92. self.addCleanup(p.stop)
  93. p = mock.patch.object(db_rev, 'bump_revision')
  94. p.start()
  95. self.addCleanup(p.stop)
  96. @mock.patch.object(db_rev, 'bump_revision')
  97. def test__create_security_group(self, mock_bump):
  98. self.mech_driver._create_security_group(
  99. resources.SECURITY_GROUP, events.AFTER_CREATE, {},
  100. security_group=self.fake_sg)
  101. external_ids = {ovn_const.OVN_SG_EXT_ID_KEY: self.fake_sg['id']}
  102. ip4_name = ovn_utils.ovn_addrset_name(self.fake_sg['id'], 'ip4')
  103. ip6_name = ovn_utils.ovn_addrset_name(self.fake_sg['id'], 'ip6')
  104. create_address_set_calls = [mock.call(name=name,
  105. external_ids=external_ids)
  106. for name in [ip4_name, ip6_name]]
  107. self.nb_ovn.create_address_set.assert_has_calls(
  108. create_address_set_calls, any_order=True)
  109. mock_bump.assert_called_once_with(
  110. self.fake_sg, ovn_const.TYPE_SECURITY_GROUPS)
  111. def test__delete_security_group(self):
  112. self.mech_driver._delete_security_group(
  113. resources.SECURITY_GROUP, events.AFTER_CREATE, {},
  114. security_group_id=self.fake_sg['id'])
  115. ip4_name = ovn_utils.ovn_addrset_name(self.fake_sg['id'], 'ip4')
  116. ip6_name = ovn_utils.ovn_addrset_name(self.fake_sg['id'], 'ip6')
  117. delete_address_set_calls = [mock.call(name=name)
  118. for name in [ip4_name, ip6_name]]
  119. self.nb_ovn.delete_address_set.assert_has_calls(
  120. delete_address_set_calls, any_order=True)
  121. @mock.patch.object(db_rev, 'bump_revision')
  122. def test__process_sg_rule_notifications_sgr_create(self, mock_bump):
  123. with mock.patch(
  124. 'networking_ovn.common.acl.update_acls_for_security_group'
  125. ) as ovn_acl_up:
  126. rule = {'security_group_id': 'sg_id'}
  127. self.mech_driver._process_sg_rule_notification(
  128. resources.SECURITY_GROUP_RULE, events.AFTER_CREATE, {},
  129. security_group_rule=rule)
  130. ovn_acl_up.assert_called_once_with(
  131. mock.ANY, mock.ANY, mock.ANY,
  132. 'sg_id', rule, is_add_acl=True)
  133. mock_bump.assert_called_once_with(
  134. rule, ovn_const.TYPE_SECURITY_GROUP_RULES)
  135. @mock.patch.object(db_rev, 'delete_revision')
  136. def test_process_sg_rule_notifications_sgr_delete(self, mock_delrev):
  137. rule = {'id': 'sgr_id', 'security_group_id': 'sg_id'}
  138. with mock.patch(
  139. 'networking_ovn.common.acl.update_acls_for_security_group'
  140. ) as ovn_acl_up:
  141. with mock.patch(
  142. 'neutron.db.securitygroups_db.'
  143. 'SecurityGroupDbMixin.get_security_group_rule',
  144. return_value=rule
  145. ):
  146. self.mech_driver._process_sg_rule_notification(
  147. resources.SECURITY_GROUP_RULE, events.BEFORE_DELETE, {},
  148. security_group_rule=rule)
  149. ovn_acl_up.assert_called_once_with(
  150. mock.ANY, mock.ANY, mock.ANY,
  151. 'sg_id', rule, is_add_acl=False)
  152. mock_delrev.assert_called_once_with(
  153. rule['id'], ovn_const.TYPE_SECURITY_GROUP_RULES)
  154. def test_add_acls_no_sec_group(self):
  155. fake_port_no_sg = fakes.FakePort.create_one_port().info()
  156. expected_acls = ovn_acl.drop_all_ip_traffic_for_port(fake_port_no_sg)
  157. acls = ovn_acl.add_acls(self.mech_driver._plugin,
  158. mock.Mock(),
  159. fake_port_no_sg,
  160. {}, {}, self.mech_driver._nb_ovn)
  161. self.assertEqual(expected_acls, acls)
  162. def test_add_acls_no_sec_group_no_port_security(self):
  163. fake_port_no_sg_no_ps = fakes.FakePort.create_one_port(
  164. attrs={'port_security_enabled': False}).info()
  165. acls = ovn_acl.add_acls(self.mech_driver._plugin,
  166. mock.Mock(),
  167. fake_port_no_sg_no_ps,
  168. {}, {}, self.mech_driver._nb_ovn)
  169. self.assertEqual([], acls)
  170. def _test_add_acls_with_sec_group_helper(self, native_dhcp=True):
  171. fake_port_sg = fakes.FakePort.create_one_port(
  172. attrs={'security_groups': [self.fake_sg['id']],
  173. 'fixed_ips': [{'subnet_id': self.fake_subnet['id'],
  174. 'ip_address': '10.10.10.20'}]}
  175. ).info()
  176. expected_acls = []
  177. expected_acls += ovn_acl.drop_all_ip_traffic_for_port(
  178. fake_port_sg)
  179. expected_acls += ovn_acl.add_acl_dhcp(
  180. fake_port_sg, self.fake_subnet, native_dhcp)
  181. sg_rule_acl = ovn_acl.add_sg_rule_acl_for_port(
  182. fake_port_sg, self.fake_sg_rule,
  183. 'outport == "' + fake_port_sg['id'] + '" ' +
  184. '&& ip4 && ip4.src == 0.0.0.0/0 ' +
  185. '&& tcp && tcp.dst == 22')
  186. expected_acls.append(sg_rule_acl)
  187. # Test with caches
  188. acls = ovn_acl.add_acls(self.mech_driver._plugin,
  189. mock.Mock(),
  190. fake_port_sg,
  191. self.sg_cache,
  192. self.subnet_cache,
  193. self.mech_driver._nb_ovn)
  194. self.assertEqual(expected_acls, acls)
  195. # Test without caches
  196. with mock.patch('neutron.db.db_base_plugin_v2.'
  197. 'NeutronDbPluginV2.get_subnet',
  198. return_value=self.fake_subnet), \
  199. mock.patch('neutron.db.securitygroups_db.'
  200. 'SecurityGroupDbMixin.get_security_group',
  201. return_value=self.fake_sg):
  202. acls = ovn_acl.add_acls(self.mech_driver._plugin,
  203. mock.Mock(),
  204. fake_port_sg,
  205. {}, {}, self.mech_driver._nb_ovn)
  206. self.assertEqual(expected_acls, acls)
  207. # Test with security groups disabled
  208. with mock.patch('networking_ovn.common.acl.is_sg_enabled',
  209. return_value=False):
  210. acls = ovn_acl.add_acls(self.mech_driver._plugin,
  211. mock.Mock(),
  212. fake_port_sg,
  213. self.sg_cache,
  214. self.subnet_cache,
  215. self.mech_driver._nb_ovn)
  216. self.assertEqual([], acls)
  217. # Test with multiple fixed IPs on the same subnet.
  218. fake_port_sg['fixed_ips'].append({'subnet_id': self.fake_subnet['id'],
  219. 'ip_address': '10.10.10.21'})
  220. acls = ovn_acl.add_acls(self.mech_driver._plugin,
  221. mock.Mock(),
  222. fake_port_sg,
  223. self.sg_cache,
  224. self.subnet_cache,
  225. self.mech_driver._nb_ovn)
  226. self.assertEqual(expected_acls, acls)
  227. def test_add_acls_with_sec_group_native_dhcp_enabled(self):
  228. self._test_add_acls_with_sec_group_helper()
  229. def test_port_invalid_binding_profile(self):
  230. invalid_binding_profiles = [
  231. {'tag': 0,
  232. 'parent_name': 'fakename'},
  233. {'tag': 1024},
  234. {'tag': 1024, 'parent_name': 1024},
  235. {'parent_name': 'test'},
  236. {'tag': 'test'},
  237. {'vtep-physical-switch': 'psw1'},
  238. {'vtep-logical-switch': 'lsw1'},
  239. {'vtep-physical-switch': 'psw1', 'vtep-logical-switch': 1234},
  240. {'vtep-physical-switch': 1234, 'vtep-logical-switch': 'lsw1'},
  241. {'vtep-physical-switch': 'psw1', 'vtep-logical-switch': 'lsw1',
  242. 'tag': 1024},
  243. {'vtep-physical-switch': 'psw1', 'vtep-logical-switch': 'lsw1',
  244. 'parent_name': 'fakename'},
  245. {'vtep-physical-switch': 'psw1', 'vtep-logical-switch': 'lsw1',
  246. 'tag': 1024, 'parent_name': 'fakename'},
  247. ]
  248. with self.network(set_context=True, tenant_id='test') as net1:
  249. with self.subnet(network=net1) as subnet1:
  250. # succeed without binding:profile
  251. with self.port(subnet=subnet1,
  252. set_context=True, tenant_id='test'):
  253. pass
  254. # fail with invalid binding profiles
  255. for invalid_profile in invalid_binding_profiles:
  256. try:
  257. kwargs = {ovn_const.OVN_PORT_BINDING_PROFILE:
  258. invalid_profile}
  259. with self.port(
  260. subnet=subnet1,
  261. expected_res_status=403,
  262. arg_list=(
  263. ovn_const.OVN_PORT_BINDING_PROFILE,),
  264. set_context=True, tenant_id='test',
  265. **kwargs):
  266. pass
  267. except exc.HTTPClientError:
  268. pass
  269. def test__validate_ignored_port_update_from_fip_port(self):
  270. p = {'id': 'id', 'device_owner': 'test'}
  271. ori_p = {'id': 'id', 'device_owner': const.DEVICE_OWNER_FLOATINGIP}
  272. self.assertRaises(mech_driver.OVNPortUpdateError,
  273. self.mech_driver._validate_ignored_port,
  274. p, ori_p)
  275. def test__validate_ignored_port_update_to_fip_port(self):
  276. p = {'id': 'id', 'device_owner': const.DEVICE_OWNER_FLOATINGIP}
  277. ori_p = {'id': 'port-id', 'device_owner': 'test'}
  278. self.assertRaises(mech_driver.OVNPortUpdateError,
  279. self.mech_driver._validate_ignored_port,
  280. p, ori_p)
  281. def test_create_and_update_ignored_fip_port(self):
  282. with self.network(set_context=True, tenant_id='test') as net1:
  283. with self.subnet(network=net1) as subnet1:
  284. with self.port(subnet=subnet1,
  285. device_owner=const.DEVICE_OWNER_FLOATINGIP,
  286. set_context=True, tenant_id='test') as port:
  287. self.nb_ovn.create_lswitch_port.assert_not_called()
  288. data = {'port': {'name': 'new'}}
  289. req = self.new_update_request('ports', data,
  290. port['port']['id'])
  291. res = req.get_response(self.api)
  292. self.assertEqual(exc.HTTPOk.code, res.status_int)
  293. self.nb_ovn.set_lswitch_port.assert_not_called()
  294. def test_update_ignored_port_from_fip_device_owner(self):
  295. with self.network(set_context=True, tenant_id='test') as net1:
  296. with self.subnet(network=net1) as subnet1:
  297. with self.port(subnet=subnet1,
  298. device_owner=const.DEVICE_OWNER_FLOATINGIP,
  299. set_context=True, tenant_id='test') as port:
  300. self.nb_ovn.create_lswitch_port.assert_not_called()
  301. data = {'port': {'device_owner': 'test'}}
  302. req = self.new_update_request('ports', data,
  303. port['port']['id'])
  304. res = req.get_response(self.api)
  305. self.assertEqual(exc.HTTPBadRequest.code, res.status_int)
  306. msg = jsonutils.loads(res.body)['NeutronError']['message']
  307. expect_msg = ('Bad port request: Updating device_owner for'
  308. ' port %s owned by network:floatingip is'
  309. ' not supported.' % port['port']['id'])
  310. self.assertEqual(msg, expect_msg)
  311. self.nb_ovn.set_lswitch_port.assert_not_called()
  312. def test_update_ignored_port_to_fip_device_owner(self):
  313. with self.network(set_context=True, tenant_id='test') as net1:
  314. with self.subnet(network=net1) as subnet1:
  315. with self.port(subnet=subnet1,
  316. device_owner='test',
  317. set_context=True, tenant_id='test') as port:
  318. self.assertEqual(
  319. 1, self.nb_ovn.create_lswitch_port.call_count)
  320. data = {'port': {'device_owner':
  321. const.DEVICE_OWNER_FLOATINGIP}}
  322. req = self.new_update_request('ports', data,
  323. port['port']['id'])
  324. res = req.get_response(self.api)
  325. self.assertEqual(exc.HTTPBadRequest.code, res.status_int)
  326. msg = jsonutils.loads(res.body)['NeutronError']['message']
  327. expect_msg = ('Bad port request: Updating device_owner to'
  328. ' network:floatingip for port %s is'
  329. ' not supported.' % port['port']['id'])
  330. self.assertEqual(msg, expect_msg)
  331. self.nb_ovn.set_lswitch_port.assert_not_called()
  332. def test_create_port_security(self):
  333. kwargs = {'mac_address': '00:00:00:00:00:01',
  334. 'fixed_ips': [{'ip_address': '10.0.0.2'},
  335. {'ip_address': '10.0.0.4'}]}
  336. with self.network(set_context=True, tenant_id='test') as net1:
  337. with self.subnet(network=net1) as subnet1:
  338. with self.port(subnet=subnet1,
  339. arg_list=('mac_address', 'fixed_ips'),
  340. set_context=True, tenant_id='test',
  341. **kwargs) as port:
  342. self.assertTrue(self.nb_ovn.create_lswitch_port.called)
  343. called_args_dict = (
  344. (self.nb_ovn.create_lswitch_port
  345. ).call_args_list[0][1])
  346. self.assertEqual(['00:00:00:00:00:01 10.0.0.2 10.0.0.4'],
  347. called_args_dict.get('port_security'))
  348. data = {'port': {'mac_address': '00:00:00:00:00:02'}}
  349. req = self.new_update_request(
  350. 'ports',
  351. data, port['port']['id'])
  352. req.get_response(self.api)
  353. self.assertTrue(self.nb_ovn.set_lswitch_port.called)
  354. called_args_dict = (
  355. (self.nb_ovn.set_lswitch_port
  356. ).call_args_list[0][1])
  357. self.assertEqual(['00:00:00:00:00:02 10.0.0.2 10.0.0.4'],
  358. called_args_dict.get('port_security'))
  359. def test_create_port_with_disabled_security(self):
  360. # NOTE(mjozefcz): Lets pretend this is nova port to not
  361. # be treated as VIP.
  362. kwargs = {'port_security_enabled': False,
  363. 'device_owner': 'compute:nova'}
  364. with self.network(set_context=True, tenant_id='test') as net1:
  365. with self.subnet(network=net1) as subnet1:
  366. with self.port(subnet=subnet1,
  367. arg_list=('port_security_enabled',),
  368. set_context=True, tenant_id='test',
  369. **kwargs) as port:
  370. self.assertTrue(self.nb_ovn.create_lswitch_port.called)
  371. called_args_dict = (
  372. (self.nb_ovn.create_lswitch_port
  373. ).call_args_list[0][1])
  374. self.assertEqual([],
  375. called_args_dict.get('port_security'))
  376. self.assertEqual('unknown',
  377. called_args_dict.get('addresses')[1])
  378. data = {'port': {'mac_address': '00:00:00:00:00:01'}}
  379. req = self.new_update_request(
  380. 'ports',
  381. data, port['port']['id'])
  382. req.get_response(self.api)
  383. self.assertTrue(self.nb_ovn.set_lswitch_port.called)
  384. called_args_dict = (
  385. (self.nb_ovn.set_lswitch_port
  386. ).call_args_list[0][1])
  387. self.assertEqual([],
  388. called_args_dict.get('port_security'))
  389. self.assertEqual(2, len(called_args_dict.get('addresses')))
  390. self.assertEqual('unknown',
  391. called_args_dict.get('addresses')[1])
  392. # Enable port security
  393. data = {'port': {'port_security_enabled': 'True'}}
  394. req = self.new_update_request(
  395. 'ports',
  396. data, port['port']['id'])
  397. req.get_response(self.api)
  398. called_args_dict = (
  399. (self.nb_ovn.set_lswitch_port
  400. ).call_args_list[1][1])
  401. self.assertEqual(2,
  402. self.nb_ovn.set_lswitch_port.call_count)
  403. self.assertEqual(1, len(called_args_dict.get('addresses')))
  404. self.assertNotIn('unknown',
  405. called_args_dict.get('addresses'))
  406. def test_create_port_security_allowed_address_pairs(self):
  407. # NOTE(mjozefcz): Lets pretend this is nova port to not
  408. # be treated as VIP.
  409. kwargs = {'allowed_address_pairs':
  410. [{"ip_address": "1.1.1.1"},
  411. {"ip_address": "2.2.2.2",
  412. "mac_address": "22:22:22:22:22:22"}],
  413. 'device_owner': 'compute:nova'}
  414. with self.network(set_context=True, tenant_id='test') as net1:
  415. with self.subnet(network=net1) as subnet1:
  416. with self.port(subnet=subnet1,
  417. arg_list=('allowed_address_pairs',),
  418. set_context=True, tenant_id='test',
  419. **kwargs) as port:
  420. port_ip = port['port'].get('fixed_ips')[0]['ip_address']
  421. self.assertTrue(self.nb_ovn.create_lswitch_port.called)
  422. called_args_dict = (
  423. (self.nb_ovn.create_lswitch_port
  424. ).call_args_list[0][1])
  425. self.assertEqual(
  426. tools.UnorderedList(
  427. ["22:22:22:22:22:22 2.2.2.2",
  428. port['port']['mac_address'] + ' ' + port_ip +
  429. ' ' + '1.1.1.1']),
  430. called_args_dict.get('port_security'))
  431. self.assertEqual(
  432. tools.UnorderedList(
  433. ["22:22:22:22:22:22",
  434. port['port']['mac_address'] + ' ' + port_ip]),
  435. called_args_dict.get('addresses'))
  436. old_mac = port['port']['mac_address']
  437. # we are updating only the port mac address. So the
  438. # mac address of the allowed address pair ip 1.1.1.1
  439. # will have old mac address
  440. data = {'port': {'mac_address': '00:00:00:00:00:01'}}
  441. req = self.new_update_request(
  442. 'ports',
  443. data, port['port']['id'])
  444. req.get_response(self.api)
  445. self.assertTrue(self.nb_ovn.set_lswitch_port.called)
  446. called_args_dict = (
  447. (self.nb_ovn.set_lswitch_port
  448. ).call_args_list[0][1])
  449. self.assertEqual(tools.UnorderedList(
  450. ["22:22:22:22:22:22 2.2.2.2",
  451. "00:00:00:00:00:01 " + port_ip,
  452. old_mac + " 1.1.1.1"]),
  453. called_args_dict.get('port_security'))
  454. self.assertEqual(
  455. tools.UnorderedList(
  456. ["22:22:22:22:22:22",
  457. "00:00:00:00:00:01 " + port_ip,
  458. old_mac]),
  459. called_args_dict.get('addresses'))
  460. def test_create_port_possible_vip(self):
  461. """Test if just created LSP has no adresses set.
  462. This could be potential VIP port. If not - next
  463. port update will set the adresses corectly during
  464. binding process.
  465. """
  466. with (
  467. self.network(set_context=True, tenant_id='test')) as net1, (
  468. self.subnet(network=net1)) as subnet1, (
  469. self.port(subnet=subnet1, set_context=True, tenant_id='test')):
  470. self.assertTrue(self.nb_ovn.create_lswitch_port.called)
  471. called_args_dict = (
  472. self.nb_ovn.create_lswitch_port.call_args_list[0][1])
  473. self.assertEqual([],
  474. called_args_dict.get('addresses'))
  475. def _create_fake_network_context(self,
  476. network_type,
  477. physical_network=None,
  478. segmentation_id=None):
  479. network_attrs = {'provider:network_type': network_type,
  480. 'provider:physical_network': physical_network,
  481. 'provider:segmentation_id': segmentation_id}
  482. segment_attrs = {'network_type': network_type,
  483. 'physical_network': physical_network,
  484. 'segmentation_id': segmentation_id}
  485. fake_network = \
  486. fakes.FakeNetwork.create_one_network(attrs=network_attrs).info()
  487. fake_segments = \
  488. [fakes.FakeSegment.create_one_segment(attrs=segment_attrs).info()]
  489. return fakes.FakeNetworkContext(fake_network, fake_segments)
  490. def _create_fake_mp_network_context(self):
  491. network_type = 'flat'
  492. network_attrs = {'segments': []}
  493. fake_segments = []
  494. for physical_network in ['physnet1', 'physnet2']:
  495. network_attrs['segments'].append(
  496. {'provider:network_type': network_type,
  497. 'provider:physical_network': physical_network})
  498. segment_attrs = {'network_type': network_type,
  499. 'physical_network': physical_network}
  500. fake_segments.append(
  501. fakes.FakeSegment.create_one_segment(
  502. attrs=segment_attrs).info())
  503. fake_network = \
  504. fakes.FakeNetwork.create_one_network(attrs=network_attrs).info()
  505. fake_network.pop('provider:network_type')
  506. fake_network.pop('provider:physical_network')
  507. fake_network.pop('provider:segmentation_id')
  508. return fakes.FakeNetworkContext(fake_network, fake_segments)
  509. def test_network_precommit(self):
  510. # Test supported network types.
  511. fake_network_context = self._create_fake_network_context('local')
  512. self.mech_driver.create_network_precommit(fake_network_context)
  513. fake_network_context = self._create_fake_network_context(
  514. 'flat', physical_network='physnet')
  515. self.mech_driver.update_network_precommit(fake_network_context)
  516. fake_network_context = self._create_fake_network_context(
  517. 'geneve', segmentation_id=10)
  518. self.mech_driver.create_network_precommit(fake_network_context)
  519. fake_network_context = self._create_fake_network_context(
  520. 'vlan', physical_network='physnet', segmentation_id=11)
  521. self.mech_driver.update_network_precommit(fake_network_context)
  522. fake_mp_network_context = self._create_fake_mp_network_context()
  523. self.mech_driver.create_network_precommit(fake_mp_network_context)
  524. # Test unsupported network types.
  525. fake_network_context = self._create_fake_network_context(
  526. 'vxlan', segmentation_id=12)
  527. self.assertRaises(n_exc.InvalidInput,
  528. self.mech_driver.create_network_precommit,
  529. fake_network_context)
  530. fake_network_context = self._create_fake_network_context(
  531. 'gre', segmentation_id=13)
  532. self.assertRaises(n_exc.InvalidInput,
  533. self.mech_driver.update_network_precommit,
  534. fake_network_context)
  535. def test_create_port_without_security_groups(self):
  536. kwargs = {'security_groups': []}
  537. with self.network(set_context=True, tenant_id='test') as net1:
  538. with self.subnet(network=net1) as subnet1:
  539. with self.port(subnet=subnet1,
  540. arg_list=('security_groups',),
  541. set_context=True, tenant_id='test',
  542. **kwargs):
  543. self.assertEqual(
  544. 1, self.nb_ovn.create_lswitch_port.call_count)
  545. self.assertEqual(2, self.nb_ovn.add_acl.call_count)
  546. self.nb_ovn.update_address_set.assert_not_called()
  547. def test_create_port_without_security_groups_no_ps(self):
  548. kwargs = {'security_groups': [], 'port_security_enabled': False}
  549. with self.network(set_context=True, tenant_id='test') as net1:
  550. with self.subnet(network=net1) as subnet1:
  551. with self.port(subnet=subnet1,
  552. arg_list=('security_groups',
  553. 'port_security_enabled'),
  554. set_context=True, tenant_id='test',
  555. **kwargs):
  556. self.assertEqual(
  557. 1, self.nb_ovn.create_lswitch_port.call_count)
  558. self.nb_ovn.add_acl.assert_not_called()
  559. self.nb_ovn.update_address_set.assert_not_called()
  560. def _test_create_port_with_security_groups_helper(self,
  561. add_acl_call_count):
  562. with self.network(set_context=True, tenant_id='test') as net1:
  563. with self.subnet(network=net1) as subnet1:
  564. with self.port(subnet=subnet1,
  565. set_context=True, tenant_id='test'):
  566. self.assertEqual(
  567. 1, self.nb_ovn.create_lswitch_port.call_count)
  568. self.assertEqual(
  569. add_acl_call_count, self.nb_ovn.add_acl.call_count)
  570. self.assertEqual(
  571. 1, self.nb_ovn.update_address_set.call_count)
  572. def test_create_port_with_security_groups_native_dhcp_enabled(self):
  573. self._test_create_port_with_security_groups_helper(7)
  574. def test_update_port_changed_security_groups(self):
  575. with self.network(set_context=True, tenant_id='test') as net1:
  576. with self.subnet(network=net1) as subnet1:
  577. with self.port(subnet=subnet1,
  578. set_context=True, tenant_id='test') as port1:
  579. sg_id = port1['port']['security_groups'][0]
  580. fake_lsp = (
  581. fakes.FakeOVNPort.from_neutron_port(
  582. port1['port']))
  583. self.nb_ovn.lookup.return_value = fake_lsp
  584. # Remove the default security group.
  585. self.nb_ovn.set_lswitch_port.reset_mock()
  586. self.nb_ovn.update_acls.reset_mock()
  587. self.nb_ovn.update_address_set.reset_mock()
  588. data = {'port': {'security_groups': []}}
  589. self._update('ports', port1['port']['id'], data)
  590. self.assertEqual(
  591. 1, self.nb_ovn.set_lswitch_port.call_count)
  592. self.assertEqual(
  593. 1, self.nb_ovn.update_acls.call_count)
  594. self.assertEqual(
  595. 1, self.nb_ovn.update_address_set.call_count)
  596. # Add the default security group.
  597. self.nb_ovn.set_lswitch_port.reset_mock()
  598. self.nb_ovn.update_acls.reset_mock()
  599. self.nb_ovn.update_address_set.reset_mock()
  600. fake_lsp.external_ids.pop(ovn_const.OVN_SG_IDS_EXT_ID_KEY)
  601. data = {'port': {'security_groups': [sg_id]}}
  602. self._update('ports', port1['port']['id'], data)
  603. self.assertEqual(
  604. 1, self.nb_ovn.set_lswitch_port.call_count)
  605. self.assertEqual(
  606. 1, self.nb_ovn.update_acls.call_count)
  607. self.assertEqual(
  608. 1, self.nb_ovn.update_address_set.call_count)
  609. def test_update_port_unchanged_security_groups(self):
  610. with self.network(set_context=True, tenant_id='test') as net1:
  611. with self.subnet(network=net1) as subnet1:
  612. with self.port(subnet=subnet1,
  613. set_context=True, tenant_id='test') as port1:
  614. fake_lsp = (
  615. fakes.FakeOVNPort.from_neutron_port(
  616. port1['port']))
  617. self.nb_ovn.lookup.return_value = fake_lsp
  618. # Update the port name.
  619. self.nb_ovn.set_lswitch_port.reset_mock()
  620. self.nb_ovn.update_acls.reset_mock()
  621. self.nb_ovn.update_address_set.reset_mock()
  622. data = {'port': {'name': 'rtheis'}}
  623. self._update('ports', port1['port']['id'], data)
  624. self.assertEqual(
  625. 1, self.nb_ovn.set_lswitch_port.call_count)
  626. self.nb_ovn.update_acls.assert_not_called()
  627. self.nb_ovn.update_address_set.assert_not_called()
  628. # Update the port fixed IPs
  629. self.nb_ovn.set_lswitch_port.reset_mock()
  630. self.nb_ovn.update_acls.reset_mock()
  631. self.nb_ovn.update_address_set.reset_mock()
  632. data = {'port': {'fixed_ips': []}}
  633. self._update('ports', port1['port']['id'], data)
  634. self.assertEqual(
  635. 1, self.nb_ovn.set_lswitch_port.call_count)
  636. self.assertEqual(
  637. 1, self.nb_ovn.update_acls.call_count)
  638. self.assertEqual(
  639. 1, self.nb_ovn.update_address_set.call_count)
  640. def _test_update_port_vip(self, is_vip=True):
  641. kwargs = {}
  642. if not is_vip:
  643. # NOTE(mjozefcz): Lets pretend this is nova port to not
  644. # be treated as VIP.
  645. kwargs['device_owner'] = 'compute:nova'
  646. with (
  647. self.network(set_context=True, tenant_id='test')) as net1, (
  648. self.subnet(network=net1)) as subnet1, (
  649. self.port(subnet=subnet1, set_context=True,
  650. tenant_id='test', **kwargs)) as port1:
  651. fake_lsp = (
  652. fakes.FakeOVNPort.from_neutron_port(
  653. port1['port']))
  654. self.nb_ovn.lookup.return_value = fake_lsp
  655. # Update the port name.
  656. self.nb_ovn.set_lswitch_port.reset_mock()
  657. data = {'port': {'name': 'rtheis'}}
  658. self._update('ports', port1['port']['id'], data)
  659. self.assertEqual(
  660. 1, self.nb_ovn.set_lswitch_port.call_count)
  661. called_args_dict = (
  662. self.nb_ovn.set_lswitch_port.call_args_list[0][1])
  663. self.assertEqual(
  664. 'rtheis',
  665. called_args_dict['external_ids']['neutron:port_name'])
  666. if is_vip:
  667. self.assertEqual([],
  668. called_args_dict.get('addresses'))
  669. else:
  670. self.assertNotEqual([],
  671. called_args_dict.get('addresses'))
  672. def test_update_port_not_vip_port(self):
  673. self._test_update_port_vip(is_vip=False)
  674. def test_update_port_vip_port(self):
  675. self._test_update_port_vip()
  676. def test_delete_port_without_security_groups(self):
  677. kwargs = {'security_groups': []}
  678. with self.network(set_context=True, tenant_id='test') as net1:
  679. with self.subnet(network=net1) as subnet1:
  680. with self.port(subnet=subnet1,
  681. arg_list=('security_groups',),
  682. set_context=True, tenant_id='test',
  683. **kwargs) as port1:
  684. fake_lsp = (
  685. fakes.FakeOVNPort.from_neutron_port(
  686. port1['port']))
  687. self.nb_ovn.lookup.return_value = fake_lsp
  688. self.nb_ovn.delete_lswitch_port.reset_mock()
  689. self.nb_ovn.delete_acl.reset_mock()
  690. self.nb_ovn.update_address_set.reset_mock()
  691. self._delete('ports', port1['port']['id'])
  692. self.assertEqual(
  693. 1, self.nb_ovn.delete_lswitch_port.call_count)
  694. self.assertEqual(
  695. 1, self.nb_ovn.delete_acl.call_count)
  696. self.nb_ovn.update_address_set.assert_not_called()
  697. def test_delete_port_with_security_groups(self):
  698. with self.network(set_context=True, tenant_id='test') as net1:
  699. with self.subnet(network=net1) as subnet1:
  700. with self.port(subnet=subnet1,
  701. set_context=True, tenant_id='test') as port1:
  702. fake_lsp = (
  703. fakes.FakeOVNPort.from_neutron_port(
  704. port1['port']))
  705. self.nb_ovn.lookup.return_value = fake_lsp
  706. self.nb_ovn.delete_lswitch_port.reset_mock()
  707. self.nb_ovn.delete_acl.reset_mock()
  708. self.nb_ovn.update_address_set.reset_mock()
  709. self._delete('ports', port1['port']['id'])
  710. self.assertEqual(
  711. 1, self.nb_ovn.delete_lswitch_port.call_count)
  712. self.assertEqual(
  713. 1, self.nb_ovn.delete_acl.call_count)
  714. self.assertEqual(
  715. 1, self.nb_ovn.update_address_set.call_count)
  716. def _test_set_port_status_up(self, is_compute_port=False):
  717. port_device_owner = 'compute:nova' if is_compute_port else ''
  718. self.mech_driver._plugin.nova_notifier = mock.Mock()
  719. with self.network(set_context=True, tenant_id='test') as net1, \
  720. self.subnet(network=net1) as subnet1, \
  721. self.port(subnet=subnet1, set_context=True,
  722. tenant_id='test',
  723. device_owner=port_device_owner) as port1, \
  724. mock.patch('neutron.db.provisioning_blocks.'
  725. 'provisioning_complete') as pc, \
  726. mock.patch.object(self.mech_driver,
  727. '_update_dnat_entry_if_needed') as ude, \
  728. mock.patch.object(
  729. self.mech_driver,
  730. '_wait_for_metadata_provisioned_if_needed') as wmp, \
  731. mock.patch.object(self.mech_driver, '_should_notify_nova',
  732. return_value=is_compute_port):
  733. self.mech_driver.set_port_status_up(port1['port']['id'])
  734. pc.assert_called_once_with(
  735. mock.ANY,
  736. port1['port']['id'],
  737. resources.PORT,
  738. provisioning_blocks.L2_AGENT_ENTITY
  739. )
  740. ude.assert_called_once_with(port1['port']['id'])
  741. wmp.assert_called_once_with(port1['port']['id'])
  742. # If the port does NOT bellong to compute, do not notify Nova
  743. # about it's status changes
  744. if not is_compute_port:
  745. self.mech_driver._plugin.nova_notifier.\
  746. notify_port_active_direct.assert_not_called()
  747. else:
  748. self.mech_driver._plugin.nova_notifier.\
  749. notify_port_active_direct.assert_called_once_with(
  750. mock.ANY)
  751. def test_set_port_status_up(self):
  752. self._test_set_port_status_up(is_compute_port=False)
  753. def test_set_compute_port_status_up(self):
  754. self._test_set_port_status_up(is_compute_port=True)
  755. def _test_set_port_status_down(self, is_compute_port=False):
  756. port_device_owner = 'compute:nova' if is_compute_port else ''
  757. self.mech_driver._plugin.nova_notifier = mock.Mock()
  758. with self.network(set_context=True, tenant_id='test') as net1, \
  759. self.subnet(network=net1) as subnet1, \
  760. self.port(subnet=subnet1, set_context=True,
  761. tenant_id='test',
  762. device_owner=port_device_owner) as port1, \
  763. mock.patch('neutron.db.provisioning_blocks.'
  764. 'add_provisioning_component') as apc, \
  765. mock.patch.object(self.mech_driver,
  766. '_update_dnat_entry_if_needed') as ude, \
  767. mock.patch.object(self.mech_driver, '_should_notify_nova',
  768. return_value=is_compute_port):
  769. self.mech_driver.set_port_status_down(port1['port']['id'])
  770. apc.assert_called_once_with(
  771. mock.ANY,
  772. port1['port']['id'],
  773. resources.PORT,
  774. provisioning_blocks.L2_AGENT_ENTITY
  775. )
  776. ude.assert_called_once_with(port1['port']['id'], False)
  777. # If the port does NOT bellong to compute, do not notify Nova
  778. # about it's status changes
  779. if not is_compute_port:
  780. self.mech_driver._plugin.nova_notifier.\
  781. record_port_status_changed.assert_not_called()
  782. self.mech_driver._plugin.nova_notifier.\
  783. send_port_status.assert_not_called()
  784. else:
  785. self.mech_driver._plugin.nova_notifier.\
  786. record_port_status_changed.assert_called_once_with(
  787. mock.ANY, const.PORT_STATUS_ACTIVE,
  788. const.PORT_STATUS_DOWN, None)
  789. self.mech_driver._plugin.nova_notifier.\
  790. send_port_status.assert_called_once_with(
  791. None, None, mock.ANY)
  792. def test_set_port_status_down(self):
  793. self._test_set_port_status_down(is_compute_port=False)
  794. def test_set_compute_port_status_down(self):
  795. self._test_set_port_status_down(is_compute_port=True)
  796. def test_set_port_status_down_not_found(self):
  797. with mock.patch('neutron.db.provisioning_blocks.'
  798. 'add_provisioning_component') as apc, \
  799. mock.patch.object(self.mech_driver,
  800. '_update_dnat_entry_if_needed'):
  801. self.mech_driver.set_port_status_down('foo')
  802. apc.assert_not_called()
  803. def test_set_port_status_concurrent_delete(self):
  804. exc = os_db_exc.DBReferenceError('', '', '', '')
  805. with self.network(set_context=True, tenant_id='test') as net1, \
  806. self.subnet(network=net1) as subnet1, \
  807. self.port(subnet=subnet1, set_context=True,
  808. tenant_id='test') as port1, \
  809. mock.patch('neutron.db.provisioning_blocks.'
  810. 'add_provisioning_component',
  811. side_effect=exc) as apc, \
  812. mock.patch.object(self.mech_driver,
  813. '_update_dnat_entry_if_needed') as ude:
  814. self.mech_driver.set_port_status_down(port1['port']['id'])
  815. apc.assert_called_once_with(
  816. mock.ANY,
  817. port1['port']['id'],
  818. resources.PORT,
  819. provisioning_blocks.L2_AGENT_ENTITY
  820. )
  821. ude.assert_called_once_with(port1['port']['id'], False)
  822. def _test__wait_for_metadata_provisioned_if_needed(self, enable_dhcp,
  823. wait_expected):
  824. with self.network(set_context=True, tenant_id='test') as net1, \
  825. self.subnet(network=net1,
  826. enable_dhcp=enable_dhcp) as subnet1, \
  827. self.port(subnet=subnet1, set_context=True,
  828. tenant_id='test') as port1, \
  829. mock.patch.object(n_utils, 'wait_until_true') as wut, \
  830. mock.patch.object(ovn_config, 'is_ovn_metadata_enabled',
  831. return_value=True):
  832. self.mech_driver._wait_for_metadata_provisioned_if_needed(
  833. port1['port']['id'])
  834. if wait_expected:
  835. wut.assert_called_once()
  836. else:
  837. wut.assert_not_called()
  838. def test__wait_for_metadata_provisioned_if_needed(self):
  839. self._test__wait_for_metadata_provisioned_if_needed(
  840. enable_dhcp=True, wait_expected=True)
  841. def test__wait_for_metadata_provisioned_if_needed_not_needed(self):
  842. self._test__wait_for_metadata_provisioned_if_needed(
  843. enable_dhcp=False, wait_expected=False)
  844. def test_bind_port_unsupported_vnic_type(self):
  845. fake_port = fakes.FakePort.create_one_port(
  846. attrs={'binding:vnic_type': 'unknown'}).info()
  847. fake_port_context = fakes.FakePortContext(fake_port, 'host', [])
  848. self.mech_driver.bind_port(fake_port_context)
  849. self.sb_ovn.get_chassis_data_for_ml2_bind_port.assert_not_called()
  850. fake_port_context.set_binding.assert_not_called()
  851. def _test_bind_port_failed(self, fake_segments):
  852. fake_port = fakes.FakePort.create_one_port().info()
  853. fake_host = 'host'
  854. fake_port_context = fakes.FakePortContext(
  855. fake_port, fake_host, fake_segments)
  856. self.mech_driver.bind_port(fake_port_context)
  857. self.sb_ovn.get_chassis_data_for_ml2_bind_port.assert_called_once_with(
  858. fake_host)
  859. fake_port_context.set_binding.assert_not_called()
  860. def test_bind_port_host_not_found(self):
  861. self.sb_ovn.get_chassis_data_for_ml2_bind_port.side_effect = \
  862. RuntimeError
  863. self._test_bind_port_failed([])
  864. def test_bind_port_no_segments_to_bind(self):
  865. self._test_bind_port_failed([])
  866. def test_bind_port_physnet_not_found(self):
  867. segment_attrs = {'network_type': 'vlan',
  868. 'physical_network': 'unknown-physnet',
  869. 'segmentation_id': 23}
  870. fake_segments = \
  871. [fakes.FakeSegment.create_one_segment(attrs=segment_attrs).info()]
  872. self._test_bind_port_failed(fake_segments)
  873. def _test_bind_port(self, fake_segments):
  874. fake_port = fakes.FakePort.create_one_port().info()
  875. fake_host = 'host'
  876. fake_port_context = fakes.FakePortContext(
  877. fake_port, fake_host, fake_segments)
  878. self.mech_driver.bind_port(fake_port_context)
  879. self.sb_ovn.get_chassis_data_for_ml2_bind_port.assert_called_once_with(
  880. fake_host)
  881. fake_port_context.set_binding.assert_called_once_with(
  882. fake_segments[0]['id'],
  883. portbindings.VIF_TYPE_OVS,
  884. self.mech_driver.vif_details[portbindings.VIF_TYPE_OVS])
  885. def _test_bind_port_sriov(self, fake_segments):
  886. fake_port = fakes.FakePort.create_one_port(
  887. attrs={'binding:vnic_type': 'direct',
  888. 'binding:profile': {'capabilities': ['switchdev']}}).info()
  889. fake_host = 'host'
  890. fake_port_context = fakes.FakePortContext(
  891. fake_port, fake_host, fake_segments)
  892. self.mech_driver.bind_port(fake_port_context)
  893. self.sb_ovn.get_chassis_data_for_ml2_bind_port.assert_called_once_with(
  894. fake_host)
  895. fake_port_context.set_binding.assert_called_once_with(
  896. fake_segments[0]['id'],
  897. portbindings.VIF_TYPE_OVS,
  898. self.mech_driver.vif_details[portbindings.VIF_TYPE_OVS])
  899. def test_bind_port_geneve(self):
  900. segment_attrs = {'network_type': 'geneve',
  901. 'physical_network': None,
  902. 'segmentation_id': 1023}
  903. fake_segments = \
  904. [fakes.FakeSegment.create_one_segment(attrs=segment_attrs).info()]
  905. self._test_bind_port(fake_segments)
  906. def test_bind_sriov_port_geneve(self):
  907. """Test binding a SR-IOV port to a geneve segment."""
  908. segment_attrs = {'network_type': 'geneve',
  909. 'physical_network': None,
  910. 'segmentation_id': 1023}
  911. fake_segments = \
  912. [fakes.FakeSegment.create_one_segment(attrs=segment_attrs).info()]
  913. self._test_bind_port_sriov(fake_segments)
  914. def test_bind_port_vlan(self):
  915. segment_attrs = {'network_type': 'vlan',
  916. 'physical_network': 'fake-physnet',
  917. 'segmentation_id': 23}
  918. fake_segments = \
  919. [fakes.FakeSegment.create_one_segment(attrs=segment_attrs).info()]
  920. self._test_bind_port(fake_segments)
  921. def test_bind_port_flat(self):
  922. segment_attrs = {'network_type': 'flat',
  923. 'physical_network': 'fake-physnet',
  924. 'segmentation_id': None}
  925. fake_segments = \
  926. [fakes.FakeSegment.create_one_segment(attrs=segment_attrs).info()]
  927. self._test_bind_port(fake_segments)
  928. def test_bind_port_vxlan(self):
  929. segment_attrs = {'network_type': 'vxlan',
  930. 'physical_network': None,
  931. 'segmentation_id': 1024}
  932. fake_segments = \
  933. [fakes.FakeSegment.create_one_segment(attrs=segment_attrs).info()]
  934. self._test_bind_port(fake_segments)
  935. def test__is_port_provisioning_required(self):
  936. fake_port = fakes.FakePort.create_one_port(
  937. attrs={'binding:vnic_type': 'normal',
  938. 'status': const.PORT_STATUS_DOWN}).info()
  939. fake_host = 'fake-physnet'
  940. # Test host not changed
  941. self.assertFalse(self.mech_driver._is_port_provisioning_required(
  942. fake_port, fake_host, fake_host))
  943. # Test invalid vnic type.
  944. fake_port['binding:vnic_type'] = 'unknown'
  945. self.assertFalse(self.mech_driver._is_port_provisioning_required(
  946. fake_port, fake_host, None))
  947. fake_port['binding:vnic_type'] = 'normal'
  948. # Test invalid status.
  949. fake_port['status'] = const.PORT_STATUS_ACTIVE
  950. self.assertFalse(self.mech_driver._is_port_provisioning_required(
  951. fake_port, fake_host, None))
  952. fake_port['status'] = const.PORT_STATUS_DOWN
  953. # Test no host.
  954. self.assertFalse(self.mech_driver._is_port_provisioning_required(
  955. fake_port, None, None))
  956. # Test invalid host.
  957. self.sb_ovn.chassis_exists.return_value = False
  958. self.assertFalse(self.mech_driver._is_port_provisioning_required(
  959. fake_port, fake_host, None))
  960. self.sb_ovn.chassis_exists.return_value = True
  961. # Test port provisioning required.
  962. self.assertTrue(self.mech_driver._is_port_provisioning_required(
  963. fake_port, fake_host, None))
  964. def _test_add_subnet_dhcp_options_in_ovn(self, subnet, ovn_dhcp_opts=None,
  965. call_get_dhcp_opts=True,
  966. call_add_dhcp_opts=True):
  967. subnet['id'] = 'fake_id'
  968. with mock.patch.object(self.mech_driver._ovn_client,
  969. '_get_ovn_dhcp_options') as get_opts:
  970. self.mech_driver._ovn_client._add_subnet_dhcp_options(
  971. subnet, mock.ANY, ovn_dhcp_opts)
  972. self.assertEqual(call_get_dhcp_opts, get_opts.called)
  973. self.assertEqual(
  974. call_add_dhcp_opts,
  975. self.mech_driver._nb_ovn.add_dhcp_options.called)
  976. def test_add_subnet_dhcp_options_in_ovn(self):
  977. subnet = {'ip_version': const.IP_VERSION_4}
  978. self._test_add_subnet_dhcp_options_in_ovn(subnet)
  979. def test_add_subnet_dhcp_options_in_ovn_with_given_ovn_dhcp_opts(self):
  980. subnet = {'ip_version': const.IP_VERSION_4}
  981. self._test_add_subnet_dhcp_options_in_ovn(
  982. subnet, ovn_dhcp_opts={'foo': 'bar', 'external_ids': {}},
  983. call_get_dhcp_opts=False)
  984. def test_add_subnet_dhcp_options_in_ovn_with_slaac_v6_subnet(self):
  985. subnet = {'ip_version': const.IP_VERSION_6,
  986. 'ipv6_address_mode': const.IPV6_SLAAC}
  987. self._test_add_subnet_dhcp_options_in_ovn(
  988. subnet, call_get_dhcp_opts=False, call_add_dhcp_opts=False)
  989. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_ports')
  990. @mock.patch('neutron_lib.utils.net.get_random_mac')
  991. def test_enable_subnet_dhcp_options_in_ovn_ipv4(self, grm, gps):
  992. grm.return_value = '01:02:03:04:05:06'
  993. gps.return_value = [
  994. {'id': 'port-id-1', 'device_owner': 'nova:compute'},
  995. {'id': 'port-id-2', 'device_owner': 'nova:compute',
  996. 'extra_dhcp_opts': [
  997. {'opt_value': '10.0.0.33', 'ip_version': 4,
  998. 'opt_name': 'router'}]},
  999. {'id': 'port-id-3', 'device_owner': 'nova:compute',
  1000. 'extra_dhcp_opts': [
  1001. {'opt_value': '1200', 'ip_version': 4,
  1002. 'opt_name': 'mtu'}]},
  1003. {'id': 'port-id-10', 'device_owner': 'network:foo'}]
  1004. subnet = {'id': 'subnet-id', 'ip_version': 4, 'cidr': '10.0.0.0/24',
  1005. 'network_id': 'network-id',
  1006. 'gateway_ip': '10.0.0.1', 'enable_dhcp': True,
  1007. 'dns_nameservers': [], 'host_routes': []}
  1008. network = {'id': 'network-id', 'mtu': 1000}
  1009. txn = self.mech_driver._nb_ovn.transaction().__enter__.return_value
  1010. dhcp_option_command = mock.Mock()
  1011. txn.add.return_value = dhcp_option_command
  1012. self.mech_driver._ovn_client._enable_subnet_dhcp_options(
  1013. subnet, network, txn)
  1014. # Check adding DHCP_Options rows
  1015. subnet_dhcp_options = {
  1016. 'external_ids': {'subnet_id': subnet['id'],
  1017. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1'},
  1018. 'cidr': subnet['cidr'], 'options': {
  1019. 'router': subnet['gateway_ip'],
  1020. 'server_id': subnet['gateway_ip'],
  1021. 'server_mac': '01:02:03:04:05:06',
  1022. 'dns_server': '{8.8.8.8}',
  1023. 'lease_time': str(12 * 60 * 60),
  1024. 'mtu': str(1000)}}
  1025. ports_dhcp_options = [{
  1026. 'external_ids': {'subnet_id': subnet['id'],
  1027. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
  1028. 'port_id': 'port-id-2'},
  1029. 'cidr': subnet['cidr'], 'options': {
  1030. 'router': '10.0.0.33',
  1031. 'server_id': subnet['gateway_ip'],
  1032. 'dns_server': '{8.8.8.8}',
  1033. 'server_mac': '01:02:03:04:05:06',
  1034. 'lease_time': str(12 * 60 * 60),
  1035. 'mtu': str(1000)}}, {
  1036. 'external_ids': {'subnet_id': subnet['id'],
  1037. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
  1038. 'port_id': 'port-id-3'},
  1039. 'cidr': subnet['cidr'], 'options': {
  1040. 'router': subnet['gateway_ip'],
  1041. 'server_id': subnet['gateway_ip'],
  1042. 'dns_server': '{8.8.8.8}',
  1043. 'server_mac': '01:02:03:04:05:06',
  1044. 'lease_time': str(12 * 60 * 60),
  1045. 'mtu': str(1200)}}]
  1046. add_dhcp_calls = [mock.call('subnet-id', **subnet_dhcp_options)]
  1047. add_dhcp_calls.extend([mock.call(
  1048. 'subnet-id', port_id=port_dhcp_options['external_ids']['port_id'],
  1049. **port_dhcp_options) for port_dhcp_options in ports_dhcp_options])
  1050. self.assertEqual(len(add_dhcp_calls),
  1051. self.mech_driver._nb_ovn.add_dhcp_options.call_count)
  1052. self.mech_driver._nb_ovn.add_dhcp_options.assert_has_calls(
  1053. add_dhcp_calls, any_order=True)
  1054. # Check setting lport rows
  1055. set_lsp_calls = [mock.call(lport_name='port-id-1',
  1056. dhcpv4_options=dhcp_option_command),
  1057. mock.call(lport_name='port-id-2',
  1058. dhcpv4_options=dhcp_option_command),
  1059. mock.call(lport_name='port-id-3',
  1060. dhcpv4_options=dhcp_option_command)]
  1061. self.assertEqual(len(set_lsp_calls),
  1062. self.mech_driver._nb_ovn.set_lswitch_port.call_count)
  1063. self.mech_driver._nb_ovn.set_lswitch_port.assert_has_calls(
  1064. set_lsp_calls, any_order=True)
  1065. @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_ports')
  1066. @mock.patch('neutron_lib.utils.net.get_random_mac')
  1067. def test_enable_subnet_dhcp_options_in_ovn_ipv6(self, grm, gps):
  1068. grm.return_value = '01:02:03:04:05:06'
  1069. gps.return_value = [
  1070. {'id': 'port-id-1', 'device_owner': 'nova:compute'},
  1071. {'id': 'port-id-2', 'device_owner': 'nova:compute',
  1072. 'extra_dhcp_opts': [
  1073. {'opt_value': '11:22:33:44:55:66', 'ip_version': 6,
  1074. 'opt_name': 'server-id'}]},
  1075. {'id': 'port-id-3', 'device_owner': 'nova:compute',
  1076. 'extra_dhcp_opts': [
  1077. {'opt_value': '10::34', 'ip_version': 6,
  1078. 'opt_name': 'dns-server'}]},
  1079. {'id': 'port-id-10', 'device_owner': 'network:foo'}]
  1080. subnet = {'id': 'subnet-id', 'ip_version': 6, 'cidr': '10::0/64',
  1081. 'gateway_ip': '10::1', 'enable_dhcp': True,
  1082. 'ipv6_address_mode': 'dhcpv6-stateless',
  1083. 'dns_nameservers': [], 'host_routes': []}
  1084. network = {'id': 'network-id', 'mtu': 1000}
  1085. txn = self.mech_driver._nb_ovn.transaction().__enter__.return_value
  1086. dhcp_option_command = mock.Mock()
  1087. txn.add.return_value = dhcp_option_command
  1088. self.mech_driver._ovn_client._enable_subnet_dhcp_options(
  1089. subnet, network, txn)
  1090. # Check adding DHCP_Options rows
  1091. subnet_dhcp_options = {
  1092. 'external_ids': {'subnet_id': subnet['id'],
  1093. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1'},
  1094. 'cidr': subnet['cidr'], 'options': {
  1095. 'dhcpv6_stateless': 'true',
  1096. 'server_id': '01:02:03:04:05:06'}}
  1097. ports_dhcp_options = [{
  1098. 'external_ids': {'subnet_id': subnet['id'],
  1099. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
  1100. 'port_id': 'port-id-2'},
  1101. 'cidr': subnet['cidr'], 'options': {
  1102. 'dhcpv6_stateless': 'true',
  1103. 'server_id': '11:22:33:44:55:66'}}, {
  1104. 'external_ids': {'subnet_id': subnet['id'],
  1105. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
  1106. 'port_id': 'port-id-3'},
  1107. 'cidr': subnet['cidr'], 'options': {
  1108. 'dhcpv6_stateless': 'true',
  1109. 'server_id': '01:02:03:04:05:06',
  1110. 'dns_server': '10::34'}}]
  1111. add_dhcp_calls = [mock.call('subnet-id', **subnet_dhcp_options)]
  1112. add_dhcp_calls.extend([mock.call(
  1113. 'subnet-id', port_id=port_dhcp_options['external_ids']['port_id'],
  1114. **port_dhcp_options) for port_dhcp_options in ports_dhcp_options])
  1115. self.assertEqual(len(add_dhcp_calls),
  1116. self.mech_driver._nb_ovn.add_dhcp_options.call_count)
  1117. self.mech_driver._nb_ovn.add_dhcp_options.assert_has_calls(
  1118. add_dhcp_calls, any_order=True)
  1119. # Check setting lport rows
  1120. set_lsp_calls = [mock.call(lport_name='port-id-1',
  1121. dhcpv6_options=dhcp_option_command),
  1122. mock.call(lport_name='port-id-2',
  1123. dhcpv6_options=dhcp_option_command),
  1124. mock.call(lport_name='port-id-3',
  1125. dhcpv6_options=dhcp_option_command)]
  1126. self.assertEqual(len(set_lsp_calls),
  1127. self.mech_driver._nb_ovn.set_lswitch_port.call_count)
  1128. self.mech_driver._nb_ovn.set_lswitch_port.assert_has_calls(
  1129. set_lsp_calls, any_order=True)
  1130. def test_enable_subnet_dhcp_options_in_ovn_ipv6_slaac(self):
  1131. subnet = {'id': 'subnet-id', 'ip_version': 6, 'enable_dhcp': True,
  1132. 'ipv6_address_mode': 'slaac'}
  1133. network = {'id': 'network-id'}
  1134. self.mech_driver._ovn_client._enable_subnet_dhcp_options(
  1135. subnet, network, mock.Mock())
  1136. self.mech_driver._nb_ovn.add_dhcp_options.assert_not_called()
  1137. self.mech_driver._nb_ovn.set_lswitch_port.assert_not_called()
  1138. def _test_remove_subnet_dhcp_options_in_ovn(self, ip_version):
  1139. opts = {'subnet': {'uuid': 'subnet-uuid'},
  1140. 'ports': [{'uuid': 'port1-uuid'}]}
  1141. self.mech_driver._nb_ovn.get_subnet_dhcp_options.return_value = opts
  1142. self.mech_driver._ovn_client._remove_subnet_dhcp_options(
  1143. 'subnet-id', mock.Mock())
  1144. # Check deleting DHCP_Options rows
  1145. delete_dhcp_calls = [mock.call('subnet-uuid'), mock.call('port1-uuid')]
  1146. self.assertEqual(
  1147. len(delete_dhcp_calls),
  1148. self.mech_driver._nb_ovn.delete_dhcp_options.call_count)
  1149. self.mech_driver._nb_ovn.delete_dhcp_options.assert_has_calls(
  1150. delete_dhcp_calls, any_order=True)
  1151. def test_remove_subnet_dhcp_options_in_ovn_ipv4(self):
  1152. self._test_remove_subnet_dhcp_options_in_ovn(4)
  1153. def test_remove_subnet_dhcp_options_in_ovn_ipv6(self):
  1154. self._test_remove_subnet_dhcp_options_in_ovn(6)
  1155. def test_update_subnet_dhcp_options_in_ovn_ipv4(self):
  1156. subnet = {'id': 'subnet-id', 'ip_version': 4, 'cidr': '10.0.0.0/24',
  1157. 'network_id': 'network-id',
  1158. 'gateway_ip': '10.0.0.1', 'enable_dhcp': True,
  1159. 'dns_nameservers': [], 'host_routes': []}
  1160. network = {'id': 'network-id', 'mtu': 1000}
  1161. orignal_options = {'subnet': {
  1162. 'external_ids': {'subnet_id': subnet['id']},
  1163. 'cidr': subnet['cidr'], 'options': {
  1164. 'router': '10.0.0.2',
  1165. 'server_id': '10.0.0.2',
  1166. 'server_mac': '01:02:03:04:05:06',
  1167. 'dns_server': '{8.8.8.8}',
  1168. 'lease_time': str(12 * 60 * 60),
  1169. 'mtu': str(1000)}}, 'ports': []}
  1170. self.mech_driver._nb_ovn.get_subnet_dhcp_options.return_value =\
  1171. orignal_options
  1172. self.mech_driver._ovn_client._update_subnet_dhcp_options(
  1173. subnet, network, mock.Mock())
  1174. new_options = {
  1175. 'external_ids': {'subnet_id': subnet['id'],
  1176. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1'},
  1177. 'cidr': subnet['cidr'], 'options': {
  1178. 'router': subnet['gateway_ip'],
  1179. 'server_id': subnet['gateway_ip'],
  1180. 'dns_server': '{8.8.8.8}',
  1181. 'server_mac': '01:02:03:04:05:06',
  1182. 'lease_time': str(12 * 60 * 60),
  1183. 'mtu': str(1000)}}
  1184. self.mech_driver._nb_ovn.add_dhcp_options.assert_called_once_with(
  1185. subnet['id'], **new_options)
  1186. def test_update_subnet_dhcp_options_in_ovn_ipv4_not_change(self):
  1187. subnet = {'id': 'subnet-id', 'ip_version': 4, 'cidr': '10.0.0.0/24',
  1188. 'network_id': 'network-id',
  1189. 'gateway_ip': '10.0.0.1', 'enable_dhcp': True,
  1190. 'dns_nameservers': [], 'host_routes': []}
  1191. network = {'id': 'network-id', 'mtu': 1000}
  1192. orignal_options = {'subnet': {
  1193. 'external_ids': {'subnet_id': subnet['id']},
  1194. 'cidr': subnet['cidr'], 'options': {
  1195. 'router': subnet['gateway_ip'],
  1196. 'server_id': subnet['gateway_ip'],
  1197. 'server_mac': '01:02:03:04:05:06',
  1198. 'dns_server': '{8.8.8.8}',
  1199. 'lease_time': str(12 * 60 * 60),
  1200. 'mtu': str(1000)}}, 'ports': []}
  1201. self.mech_driver._nb_ovn.get_subnet_dhcp_options.return_value =\
  1202. orignal_options
  1203. self.mech_driver._ovn_client._update_subnet_dhcp_options(
  1204. subnet, network, mock.Mock())
  1205. self.mech_driver._nb_ovn.add_dhcp_options.assert_not_called()
  1206. def test_update_subnet_dhcp_options_in_ovn_ipv6(self):
  1207. subnet = {'id': 'subnet-id', 'ip_version': 6, 'cidr': '10::0/64',
  1208. 'network_id': 'network-id',
  1209. 'gateway_ip': '10::1', 'enable_dhcp': True,
  1210. 'ipv6_address_mode': 'dhcpv6-stateless',
  1211. 'dns_nameservers': ['10::3'], 'host_routes': []}
  1212. network = {'id': 'network-id', 'mtu': 1000}
  1213. orignal_options = {'subnet': {
  1214. 'external_ids': {'subnet_id': subnet['id']},
  1215. 'cidr': subnet['cidr'], 'options': {
  1216. 'dhcpv6_stateless': 'true',
  1217. 'server_id': '01:02:03:04:05:06'}}, 'ports': []}
  1218. self.mech_driver._nb_ovn.get_subnet_dhcp_options.return_value =\
  1219. orignal_options
  1220. self.mech_driver._ovn_client._update_subnet_dhcp_options(
  1221. subnet, network, mock.Mock())
  1222. new_options = {
  1223. 'external_ids': {'subnet_id': subnet['id'],
  1224. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1'},
  1225. 'cidr': subnet['cidr'], 'options': {
  1226. 'dhcpv6_stateless': 'true',
  1227. 'dns_server': '{10::3}',
  1228. 'server_id': '01:02:03:04:05:06'}}
  1229. self.mech_driver._nb_ovn.add_dhcp_options.assert_called_once_with(
  1230. subnet['id'], **new_options)
  1231. def test_update_subnet_dhcp_options_in_ovn_ipv6_not_change(self):
  1232. subnet = {'id': 'subnet-id', 'ip_version': 6, 'cidr': '10::0/64',
  1233. 'gateway_ip': '10::1', 'enable_dhcp': True,
  1234. 'ipv6_address_mode': 'dhcpv6-stateless',
  1235. 'dns_nameservers': [], 'host_routes': []}
  1236. network = {'id': 'network-id', 'mtu': 1000}
  1237. orignal_options = {'subnet': {
  1238. 'external_ids': {'subnet_id': subnet['id']},
  1239. 'cidr': subnet['cidr'], 'options': {
  1240. 'dhcpv6_stateless': 'true',
  1241. 'server_id': '01:02:03:04:05:06'}}, 'ports': []}
  1242. self.mech_driver._nb_ovn.get_subnet_dhcp_options.return_value =\
  1243. orignal_options
  1244. self.mech_driver._ovn_client._update_subnet_dhcp_options(
  1245. subnet, network, mock.Mock())
  1246. self.mech_driver._nb_ovn.add_dhcp_options.assert_not_called()
  1247. def test_update_subnet_dhcp_options_in_ovn_ipv6_slaac(self):
  1248. subnet = {'id': 'subnet-id', 'ip_version': 6, 'enable_dhcp': True,
  1249. 'ipv6_address_mode': 'slaac'}
  1250. network = {'id': 'network-id'}
  1251. self.mech_driver._ovn_client._update_subnet_dhcp_options(
  1252. subnet, network, mock.Mock())
  1253. self.mech_driver._nb_ovn.get_subnet_dhcp_options.assert_not_called()
  1254. self.mech_driver._nb_ovn.add_dhcp_options.assert_not_called()
  1255. def test_update_subnet_postcommit_ovn_do_nothing(self):
  1256. context = fakes.FakeSubnetContext(
  1257. subnet={'enable_dhcp': False, 'ip_version': 4, 'network_id': 'id',
  1258. 'id': 'subnet_id'},
  1259. network={'id': 'id'})
  1260. with mock.patch.object(
  1261. self.mech_driver._ovn_client,
  1262. '_enable_subnet_dhcp_options') as esd,\
  1263. mock.patch.object(
  1264. self.mech_driver._ovn_client,
  1265. '_remove_subnet_dhcp_options') as dsd,\
  1266. mock.patch.object(
  1267. self.mech_driver._ovn_client,
  1268. '_update_subnet_dhcp_options') as usd,\
  1269. mock.patch.object(
  1270. self.mech_driver._ovn_client,
  1271. '_find_metadata_port') as fmd,\
  1272. mock.patch.object(
  1273. self.mech_driver._ovn_client,
  1274. 'update_metadata_port') as umd:
  1275. self.mech_driver.update_subnet_postcommit(context)
  1276. esd.assert_not_called()
  1277. dsd.assert_not_called()
  1278. usd.assert_not_called()
  1279. fmd.assert_not_called()
  1280. umd.assert_not_called()
  1281. def test_update_subnet_postcommit_enable_dhcp(self):
  1282. context = fakes.FakeSubnetContext(
  1283. subnet={'enable_dhcp': True, 'ip_version': 4, 'network_id': 'id',
  1284. 'id': 'subnet_id'},
  1285. network={'id': 'id'})
  1286. with mock.patch.object(
  1287. self.mech_driver._ovn_client,
  1288. '_enable_subnet_dhcp_options') as esd,\
  1289. mock.patch.object(
  1290. self.mech_driver._ovn_client,
  1291. 'update_metadata_port') as umd:
  1292. self.mech_driver.update_subnet_postcommit(context)
  1293. esd.assert_called_once_with(
  1294. context.current, context.network.current, mock.ANY)
  1295. umd.assert_called_once_with(mock.ANY, 'id')
  1296. def test_update_subnet_postcommit_disable_dhcp(self):
  1297. self.mech_driver._nb_ovn.get_subnet_dhcp_options.return_value = {
  1298. 'subnet': mock.sentinel.subnet, 'ports': []}
  1299. context = fakes.FakeSubnetContext(
  1300. subnet={'enable_dhcp': False, 'id': 'fake_id', 'ip_version': 4,
  1301. 'network_id': 'id'},
  1302. network={'id': 'id'})
  1303. with mock.patch.object(
  1304. self.mech_driver._ovn_client,
  1305. '_remove_subnet_dhcp_options') as dsd,\
  1306. mock.patch.object(
  1307. self.mech_driver._ovn_client,
  1308. 'update_metadata_port') as umd:
  1309. self.mech_driver.update_subnet_postcommit(context)
  1310. dsd.assert_called_once_with(context.current['id'], mock.ANY)
  1311. umd.assert_called_once_with(mock.ANY, 'id')
  1312. def test_update_subnet_postcommit_update_dhcp(self):
  1313. self.mech_driver._nb_ovn.get_subnet_dhcp_options.return_value = {
  1314. 'subnet': mock.sentinel.subnet, 'ports': []}
  1315. context = fakes.FakeSubnetContext(
  1316. subnet={'enable_dhcp': True, 'ip_version': 4, 'network_id': 'id',
  1317. 'id': 'subnet_id'},
  1318. network={'id': 'id'})
  1319. with mock.patch.object(
  1320. self.mech_driver._ovn_client,
  1321. '_update_subnet_dhcp_options') as usd,\
  1322. mock.patch.object(
  1323. self.mech_driver._ovn_client,
  1324. 'update_metadata_port') as umd:
  1325. self.mech_driver.update_subnet_postcommit(context)
  1326. usd.assert_called_once_with(
  1327. context.current, context.network.current, mock.ANY)
  1328. umd.assert_called_once_with(mock.ANY, 'id')
  1329. @mock.patch.object(provisioning_blocks, 'is_object_blocked')
  1330. @mock.patch.object(provisioning_blocks, 'provisioning_complete')
  1331. def test_notify_dhcp_updated(self, mock_prov_complete, mock_is_obj_block):
  1332. port_id = 'fake-port-id'
  1333. mock_is_obj_block.return_value = True
  1334. self.mech_driver._notify_dhcp_updated(port_id)
  1335. mock_prov_complete.assert_called_once_with(
  1336. mock.ANY, port_id, resources.PORT,
  1337. provisioning_blocks.DHCP_ENTITY)
  1338. mock_is_obj_block.return_value = False
  1339. mock_prov_complete.reset_mock()
  1340. self.mech_driver._notify_dhcp_updated(port_id)
  1341. mock_prov_complete.assert_not_called()
  1342. @mock.patch.object(mech_driver.OVNMechanismDriver,
  1343. '_is_port_provisioning_required', lambda *_: True)
  1344. @mock.patch.object(mech_driver.OVNMechanismDriver, '_notify_dhcp_updated')
  1345. @mock.patch.object(ovn_client.OVNClient, 'create_port')
  1346. def test_create_port_postcommit(self, mock_create_port, mock_notify_dhcp):
  1347. fake_port = fakes.FakePort.create_one_port(
  1348. attrs={'status': const.PORT_STATUS_DOWN}).info()
  1349. fake_ctx = mock.Mock(current=fake_port)
  1350. self.mech_driver.create_port_postcommit(fake_ctx)
  1351. passed_fake_port = copy.deepcopy(fake_port)
  1352. passed_fake_port['network'] = fake_ctx.network.current
  1353. mock_create_port.assert_called_once_with(passed_fake_port)
  1354. mock_notify_dhcp.assert_called_once_with(fake_port['id'])
  1355. @mock.patch.object(mech_driver.OVNMechanismDriver,
  1356. '_is_port_provisioning_required', lambda *_: True)
  1357. @mock.patch.object(mech_driver.OVNMechanismDriver, '_notify_dhcp_updated')
  1358. @mock.patch.object(ovn_client.OVNClient, 'update_port')
  1359. def test_update_port_postcommit(self, mock_update_port,
  1360. mock_notify_dhcp):
  1361. fake_port = fakes.FakePort.create_one_port(
  1362. attrs={'status': const.PORT_STATUS_ACTIVE}).info()
  1363. fake_ctx = mock.Mock(current=fake_port, original=fake_port)
  1364. self.mech_driver.update_port_postcommit(fake_ctx)
  1365. passed_fake_port = copy.deepcopy(fake_port)
  1366. passed_fake_port['network'] = fake_ctx.network.current
  1367. passed_fake_port_orig = copy.deepcopy(fake_ctx.original)
  1368. passed_fake_port_orig['network'] = fake_ctx.network.current
  1369. mock_update_port.assert_called_once_with(
  1370. passed_fake_port, port_object=passed_fake_port_orig)
  1371. mock_notify_dhcp.assert_called_once_with(fake_port['id'])
  1372. @mock.patch.object(mech_driver.OVNMechanismDriver,
  1373. '_is_port_provisioning_required', lambda *_: True)
  1374. @mock.patch.object(mech_driver.OVNMechanismDriver, '_notify_dhcp_updated')
  1375. @mock.patch.object(ovn_client.OVNClient, 'update_port')
  1376. @mock.patch.object(context, 'get_admin_context')
  1377. def test_update_port_postcommit_live_migration(
  1378. self, mock_admin_context, mock_update_port, mock_notify_dhcp):
  1379. self.plugin.update_port_status = mock.Mock()
  1380. foo_admin_context = mock.Mock()
  1381. mock_admin_context.return_value = foo_admin_context
  1382. fake_port = fakes.FakePort.create_one_port(
  1383. attrs={
  1384. 'status': const.PORT_STATUS_DOWN,
  1385. portbindings.PROFILE: {ovn_const.MIGRATING_ATTR: 'foo'},
  1386. portbindings.VIF_TYPE: portbindings.VIF_TYPE_OVS}).info()
  1387. fake_ctx = mock.Mock(current=fake_port, original=fake_port)
  1388. self.mech_driver.update_port_postcommit(fake_ctx)
  1389. mock_update_port.assert_not_called()
  1390. mock_notify_dhcp.assert_not_called()
  1391. self.plugin.update_port_status.assert_called_once_with(
  1392. foo_admin_context, fake_port['id'], const.PORT_STATUS_ACTIVE)
  1393. def _add_chassis_agent(self, nb_cfg, agent_type, updated_at=None):
  1394. chassis = mock.Mock()
  1395. chassis.nb_cfg = nb_cfg
  1396. chassis.uuid = uuid.uuid4()
  1397. id_ = chassis.uuid
  1398. if agent_type == ovn_const.OVN_METADATA_AGENT:
  1399. chassis.external_ids = {
  1400. ovn_const.OVN_AGENT_METADATA_SB_CFG_KEY: nb_cfg}
  1401. id_ = ovn_utils.ovn_metadata_name(chassis.uuid)
  1402. stats.AgentStats.add_stat(id_, nb_cfg, updated_at)
  1403. return chassis
  1404. def test_agent_alive_true(self):
  1405. for agent_type in (ovn_const.OVN_CONTROLLER_AGENT,
  1406. ovn_const.OVN_METADATA_AGENT):
  1407. self.mech_driver._nb_ovn.nb_global.nb_cfg = 5
  1408. chassis = self._add_chassis_agent(5, agent_type)
  1409. self.assertTrue(self.mech_driver.agent_alive(chassis, agent_type))
  1410. def test_agent_alive_not_timed_out(self):
  1411. for agent_type in (ovn_const.OVN_CONTROLLER_AGENT,
  1412. ovn_const.OVN_METADATA_AGENT):
  1413. self.mech_driver._nb_ovn.nb_global.nb_cfg = 5
  1414. chassis = self._add_chassis_agent(4, agent_type)
  1415. self.assertTrue(self.mech_driver.agent_alive(chassis, agent_type))
  1416. def test_agent_alive_timed_out(self):
  1417. for agent_type in (ovn_const.OVN_CONTROLLER_AGENT,
  1418. ovn_const.OVN_METADATA_AGENT):
  1419. self.mech_driver._nb_ovn.nb_global.nb_cfg = 5
  1420. now = timeutils.utcnow()
  1421. updated_at = now - datetime.timedelta(cfg.CONF.agent_down_time + 1)
  1422. chassis = self._add_chassis_agent(4, agent_type, updated_at)
  1423. self.assertFalse(self.mech_driver.agent_alive(chassis, agent_type))
  1424. def test_agent_not_found(self):
  1425. agent_type = ovn_const.OVN_CONTROLLER_AGENT
  1426. chassis = self._add_chassis_agent(1, agent_type)
  1427. self.mech_driver._nb_ovn.nb_global.nb_cfg = 1
  1428. # Assert that the agent has been registered and is alive
  1429. self.assertTrue(self.mech_driver.agent_alive(chassis, agent_type))
  1430. self.mech_driver._nb_ovn.nb_global.nb_cfg = 2
  1431. # Delete the agent from the stats tracker
  1432. stats.AgentStats.del_agent(chassis.uuid)
  1433. # Assert that subsequently calls checking the status of the agent
  1434. # shows it as "dead" instead of blowing up with an exception
  1435. self.assertFalse(self.mech_driver.agent_alive(chassis, agent_type))
  1436. def _test__update_dnat_entry_if_needed(self, up=True):
  1437. ovn_config.cfg.CONF.set_override(
  1438. 'enable_distributed_floating_ip', True, group='ovn')
  1439. port_id = 'fake-port-id'
  1440. fake_ext_mac_key = 'fake-ext-mac-key'
  1441. fake_nat_uuid = uuidutils.generate_uuid()
  1442. nat_row = fakes.FakeOvsdbRow.create_one_ovsdb_row(
  1443. attrs={'_uuid': fake_nat_uuid, 'external_ids': {
  1444. ovn_const.OVN_FIP_EXT_MAC_KEY: fake_ext_mac_key}})
  1445. fake_db_find = mock.Mock()
  1446. fake_db_find.execute.return_value = [nat_row]
  1447. self.nb_ovn.db_find.return_value = fake_db_find
  1448. self.mech_driver._update_dnat_entry_if_needed(port_id, up=up)
  1449. if up:
  1450. # Assert that we are setting the external_mac in the NAT table
  1451. self.nb_ovn.db_set.assert_called_once_with(
  1452. 'NAT', fake_nat_uuid, ('external_mac', fake_ext_mac_key))
  1453. else:
  1454. # Assert that we are cleaning the external_mac from the NAT table
  1455. self.nb_ovn.db_clear.assert_called_once_with(
  1456. 'NAT', fake_nat_uuid, 'external_mac')
  1457. def test__update_dnat_entry_if_needed_up(self):
  1458. self._test__update_dnat_entry_if_needed()
  1459. def test__update_dnat_entry_if_needed_down(self):
  1460. self._test__update_dnat_entry_if_needed(up=False)
  1461. class OVNMechanismDriverTestCase(test_plugin.Ml2PluginV2TestCase):
  1462. _mechanism_drivers = ['logger', 'ovn']
  1463. def setUp(self):
  1464. cfg.CONF.set_override('tenant_network_types',
  1465. ['geneve'],
  1466. group='ml2')
  1467. cfg.CONF.set_override('vni_ranges',
  1468. ['1:65536'],
  1469. group='ml2_type_geneve')
  1470. ovn_config.cfg.CONF.set_override('dns_servers',
  1471. ['8.8.8.8'],
  1472. group='ovn')
  1473. super(OVNMechanismDriverTestCase, self).setUp()
  1474. mm = directory.get_plugin().mechanism_manager
  1475. self.mech_driver = mm.mech_drivers['ovn'].obj
  1476. nb_ovn = fakes.FakeOvsdbNbOvnIdl()
  1477. sb_ovn = fakes.FakeOvsdbSbOvnIdl()
  1478. self.mech_driver._nb_ovn = nb_ovn
  1479. self.mech_driver._sb_ovn = sb_ovn
  1480. self.mech_driver._insert_port_provisioning_block = mock.Mock()
  1481. p = mock.patch.object(ovn_utils, 'get_revision_number', return_value=1)
  1482. p.start()
  1483. self.addCleanup(p.stop)
  1484. class TestOVNMechansimDriverBasicGet(test_plugin.TestMl2BasicGet,
  1485. OVNMechanismDriverTestCase):
  1486. pass
  1487. class TestOVNMechansimDriverV2HTTPResponse(test_plugin.TestMl2V2HTTPResponse,
  1488. OVNMechanismDriverTestCase):
  1489. pass
  1490. class TestOVNMechansimDriverNetworksV2(test_plugin.TestMl2NetworksV2,
  1491. OVNMechanismDriverTestCase):
  1492. pass
  1493. class TestOVNMechansimDriverSubnetsV2(test_plugin.TestMl2SubnetsV2,
  1494. OVNMechanismDriverTestCase):
  1495. def setUp(self):
  1496. # Disable metadata so that we don't interfere with existing tests
  1497. # in Neutron tree. Doing this because some of the tests assume that
  1498. # first IP address in a subnet will be available and this is not true
  1499. # with metadata since it will book an IP address on each subnet.
  1500. ovn_config.cfg.CONF.set_override('ovn_metadata_enabled',
  1501. False,
  1502. group='ovn')
  1503. super(TestOVNMechansimDriverSubnetsV2, self).setUp()
  1504. # NOTE(rtheis): Mock the OVN port update since it is getting subnet
  1505. # information for ACL processing. This interferes with the update_port
  1506. # mock already done by the test.
  1507. def test_subnet_update_ipv4_and_ipv6_pd_v6stateless_subnets(self):
  1508. with mock.patch.object(self.mech_driver._ovn_client, 'update_port'),\
  1509. mock.patch.object(self.mech_driver._ovn_client,
  1510. '_get_subnet_dhcp_options_for_port',
  1511. return_value={}):
  1512. super(TestOVNMechansimDriverSubnetsV2, self).\
  1513. test_subnet_update_ipv4_and_ipv6_pd_v6stateless_subnets()
  1514. # NOTE(rtheis): Mock the OVN port update since it is getting subnet
  1515. # information for ACL processing. This interferes with the update_port
  1516. # mock already done by the test.
  1517. def test_subnet_update_ipv4_and_ipv6_pd_slaac_subnets(self):
  1518. with mock.patch.object(self.mech_driver._ovn_client, 'update_port'),\
  1519. mock.patch.object(self.mech_driver._ovn_client,
  1520. '_get_subnet_dhcp_options_for_port',
  1521. return_value={}):
  1522. super(TestOVNMechansimDriverSubnetsV2, self).\
  1523. test_subnet_update_ipv4_and_ipv6_pd_slaac_subnets()
  1524. # NOTE(numans) Overriding the base test case here because the base test
  1525. # case creates a network with vxlan type and OVN mech driver doesn't
  1526. # support it.
  1527. def test_create_subnet_check_mtu_in_mech_context(self):
  1528. plugin = directory.get_plugin()
  1529. plugin.mechanism_manager.create_subnet_precommit = mock.Mock()
  1530. net_arg = {pnet.NETWORK_TYPE: 'geneve',
  1531. pnet.SEGMENTATION_ID: '1'}
  1532. network = self._make_network(self.fmt, 'net1', True,
  1533. arg_list=(pnet.NETWORK_TYPE,
  1534. pnet.SEGMENTATION_ID,),
  1535. **net_arg)
  1536. with self.subnet(network=network):
  1537. mock_subnet_pre = plugin.mechanism_manager.create_subnet_precommit
  1538. observerd_mech_context = mock_subnet_pre.call_args_list[0][0][0]
  1539. self.assertEqual(network['network']['mtu'],
  1540. observerd_mech_context.network.current['mtu'])
  1541. class TestOVNMechansimDriverPortsV2(test_plugin.TestMl2PortsV2,
  1542. OVNMechanismDriverTestCase):
  1543. def setUp(self):
  1544. # Disable metadata so that we don't interfere with existing tests
  1545. # in Neutron tree. Doing this because some of the tests assume that
  1546. # first IP address in a subnet will be available and this is not true
  1547. # with metadata since it will book an IP address on each subnet.
  1548. ovn_config.cfg.CONF.set_override('ovn_metadata_enabled',
  1549. False,
  1550. group='ovn')
  1551. super(TestOVNMechansimDriverPortsV2, self).setUp()
  1552. # NOTE(rtheis): Override this test to verify that updating
  1553. # a port MAC fails when the port is bound.
  1554. def test_update_port_mac(self):
  1555. self.check_update_port_mac(
  1556. host_arg={portbindings.HOST_ID: 'fake-host'},
  1557. arg_list=(portbindings.HOST_ID,),
  1558. expected_status=exc.HTTPConflict.code,
  1559. expected_error='PortBound')
  1560. class TestOVNMechansimDriverAllowedAddressPairs(
  1561. test_plugin.TestMl2AllowedAddressPairs,
  1562. OVNMechanismDriverTestCase):
  1563. pass
  1564. class TestOVNMechansimDriverPortSecurity(
  1565. test_ext_portsecurity.PSExtDriverTestCase,
  1566. OVNMechanismDriverTestCase):
  1567. pass
  1568. class TestOVNMechansimDriverSegment(test_segment.HostSegmentMappingTestCase):
  1569. _mechanism_drivers = ['logger', 'ovn']
  1570. def setUp(self):
  1571. super(TestOVNMechansimDriverSegment, self).setUp()
  1572. mm = directory.get_plugin().mechanism_manager
  1573. self.mech_driver = mm.mech_drivers['ovn'].obj
  1574. nb_ovn = fakes.FakeOvsdbNbOvnIdl()
  1575. sb_ovn = fakes.FakeOvsdbSbOvnIdl()
  1576. self.mech_driver._nb_ovn = nb_ovn
  1577. self.mech_driver._sb_ovn = sb_ovn
  1578. p = mock.patch.object(ovn_utils, 'get_revision_number', return_value=1)
  1579. p.start()
  1580. self.addCleanup(p.stop)
  1581. def _test_segment_host_mapping(self):
  1582. # Disable the callback to update SegmentHostMapping by default, so
  1583. # that update_segment_host_mapping is the only path to add the mapping
  1584. registry.unsubscribe(
  1585. self.mech_driver._add_segment_host_mapping_for_segment,
  1586. resources.SEGMENT, events.AFTER_CREATE)
  1587. host = 'hostname'
  1588. with self.network() as network:
  1589. network = network['network']
  1590. segment1 = self._test_create_segment(
  1591. network_id=network['id'], physical_network='phys_net1',
  1592. segmentation_id=200, network_type='vlan')['segment']
  1593. # As geneve networks mtu shouldn't be more than 1450, update it
  1594. data = {'network': {'mtu': 1450}}
  1595. req = self.new_update_request('networks', data, network['id'])
  1596. res = self.deserialize(self.fmt, req.get_response(self.api))
  1597. self.assertEqual(1450, res['network']['mtu'])
  1598. self._test_create_segment(
  1599. network_id=network['id'],
  1600. segmentation_id=200,
  1601. network_type='geneve')['segment']
  1602. self.mech_driver.update_segment_host_mapping(host, ['phys_net1'])
  1603. segments_host_db = self._get_segments_for_host(host)
  1604. self.assertEqual({segment1['id']}, set(segments_host_db))
  1605. return network['id'], host
  1606. def test_update_segment_host_mapping(self):
  1607. network_id, host = self._test_segment_host_mapping()
  1608. # Update the mapping
  1609. segment2 = self._test_create_segment(
  1610. network_id=network_id, physical_network='phys_net2',
  1611. segmentation_id=201, network_type='vlan')['segment']
  1612. self.mech_driver.update_segment_host_mapping(host, ['phys_net2'])
  1613. segments_host_db = self._get_segments_for_host(host)
  1614. self.assertEqual({segment2['id']}, set(segments_host_db))
  1615. def test_clear_segment_host_mapping(self):
  1616. _, host = self._test_segment_host_mapping()
  1617. # Clear the mapping
  1618. self.mech_driver.update_segment_host_mapping(host, [])
  1619. segments_host_db = self._get_segments_for_host(host)
  1620. self.assertEqual({}, segments_host_db)
  1621. def test_update_segment_host_mapping_with_new_segment(self):
  1622. hostname_with_physnets = {'hostname1': ['phys_net1', 'phys_net2'],
  1623. 'hostname2': ['phys_net1']}
  1624. ovn_sb_api = self.mech_driver._sb_ovn
  1625. ovn_sb_api.get_chassis_hostname_and_physnets.return_value = (
  1626. hostname_with_physnets)
  1627. self.mech_driver.subscribe()
  1628. with self.network() as network:
  1629. network_id = network['network']['id']
  1630. segment = self._test_create_segment(
  1631. network_id=network_id, physical_network='phys_net2',
  1632. segmentation_id=201, network_type='vlan')['segment']
  1633. segments_host_db1 = self._get_segments_for_host('hostname1')
  1634. # A new SegmentHostMapping should be created for hostname1
  1635. self.assertEqual({segment['id']}, set(segments_host_db1))
  1636. segments_host_db2 = self._get_segments_for_host('hostname2')
  1637. self.assertFalse(set(segments_host_db2))
  1638. @mock.patch.object(n_net, 'get_random_mac', lambda *_: '01:02:03:04:05:06')
  1639. class TestOVNMechansimDriverDHCPOptions(OVNMechanismDriverTestCase):
  1640. def _test_get_ovn_dhcp_options_helper(self, subnet, network,
  1641. expected_dhcp_options,
  1642. service_mac=None):
  1643. dhcp_options = self.mech_driver._ovn_client._get_ovn_dhcp_options(
  1644. subnet, network, service_mac)
  1645. self.assertEqual(expected_dhcp_options, dhcp_options)
  1646. def test_get_ovn_dhcp_options(self):
  1647. subnet = {'id': 'foo-subnet', 'network_id': 'network-id',
  1648. 'cidr': '10.0.0.0/24',
  1649. 'ip_version': 4,
  1650. 'enable_dhcp': True,
  1651. 'gateway_ip': '10.0.0.1',
  1652. 'dns_nameservers': ['7.7.7.7', '8.8.8.8'],
  1653. 'host_routes': [{'destination': '20.0.0.4',
  1654. 'nexthop': '10.0.0.100'}]}
  1655. network = {'id': 'network-id', 'mtu': 1400}
  1656. expected_dhcp_options = {'cidr': '10.0.0.0/24',
  1657. 'external_ids': {
  1658. 'subnet_id': 'foo-subnet',
  1659. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1'}}
  1660. expected_dhcp_options['options'] = {
  1661. 'server_id': subnet['gateway_ip'],
  1662. 'server_mac': '01:02:03:04:05:06',
  1663. 'lease_time': str(12 * 60 * 60),
  1664. 'mtu': '1400',
  1665. 'router': subnet['gateway_ip'],
  1666. 'dns_server': '{7.7.7.7, 8.8.8.8}',
  1667. 'classless_static_route':
  1668. '{20.0.0.4,10.0.0.100, 0.0.0.0/0,10.0.0.1}'
  1669. }
  1670. self._test_get_ovn_dhcp_options_helper(subnet, network,
  1671. expected_dhcp_options)
  1672. expected_dhcp_options['options']['server_mac'] = '11:22:33:44:55:66'
  1673. self._test_get_ovn_dhcp_options_helper(subnet, network,
  1674. expected_dhcp_options,
  1675. service_mac='11:22:33:44:55:66')
  1676. def test_get_ovn_dhcp_options_dhcp_disabled(self):
  1677. subnet = {'id': 'foo-subnet', 'network_id': 'network-id',
  1678. 'cidr': '10.0.0.0/24',
  1679. 'ip_version': 4,
  1680. 'enable_dhcp': False,
  1681. 'gateway_ip': '10.0.0.1',
  1682. 'dns_nameservers': ['7.7.7.7', '8.8.8.8'],
  1683. 'host_routes': [{'destination': '20.0.0.4',
  1684. 'nexthop': '10.0.0.100'}]}
  1685. network = {'id': 'network-id', 'mtu': 1400}
  1686. expected_dhcp_options = {'cidr': '10.0.0.0/24',
  1687. 'external_ids': {
  1688. 'subnet_id': 'foo-subnet',
  1689. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1'},
  1690. 'options': {}}
  1691. self._test_get_ovn_dhcp_options_helper(subnet, network,
  1692. expected_dhcp_options)
  1693. def test_get_ovn_dhcp_options_no_gw_ip(self):
  1694. subnet = {'id': 'foo-subnet', 'network_id': 'network-id',
  1695. 'cidr': '10.0.0.0/24',
  1696. 'ip_version': 4,
  1697. 'enable_dhcp': True,
  1698. 'gateway_ip': None,
  1699. 'dns_nameservers': ['7.7.7.7', '8.8.8.8'],
  1700. 'host_routes': [{'destination': '20.0.0.4',
  1701. 'nexthop': '10.0.0.100'}]}
  1702. network = {'id': 'network-id', 'mtu': 1400}
  1703. expected_dhcp_options = {'cidr': '10.0.0.0/24',
  1704. 'external_ids': {
  1705. 'subnet_id': 'foo-subnet',
  1706. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1'},
  1707. 'options': {}}
  1708. self._test_get_ovn_dhcp_options_helper(subnet, network,
  1709. expected_dhcp_options)
  1710. def test_get_ovn_dhcp_options_no_gw_ip_but_metadata_ip(self):
  1711. subnet = {'id': 'foo-subnet', 'network_id': 'network-id',
  1712. 'cidr': '10.0.0.0/24',
  1713. 'ip_version': 4,
  1714. 'enable_dhcp': True,
  1715. 'dns_nameservers': [],
  1716. 'host_routes': [],
  1717. 'gateway_ip': None}
  1718. network = {'id': 'network-id', 'mtu': 1400}
  1719. expected_dhcp_options = {
  1720. 'cidr': '10.0.0.0/24',
  1721. 'external_ids': {'subnet_id': 'foo-subnet',
  1722. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1'},
  1723. 'options': {'server_id': '10.0.0.2',
  1724. 'server_mac': '01:02:03:04:05:06',
  1725. 'dns_server': '{8.8.8.8}',
  1726. 'lease_time': str(12 * 60 * 60),
  1727. 'mtu': '1400',
  1728. 'classless_static_route':
  1729. '{169.254.169.254/32,10.0.0.2}'}}
  1730. with mock.patch.object(self.mech_driver._ovn_client,
  1731. '_find_metadata_port_ip',
  1732. return_value='10.0.0.2'):
  1733. self._test_get_ovn_dhcp_options_helper(subnet, network,
  1734. expected_dhcp_options)
  1735. def test_get_ovn_dhcp_options_with_global_options(self):
  1736. ovn_config.cfg.CONF.set_override('ovn_dhcp4_global_options',
  1737. 'ntp_server:8.8.8.8,'
  1738. 'mtu:9000,'
  1739. 'wpad:',
  1740. group='ovn')
  1741. subnet = {'id': 'foo-subnet', 'network_id': 'network-id',
  1742. 'cidr': '10.0.0.0/24',
  1743. 'ip_version': 4,
  1744. 'enable_dhcp': True,
  1745. 'gateway_ip': '10.0.0.1',
  1746. 'dns_nameservers': ['7.7.7.7', '8.8.8.8'],
  1747. 'host_routes': [{'destination': '20.0.0.4',
  1748. 'nexthop': '10.0.0.100'}]}
  1749. network = {'id': 'network-id', 'mtu': 1400}
  1750. expected_dhcp_options = {'cidr': '10.0.0.0/24',
  1751. 'external_ids': {
  1752. 'subnet_id': 'foo-subnet',
  1753. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1'}}
  1754. expected_dhcp_options['options'] = {
  1755. 'server_id': subnet['gateway_ip'],
  1756. 'server_mac': '01:02:03:04:05:06',
  1757. 'lease_time': str(12 * 60 * 60),
  1758. 'mtu': '1400',
  1759. 'router': subnet['gateway_ip'],
  1760. 'ntp_server': '8.8.8.8',
  1761. 'dns_server': '{7.7.7.7, 8.8.8.8}',
  1762. 'classless_static_route':
  1763. '{20.0.0.4,10.0.0.100, 0.0.0.0/0,10.0.0.1}'
  1764. }
  1765. self._test_get_ovn_dhcp_options_helper(subnet, network,
  1766. expected_dhcp_options)
  1767. expected_dhcp_options['options']['server_mac'] = '11:22:33:44:55:66'
  1768. self._test_get_ovn_dhcp_options_helper(subnet, network,
  1769. expected_dhcp_options,
  1770. service_mac='11:22:33:44:55:66')
  1771. def test_get_ovn_dhcp_options_with_global_options_ipv6(self):
  1772. ovn_config.cfg.CONF.set_override('ovn_dhcp6_global_options',
  1773. 'ntp_server:8.8.8.8,'
  1774. 'server_id:01:02:03:04:05:04,'
  1775. 'wpad:',
  1776. group='ovn')
  1777. subnet = {'id': 'foo-subnet', 'network_id': 'network-id',
  1778. 'cidr': 'ae70::/24',
  1779. 'ip_version': 6,
  1780. 'enable_dhcp': True,
  1781. 'dns_nameservers': ['7.7.7.7', '8.8.8.8']}
  1782. network = {'id': 'network-id', 'mtu': 1400}
  1783. ext_ids = {'subnet_id': 'foo-subnet',
  1784. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1'}
  1785. expected_dhcp_options = {
  1786. 'cidr': 'ae70::/24', 'external_ids': ext_ids,
  1787. 'options': {'server_id': '01:02:03:04:05:06',
  1788. 'ntp_server': '8.8.8.8',
  1789. 'dns_server': '{7.7.7.7, 8.8.8.8}'}}
  1790. self._test_get_ovn_dhcp_options_helper(subnet, network,
  1791. expected_dhcp_options)
  1792. expected_dhcp_options['options']['server_id'] = '11:22:33:44:55:66'
  1793. self._test_get_ovn_dhcp_options_helper(subnet, network,
  1794. expected_dhcp_options,
  1795. service_mac='11:22:33:44:55:66')
  1796. def test_get_ovn_dhcp_options_ipv6_subnet(self):
  1797. subnet = {'id': 'foo-subnet', 'network_id': 'network-id',
  1798. 'cidr': 'ae70::/24',
  1799. 'ip_version': 6,
  1800. 'enable_dhcp': True,
  1801. 'dns_nameservers': ['7.7.7.7', '8.8.8.8']}
  1802. network = {'id': 'network-id', 'mtu': 1400}
  1803. ext_ids = {'subnet_id': 'foo-subnet',
  1804. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1'}
  1805. expected_dhcp_options = {
  1806. 'cidr': 'ae70::/24', 'external_ids': ext_ids,
  1807. 'options': {'server_id': '01:02:03:04:05:06',
  1808. 'dns_server': '{7.7.7.7, 8.8.8.8}'}}
  1809. self._test_get_ovn_dhcp_options_helper(subnet, network,
  1810. expected_dhcp_options)
  1811. expected_dhcp_options['options']['server_id'] = '11:22:33:44:55:66'
  1812. self._test_get_ovn_dhcp_options_helper(subnet, network,
  1813. expected_dhcp_options,
  1814. service_mac='11:22:33:44:55:66')
  1815. def test_get_ovn_dhcp_options_dhcpv6_stateless_subnet(self):
  1816. subnet = {'id': 'foo-subnet', 'network_id': 'network-id',
  1817. 'cidr': 'ae70::/24',
  1818. 'ip_version': 6,
  1819. 'enable_dhcp': True,
  1820. 'dns_nameservers': ['7.7.7.7', '8.8.8.8'],
  1821. 'ipv6_address_mode': const.DHCPV6_STATELESS}
  1822. network = {'id': 'network-id', 'mtu': 1400}
  1823. ext_ids = {'subnet_id': 'foo-subnet',
  1824. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1'}
  1825. expected_dhcp_options = {
  1826. 'cidr': 'ae70::/24', 'external_ids': ext_ids,
  1827. 'options': {'server_id': '01:02:03:04:05:06',
  1828. 'dns_server': '{7.7.7.7, 8.8.8.8}',
  1829. 'dhcpv6_stateless': 'true'}}
  1830. self._test_get_ovn_dhcp_options_helper(subnet, network,
  1831. expected_dhcp_options)
  1832. expected_dhcp_options['options']['server_id'] = '11:22:33:44:55:66'
  1833. self._test_get_ovn_dhcp_options_helper(subnet, network,
  1834. expected_dhcp_options,
  1835. service_mac='11:22:33:44:55:66')
  1836. def test_get_ovn_dhcp_options_metadata_route(self):
  1837. subnet = {'id': 'foo-subnet', 'network_id': 'network-id',
  1838. 'cidr': '10.0.0.0/24',
  1839. 'ip_version': 4,
  1840. 'enable_dhcp': True,
  1841. 'gateway_ip': '10.0.0.1',
  1842. 'dns_nameservers': ['7.7.7.7', '8.8.8.8'],
  1843. 'host_routes': []}
  1844. network = {'id': 'network-id', 'mtu': 1400}
  1845. expected_dhcp_options = {'cidr': '10.0.0.0/24',
  1846. 'external_ids': {
  1847. 'subnet_id': 'foo-subnet',
  1848. ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1'}}
  1849. expected_dhcp_options['options'] = {
  1850. 'server_id': subnet['gateway_ip'],
  1851. 'server_mac': '01:02:03:04:05:06',
  1852. 'lease_time': str(12 * 60 * 60),
  1853. 'mtu': '1400',
  1854. 'router': subnet['gateway_ip'],
  1855. 'dns_server': '{7.7.7.7, 8.8.8.8}',
  1856. 'classless_static_route':
  1857. '{169.254.169.254/32,10.0.0.2, 0.0.0.0/0,10.0.0.1}'
  1858. }
  1859. with mock.patch.object(self.mech_driver._ovn_client,
  1860. '_find_metadata_port_ip',
  1861. return_value='10.0.0.2'):
  1862. self._test_get_ovn_dhcp_options_helper(subnet, network,
  1863. expected_dhcp_options)
  1864. def _test__get_port_dhcp_options_port_dhcp_opts_set(self, ip_version=4):
  1865. if ip_version == 4:
  1866. ip_address = '10.0.0.11'
  1867. else:
  1868. ip_address = 'aef0::4'
  1869. port = {
  1870. 'id': 'foo-port',
  1871. 'device_owner': 'compute:None',
  1872. 'fixed_ips': [{'subnet_id': 'foo-subnet',
  1873. 'ip_address': ip_address}]}
  1874. if ip_version == 4:
  1875. port['extra_dhcp_opts'] = [
  1876. {'ip_version': 4, 'opt_name': 'mtu', 'opt_value': '1200'},
  1877. {'ip_version': 4, 'opt_name': 'ntp-server',
  1878. 'opt_value': '8.8.8.8'}]
  1879. else:
  1880. port['extra_dhcp_opts'] = [
  1881. {'ip_version': 6, 'opt_name': 'domain-search',
  1882. 'opt_value': 'foo-domain'},
  1883. {'ip_version': 4, 'opt_name': 'dns-server',
  1884. 'opt_value': '7.7.7.7'}]
  1885. self.mech_driver._ovn_client._get_subnet_dhcp_options_for_port = (
  1886. mock.Mock(
  1887. return_value=({
  1888. 'cidr': '10.0.0.0/24' if ip_version == 4 else 'aef0::/64',
  1889. 'external_ids': {'subnet_id': 'foo-subnet'},
  1890. 'options': (ip_version == 4) and {
  1891. 'router': '10.0.0.1', 'mtu': '1400'} or {
  1892. 'server_id': '01:02:03:04:05:06'},
  1893. 'uuid': 'foo-uuid'})))
  1894. if ip_version == 4:
  1895. expected_dhcp_options = {
  1896. 'cidr': '10.0.0.0/24',
  1897. 'external_ids': {'subnet_id': 'foo-subnet',
  1898. 'port_id': 'foo-port'},
  1899. 'options': {'router': '10.0.0.1', 'mtu': '1200',
  1900. 'ntp_server': '8.8.8.8'}}
  1901. else:
  1902. expected_dhcp_options = {
  1903. 'cidr': 'aef0::/64',
  1904. 'external_ids': {'subnet_id': 'foo-subnet',
  1905. 'port_id': 'foo-port'},
  1906. 'options': {'server_id': '01:02:03:04:05:06',
  1907. 'domain_search': 'foo-domain'}}
  1908. self.mech_driver._nb_ovn.add_dhcp_options.return_value = 'foo-val'
  1909. dhcp_options = self.mech_driver._ovn_client._get_port_dhcp_options(
  1910. port, ip_version)
  1911. self.assertEqual({'cmd': 'foo-val'}, dhcp_options)
  1912. self.mech_driver._nb_ovn.add_dhcp_options.assert_called_once_with(
  1913. 'foo-subnet', port_id='foo-port', **expected_dhcp_options)
  1914. def test__get_port_dhcp_options_port_dhcp_opts_set_v4(self):
  1915. self._test__get_port_dhcp_options_port_dhcp_opts_set(ip_version=4)
  1916. def test__get_port_dhcp_options_port_dhcp_opts_set_v6(self):
  1917. self._test__get_port_dhcp_options_port_dhcp_opts_set(ip_version=6)
  1918. def _test__get_port_dhcp_options_port_dhcp_opts_not_set(
  1919. self, ip_version=4):
  1920. if ip_version == 4:
  1921. port = {'id': 'foo-port',
  1922. 'device_owner': 'compute:None',
  1923. 'fixed_ips': [{'subnet_id': 'foo-subnet',
  1924. 'ip_address': '10.0.0.11'}]}
  1925. else:
  1926. port = {'id': 'foo-port',
  1927. 'device_owner': 'compute:None',
  1928. 'fixed_ips': [{'subnet_id': 'foo-subnet',
  1929. 'ip_address': 'aef0::4'}]}
  1930. if ip_version == 4:
  1931. expected_dhcp_opts = {
  1932. 'cidr': '10.0.0.0/24',
  1933. 'external_ids': {'subnet_id': 'foo-subnet'},
  1934. 'options': {'router': '10.0.0.1', 'mtu': '1400'}}
  1935. else:
  1936. expected_dhcp_opts = {
  1937. 'cidr': 'aef0::/64',
  1938. 'external_ids': {'subnet_id': 'foo-subnet'},
  1939. 'options': {'server_id': '01:02:03:04:05:06'}}
  1940. self.mech_driver._ovn_client._get_subnet_dhcp_options_for_port = (
  1941. mock.Mock(return_value=expected_dhcp_opts))
  1942. self.assertEqual(
  1943. expected_dhcp_opts,
  1944. self.mech_driver._ovn_client._get_port_dhcp_options(
  1945. port, ip_version=ip_version))
  1946. # Since the port has no extra DHCPv4/v6 options defined, no new
  1947. # DHCP_Options row should be created and logical switch port DHCPv4/v6
  1948. # options should point to the subnet DHCPv4/v6 options.
  1949. self.mech_driver._nb_ovn.add_dhcp_options.assert_not_called()
  1950. def test__get_port_dhcp_options_port_dhcp_opts_not_set_v4(self):
  1951. self._test__get_port_dhcp_options_port_dhcp_opts_not_set(ip_version=4)
  1952. def test__get_port_dhcp_options_port_dhcp_opts_not_set_v6(self):
  1953. self._test__get_port_dhcp_options_port_dhcp_opts_not_set(ip_version=6)
  1954. def _test__get_port_dhcp_options_port_dhcp_disabled(self, ip_version=4):
  1955. port = {
  1956. 'id': 'foo-port',
  1957. 'device_owner': 'compute:None',
  1958. 'fixed_ips': [{'subnet_id': 'foo-subnet',
  1959. 'ip_address': '10.0.0.11'},
  1960. {'subnet_id': 'foo-subnet-v6',
  1961. 'ip_address': 'aef0::11'}],
  1962. 'extra_dhcp_opts': [{'ip_version': 4, 'opt_name': 'dhcp_disabled',
  1963. 'opt_value': 'False'},
  1964. {'ip_version': 6, 'opt_name': 'dhcp_disabled',
  1965. 'opt_value': 'False'}]
  1966. }
  1967. subnet_dhcp_opts = mock.Mock()
  1968. self.mech_driver._ovn_client._get_subnet_dhcp_options_for_port = (
  1969. mock.Mock(return_value=subnet_dhcp_opts))
  1970. # No dhcp_disabled set to true, subnet dhcp options will be get for
  1971. # this port. Since it doesn't have any other extra dhcp options, but
  1972. # dhcp_disabled, no port dhcp options will be created.
  1973. self.assertEqual(
  1974. subnet_dhcp_opts,
  1975. self.mech_driver._ovn_client._get_port_dhcp_options(
  1976. port, ip_version))
  1977. self.assertEqual(
  1978. 1,
  1979. self.mech_driver._ovn_client._get_subnet_dhcp_options_for_port.
  1980. call_count)
  1981. self.mech_driver._nb_ovn.add_dhcp_options.assert_not_called()
  1982. # Set dhcp_disabled with ip_version specified by this test case to
  1983. # true, no dhcp options will be get since it's dhcp_disabled now for
  1984. # ip_version be tested.
  1985. opt_index = 0 if ip_version == 4 else 1
  1986. port['extra_dhcp_opts'][opt_index]['opt_value'] = 'True'
  1987. self.mech_driver._ovn_client._get_subnet_dhcp_options_for_port.\
  1988. reset_mock()
  1989. self.assertIsNone(
  1990. self.mech_driver._ovn_client._get_port_dhcp_options(
  1991. port, ip_version))
  1992. self.assertEqual(
  1993. 0,
  1994. self.mech_driver._ovn_client._get_subnet_dhcp_options_for_port.
  1995. call_count)
  1996. self.mech_driver._nb_ovn.add_dhcp_options.assert_not_called()
  1997. # Set dhcp_disabled with ip_version specified by this test case to
  1998. # false, and set dhcp_disabled with ip_version not in test to true.
  1999. # Subnet dhcp options will be get, since dhcp_disabled with ip_version
  2000. # not in test should not affect.
  2001. opt_index_1 = 1 if ip_version == 4 else 0
  2002. port['extra_dhcp_opts'][opt_index]['opt_value'] = 'False'
  2003. port['extra_dhcp_opts'][opt_index_1]['opt_value'] = 'True'
  2004. self.assertEqual(
  2005. subnet_dhcp_opts,
  2006. self.mech_driver._ovn_client._get_port_dhcp_options(
  2007. port, ip_version))
  2008. self.assertEqual(
  2009. 1,
  2010. self.mech_driver._ovn_client._get_subnet_dhcp_options_for_port.
  2011. call_count)
  2012. self.mech_driver._nb_ovn.add_dhcp_options.assert_not_called()
  2013. def test__get_port_dhcp_options_port_dhcp_disabled_v4(self):
  2014. self._test__get_port_dhcp_options_port_dhcp_disabled(ip_version=4)
  2015. def test__get_port_dhcp_options_port_dhcp_disabled_v6(self):
  2016. self._test__get_port_dhcp_options_port_dhcp_disabled(ip_version=6)
  2017. def test__get_port_dhcp_options_port_with_invalid_device_owner(self):
  2018. port = {
  2019. 'id': 'foo-port',
  2020. 'device_owner': 'neutron:router_interface',
  2021. 'fixed_ips': ['fake']
  2022. }
  2023. self.assertIsNone(
  2024. self.mech_driver._ovn_client._get_port_dhcp_options(
  2025. port, mock.ANY))
  2026. def _test__get_subnet_dhcp_options_for_port(self, ip_version=4,
  2027. enable_dhcp=True):
  2028. port = {'fixed_ips': [
  2029. {'ip_address': '10.0.0.4',
  2030. 'subnet_id': 'v4_snet_id_1' if enable_dhcp else 'v4_snet_id_2'},
  2031. {'ip_address': '2001:dba::4',
  2032. 'subnet_id': 'v6_snet_id_1' if enable_dhcp else 'v6_snet_id_2'},
  2033. {'ip_address': '2001:dbb::4', 'subnet_id': 'v6_snet_id_3'}]}
  2034. def fake(subnets):
  2035. fake_rows = {
  2036. 'v4_snet_id_1': 'foo',
  2037. 'v6_snet_id_1': {'options': {}},
  2038. 'v6_snet_id_3': {'options': {
  2039. ovn_const.DHCPV6_STATELESS_OPT: 'true'}}}
  2040. return [fake_rows[row] for row in fake_rows if row in subnets]
  2041. self.mech_driver._nb_ovn.get_subnets_dhcp_options.side_effect = fake
  2042. if ip_version == 4:
  2043. expected_opts = 'foo' if enable_dhcp else None
  2044. else:
  2045. expected_opts = {
  2046. 'options': {} if enable_dhcp else {
  2047. ovn_const.DHCPV6_STATELESS_OPT: 'true'}}
  2048. self.assertEqual(
  2049. expected_opts,
  2050. self.mech_driver._ovn_client._get_subnet_dhcp_options_for_port(
  2051. port, ip_version))
  2052. def test__get_subnet_dhcp_options_for_port_v4(self):
  2053. self._test__get_subnet_dhcp_options_for_port()
  2054. def test__get_subnet_dhcp_options_for_port_v4_dhcp_disabled(self):
  2055. self._test__get_subnet_dhcp_options_for_port(enable_dhcp=False)
  2056. def test__get_subnet_dhcp_options_for_port_v6(self):
  2057. self._test__get_subnet_dhcp_options_for_port(ip_version=6)
  2058. def test__get_subnet_dhcp_options_for_port_v6_dhcp_disabled(self):
  2059. self._test__get_subnet_dhcp_options_for_port(ip_version=6,
  2060. enable_dhcp=False)
  2061. class TestOVNMechanismDriverSecurityGroup(
  2062. test_security_group.Ml2SecurityGroupsTestCase):
  2063. # This set of test cases is supplement to test_acl.py, the purpose is to
  2064. # test acl methods invoking. Content correctness of args of acl methods
  2065. # is mainly guaranteed by acl_test.py.
  2066. def setUp(self):
  2067. cfg.CONF.set_override('mechanism_drivers',
  2068. ['logger', 'ovn'],
  2069. 'ml2')
  2070. cfg.CONF.set_override('dns_servers', ['8.8.8.8'], group='ovn')
  2071. super(TestOVNMechanismDriverSecurityGroup, self).setUp()
  2072. mm = directory.get_plugin().mechanism_manager
  2073. self.mech_driver = mm.mech_drivers['ovn'].obj
  2074. nb_ovn = fakes.FakeOvsdbNbOvnIdl()
  2075. sb_ovn = fakes.FakeOvsdbSbOvnIdl()
  2076. self.mech_driver._nb_ovn = nb_ovn
  2077. self.mech_driver._sb_ovn = sb_ovn
  2078. self.ctx = context.get_admin_context()
  2079. revision_plugin.RevisionPlugin()
  2080. def _delete_default_sg_rules(self, security_group_id):
  2081. res = self._list(
  2082. 'security-group-rules',
  2083. query_params='security_group_id=%s' % security_group_id)
  2084. for r in res['security_group_rules']:
  2085. self._delete('security-group-rules', r['id'])
  2086. def _create_sg(self, sg_name):
  2087. sg = self._make_security_group(self.fmt, sg_name, '')
  2088. return sg['security_group']
  2089. def _create_empty_sg(self, sg_name):
  2090. sg = self._create_sg(sg_name)
  2091. self._delete_default_sg_rules(sg['id'])
  2092. return sg
  2093. def _create_sg_rule(self, sg_id, direction, proto,
  2094. port_range_min=None, port_range_max=None,
  2095. remote_ip_prefix=None, remote_group_id=None,
  2096. ethertype=const.IPv4):
  2097. r = self._build_security_group_rule(sg_id, direction, proto,
  2098. port_range_min=port_range_min,
  2099. port_range_max=port_range_max,
  2100. remote_ip_prefix=remote_ip_prefix,
  2101. remote_group_id=remote_group_id,
  2102. ethertype=ethertype)
  2103. res = self._create_security_group_rule(self.fmt, r)
  2104. rule = self.deserialize(self.fmt, res)
  2105. return rule['security_group_rule']
  2106. def _delete_sg_rule(self, rule_id):
  2107. self._delete('security-group-rules', rule_id)
  2108. def test_create_security_group_with_port_group(self):
  2109. self.mech_driver._nb_ovn.is_port_groups_supported.return_value = True
  2110. sg = self._create_sg('sg')
  2111. expected_pg_name = ovn_utils.ovn_port_group_name(sg['id'])
  2112. expected_pg_add_calls = [
  2113. mock.call(acls=[],
  2114. external_ids={'neutron:security_group_id': sg['id']},
  2115. name=expected_pg_name),
  2116. ]
  2117. self.mech_driver._nb_ovn.pg_add.assert_has_calls(
  2118. expected_pg_add_calls)
  2119. def test_delete_security_group_with_port_group(self):
  2120. self.mech_driver._nb_ovn.is_port_groups_supported.return_value = True
  2121. sg = self._create_sg('sg')
  2122. self._delete('security-groups', sg['id'])
  2123. expected_pg_name = ovn_utils.ovn_port_group_name(sg['id'])
  2124. expected_pg_del_calls = [
  2125. mock.call(name=expected_pg_name),
  2126. ]
  2127. self.mech_driver._nb_ovn.pg_del.assert_has_calls(
  2128. expected_pg_del_calls)
  2129. def test_create_port_with_port_group(self):
  2130. self.mech_driver._nb_ovn.is_port_groups_supported.return_value = True
  2131. with self.network() as n, self.subnet(n):
  2132. sg = self._create_empty_sg('sg')
  2133. self._make_port(self.fmt, n['network']['id'],
  2134. security_groups=[sg['id']])
  2135. # Assert the port has been added to the right security groups
  2136. expected_pg_name = ovn_utils.ovn_port_group_name(sg['id'])
  2137. expected_pg_add_ports_calls = [
  2138. mock.call('neutron_pg_drop', mock.ANY),
  2139. mock.call(expected_pg_name, mock.ANY)
  2140. ]
  2141. self.mech_driver._nb_ovn.pg_add_ports.assert_has_calls(
  2142. expected_pg_add_ports_calls)
  2143. # Assert add_acl() is not used anymore
  2144. self.assertFalse(self.mech_driver._nb_ovn.add_acl.called)
  2145. def test_create_port_with_sg_default_rules(self):
  2146. with self.network() as n, self.subnet(n):
  2147. sg = self._create_sg('sg')
  2148. self._make_port(self.fmt, n['network']['id'],
  2149. security_groups=[sg['id']])
  2150. # One DHCP rule, one IPv6 rule, one IPv4 rule and
  2151. # two default dropping rules.
  2152. self.assertEqual(
  2153. 5, self.mech_driver._nb_ovn.add_acl.call_count)
  2154. def test_create_port_with_empty_sg(self):
  2155. with self.network() as n, self.subnet(n):
  2156. sg = self._create_empty_sg('sg')
  2157. self._make_port(self.fmt, n['network']['id'],
  2158. security_groups=[sg['id']])
  2159. # One DHCP rule and two default dropping rules.
  2160. self.assertEqual(
  2161. 3, self.mech_driver._nb_ovn.add_acl.call_count)
  2162. def test_create_port_with_multi_sgs(self):
  2163. with self.network() as n, self.subnet(n):
  2164. sg1 = self._create_empty_sg('sg1')
  2165. sg2 = self._create_empty_sg('sg2')
  2166. self._create_sg_rule(sg1['id'], 'ingress', const.PROTO_NAME_TCP,
  2167. port_range_min=22, port_range_max=23)
  2168. self._create_sg_rule(sg2['id'], 'egress', const.PROTO_NAME_UDP,
  2169. remote_ip_prefix='0.0.0.0/0')
  2170. self._make_port(self.fmt, n['network']['id'],
  2171. security_groups=[sg1['id'], sg2['id']])
  2172. # One DHCP rule, one TCP rule, one UDP rule and
  2173. # two default dropping rules.
  2174. self.assertEqual(
  2175. 5, self.mech_driver._nb_ovn.add_acl.call_count)
  2176. def test_create_port_with_multi_sgs_duplicate_rules(self):
  2177. with self.network() as n, self.subnet(n):
  2178. sg1 = self._create_empty_sg('sg1')
  2179. sg2 = self._create_empty_sg('sg2')
  2180. self._create_sg_rule(sg1['id'], 'ingress', const.PROTO_NAME_TCP,
  2181. port_range_min=22, port_range_max=23,
  2182. remote_ip_prefix='20.0.0.0/24')
  2183. self._create_sg_rule(sg2['id'], 'ingress', const.PROTO_NAME_TCP,
  2184. port_range_min=22, port_range_max=23,
  2185. remote_ip_prefix='20.0.0.0/24')
  2186. self._make_port(self.fmt, n['network']['id'],
  2187. security_groups=[sg1['id'], sg2['id']])
  2188. # One DHCP rule, two TCP rule and two default dropping rules.
  2189. self.assertEqual(
  2190. 5, self.mech_driver._nb_ovn.add_acl.call_count)
  2191. def test_update_port_with_sgs(self):
  2192. with self.network() as n, self.subnet(n):
  2193. sg1 = self._create_empty_sg('sg1')
  2194. self._create_sg_rule(sg1['id'], 'ingress', const.PROTO_NAME_TCP,
  2195. ethertype=const.IPv6)
  2196. p = self._make_port(self.fmt, n['network']['id'],
  2197. security_groups=[sg1['id']])['port']
  2198. # One DHCP rule, one TCP rule and two default dropping rules.
  2199. self.assertEqual(
  2200. 4, self.mech_driver._nb_ovn.add_acl.call_count)
  2201. sg2 = self._create_empty_sg('sg2')
  2202. self._create_sg_rule(sg2['id'], 'egress', const.PROTO_NAME_UDP,
  2203. remote_ip_prefix='30.0.0.0/24')
  2204. data = {'port': {'security_groups': [sg1['id'], sg2['id']]}}
  2205. req = self.new_update_request('ports', data, p['id'])
  2206. req.get_response(self.api)
  2207. self.assertEqual(
  2208. 1, self.mech_driver._nb_ovn.update_acls.call_count)
  2209. def test_update_sg_change_rule(self):
  2210. with self.network() as n, self.subnet(n):
  2211. sg = self._create_empty_sg('sg')
  2212. self._make_port(self.fmt, n['network']['id'],
  2213. security_groups=[sg['id']])
  2214. # One DHCP rule and two default dropping rules.
  2215. self.assertEqual(
  2216. 3, self.mech_driver._nb_ovn.add_acl.call_count)
  2217. sg_r = self._create_sg_rule(sg['id'], 'ingress',
  2218. const.PROTO_NAME_UDP,
  2219. ethertype=const.IPv6)
  2220. self.assertEqual(
  2221. 1, self.mech_driver._nb_ovn.update_acls.call_count)
  2222. self._delete_sg_rule(sg_r['id'])
  2223. self.assertEqual(
  2224. 2, self.mech_driver._nb_ovn.update_acls.call_count)
  2225. def test_update_sg_change_rule_unrelated_port(self):
  2226. with self.network() as n, self.subnet(n):
  2227. sg1 = self._create_empty_sg('sg1')
  2228. sg2 = self._create_empty_sg('sg2')
  2229. self._create_sg_rule(sg1['id'], 'ingress', const.PROTO_NAME_TCP,
  2230. remote_group_id=sg2['id'])
  2231. self._make_port(self.fmt, n['network']['id'],
  2232. security_groups=[sg1['id']])
  2233. # One DHCP rule, one TCP rule and two default dropping rules.
  2234. self.assertEqual(
  2235. 4, self.mech_driver._nb_ovn.add_acl.call_count)
  2236. sg2_r = self._create_sg_rule(sg2['id'], 'egress',
  2237. const.PROTO_NAME_UDP)
  2238. self.mech_driver._nb_ovn.update_acls.assert_not_called()
  2239. self._delete_sg_rule(sg2_r['id'])
  2240. self.mech_driver._nb_ovn.update_acls.assert_not_called()
  2241. def test_update_sg_duplicate_rule(self):
  2242. with self.network() as n, self.subnet(n):
  2243. sg1 = self._create_empty_sg('sg1')
  2244. sg2 = self._create_empty_sg('sg2')
  2245. self._create_sg_rule(sg1['id'], 'ingress',
  2246. const.PROTO_NAME_UDP,
  2247. port_range_min=22, port_range_max=23)
  2248. self._make_port(self.fmt, n['network']['id'],
  2249. security_groups=[sg1['id'], sg2['id']])
  2250. # One DHCP rule, one UDP rule and two default dropping rules.
  2251. self.assertEqual(
  2252. 4, self.mech_driver._nb_ovn.add_acl.call_count)
  2253. # Add a new duplicate rule to sg2. It's expected to be added.
  2254. sg2_r = self._create_sg_rule(sg2['id'], 'ingress',
  2255. const.PROTO_NAME_UDP,
  2256. port_range_min=22, port_range_max=23)
  2257. self.mech_driver._nb_ovn.update_acls.assert_called_once()
  2258. # Delete the duplicate rule. It's expected to be deleted.
  2259. self._delete_sg_rule(sg2_r['id'])
  2260. self.assertEqual(
  2261. 2, self.mech_driver._nb_ovn.update_acls.call_count)
  2262. def test_update_sg_duplicate_rule_multi_ports(self):
  2263. with self.network() as n, self.subnet(n):
  2264. sg1 = self._create_empty_sg('sg1')
  2265. sg2 = self._create_empty_sg('sg2')
  2266. sg3 = self._create_empty_sg('sg3')
  2267. self._create_sg_rule(sg1['id'], 'ingress',
  2268. const.PROTO_NAME_UDP,
  2269. remote_group_id=sg3['id'])
  2270. self._create_sg_rule(sg2['id'], 'egress', const.PROTO_NAME_TCP,
  2271. port_range_min=60, port_range_max=70)
  2272. self._make_port(self.fmt, n['network']['id'],
  2273. security_groups=[sg1['id'], sg2['id']])
  2274. self._make_port(self.fmt, n['network']['id'],
  2275. security_groups=[sg1['id'], sg2['id']])
  2276. self._make_port(self.fmt, n['network']['id'],
  2277. security_groups=[sg2['id'], sg3['id']])
  2278. # Rules include 5 + 5 + 4
  2279. self.assertEqual(
  2280. 14, self.mech_driver._nb_ovn.add_acl.call_count)
  2281. # Add a rule to sg1 duplicate with sg2. It's expected to be added.
  2282. sg1_r = self._create_sg_rule(sg1['id'], 'egress',
  2283. const.PROTO_NAME_TCP,
  2284. port_range_min=60, port_range_max=70)
  2285. self.mech_driver._nb_ovn.update_acls.assert_called_once()
  2286. # Add a rule to sg2 duplicate with sg1 but not duplicate with sg3.
  2287. # It's expected to be added as well.
  2288. sg2_r = self._create_sg_rule(sg2['id'], 'ingress',
  2289. const.PROTO_NAME_UDP,
  2290. remote_group_id=sg3['id'])
  2291. self.assertEqual(
  2292. 2, self.mech_driver._nb_ovn.update_acls.call_count)
  2293. # Delete the duplicate rule in sg1. It's expected to be deleted.
  2294. self._delete_sg_rule(sg1_r['id'])
  2295. self.assertEqual(
  2296. 3, self.mech_driver._nb_ovn.update_acls.call_count)
  2297. # Delete the duplicate rule in sg2. It's expected to be deleted.
  2298. self._delete_sg_rule(sg2_r['id'])
  2299. self.assertEqual(
  2300. 4, self.mech_driver._nb_ovn.update_acls.call_count)
  2301. class TestOVNMechanismDriverMetadataPort(test_plugin.Ml2PluginV2TestCase):
  2302. _mechanism_drivers = ['logger', 'ovn']
  2303. def setUp(self):
  2304. super(TestOVNMechanismDriverMetadataPort, self).setUp()
  2305. mm = directory.get_plugin().mechanism_manager
  2306. self.mech_driver = mm.mech_drivers['ovn'].obj
  2307. self.mech_driver._nb_ovn = fakes.FakeOvsdbNbOvnIdl()
  2308. self.mech_driver._sb_ovn = fakes.FakeOvsdbSbOvnIdl()
  2309. self.nb_ovn = self.mech_driver._nb_ovn
  2310. self.sb_ovn = self.mech_driver._sb_ovn
  2311. ovn_config.cfg.CONF.set_override('ovn_metadata_enabled',
  2312. True,
  2313. group='ovn')
  2314. p = mock.patch.object(ovn_utils, 'get_revision_number', return_value=1)
  2315. p.start()
  2316. self.addCleanup(p.stop)
  2317. def test_metadata_port_on_network_create(self):
  2318. """Check metadata port create.
  2319. Check that a localport is created when a neutron network is
  2320. created.
  2321. """
  2322. with self.network():
  2323. self.assertEqual(1, self.nb_ovn.create_lswitch_port.call_count)
  2324. args, kwargs = self.nb_ovn.create_lswitch_port.call_args
  2325. self.assertEqual('localport', kwargs['type'])
  2326. def test_metadata_port_not_created_if_exists(self):
  2327. """Check that metadata port is not created if it already exists.
  2328. In the event of a sync, it might happen that a metadata port exists
  2329. already. When we are creating the logical switch in OVN we don't want
  2330. this port to be created again.
  2331. """
  2332. with mock.patch.object(
  2333. self.mech_driver._ovn_client, '_find_metadata_port',
  2334. return_value={'port': {'id': 'metadata_port1'}}):
  2335. with self.network():
  2336. self.assertEqual(0, self.nb_ovn.create_lswitch_port.call_count)
  2337. def test_metadata_ip_on_subnet_create(self):
  2338. """Check metadata port update.
  2339. Check that the metadata port is updated with a new IP address when a
  2340. subnet is created.
  2341. """
  2342. with self.network(set_context=True, tenant_id='test') as net1:
  2343. with self.subnet(network=net1, cidr='10.0.0.0/24') as subnet1:
  2344. # Create a network:dhcp owner port just as how Neutron DHCP
  2345. # agent would do.
  2346. with self.port(subnet=subnet1,
  2347. device_owner=const.DEVICE_OWNER_DHCP,
  2348. device_id='dhcpxxxx',
  2349. set_context=True, tenant_id='test'):
  2350. with self.subnet(network=net1, cidr='20.0.0.0/24'):
  2351. self.assertEqual(
  2352. 2, self.nb_ovn.set_lswitch_port.call_count)
  2353. args, kwargs = self.nb_ovn.set_lswitch_port.call_args
  2354. self.assertEqual('localport', kwargs['type'])
  2355. self.assertEqual('10.0.0.2/24 20.0.0.2/24',
  2356. kwargs['external_ids'].get(
  2357. ovn_const.OVN_CIDRS_EXT_ID_KEY,
  2358. ''))
  2359. def test_metadata_port_on_network_delete(self):
  2360. """Check metadata port delete.
  2361. Check that the metadata port is deleted when a network is deleted.
  2362. """
  2363. net = self._make_network(self.fmt, name="net1", admin_state_up=True)
  2364. network_id = net['network']['id']
  2365. req = self.new_delete_request('networks', network_id)
  2366. res = req.get_response(self.api)
  2367. self.assertEqual(exc.HTTPNoContent.code,
  2368. res.status_int)
  2369. self.assertEqual(1, self.nb_ovn.delete_lswitch_port.call_count)