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

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