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.

test_mech_driver.py 112KB

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