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.

ovn_client.py 85KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894
  1. # Copyright 2017 Red Hat, Inc.
  2. # All Rights Reserved.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  5. # not use this file except in compliance with the License. You may obtain
  6. # a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. # License for the specific language governing permissions and limitations
  14. # under the License.
  15. import collections
  16. import copy
  17. import netaddr
  18. from neutron_lib.api.definitions import l3
  19. from neutron_lib.api.definitions import port_security as psec
  20. from neutron_lib.api.definitions import portbindings
  21. from neutron_lib.api.definitions import provider_net as pnet
  22. from neutron_lib import constants as const
  23. from neutron_lib import context as n_context
  24. from neutron_lib import exceptions as n_exc
  25. from neutron_lib.plugins import constants as plugin_constants
  26. from neutron_lib.plugins import directory
  27. from neutron_lib.plugins import utils as p_utils
  28. from neutron_lib.utils import helpers
  29. from neutron_lib.utils import net as n_net
  30. from oslo_config import cfg
  31. from oslo_log import log
  32. from oslo_utils import excutils
  33. from ovsdbapp.backend.ovs_idl import idlutils
  34. from networking_ovn.agent.metadata import agent as metadata_agent
  35. from networking_ovn.common import acl as ovn_acl
  36. from networking_ovn.common import config
  37. from networking_ovn.common import constants as ovn_const
  38. from networking_ovn.common import utils
  39. from networking_ovn.db import revision as db_rev
  40. from networking_ovn.l3 import l3_ovn_scheduler
  41. from networking_ovn.ml2 import qos_driver
  42. LOG = log.getLogger(__name__)
  43. OvnPortInfo = collections.namedtuple(
  44. 'OvnPortInfo', ['type', 'options', 'addresses', 'port_security',
  45. 'parent_name', 'tag', 'dhcpv4_options', 'dhcpv6_options',
  46. 'cidrs', 'device_owner', 'security_group_ids'])
  47. GW_INFO = collections.namedtuple('GatewayInfo', ['network_id', 'subnet_id',
  48. 'router_ip', 'gateway_ip'])
  49. class OVNClient(object):
  50. def __init__(self, nb_idl, sb_idl):
  51. self._nb_idl = nb_idl
  52. self._sb_idl = sb_idl
  53. self._plugin_property = None
  54. self._l3_plugin_property = None
  55. self._qos_driver = qos_driver.OVNQosDriver(self)
  56. self._ovn_scheduler = l3_ovn_scheduler.get_scheduler()
  57. @property
  58. def _plugin(self):
  59. if self._plugin_property is None:
  60. self._plugin_property = directory.get_plugin()
  61. return self._plugin_property
  62. @property
  63. def _l3_plugin(self):
  64. if self._l3_plugin_property is None:
  65. self._l3_plugin_property = directory.get_plugin(
  66. plugin_constants.L3)
  67. return self._l3_plugin_property
  68. def _transaction(self, commands, txn=None):
  69. """Create a new transaction or add the commands to an existing one."""
  70. if txn is None:
  71. with self._nb_idl.transaction(check_error=True) as txn:
  72. for cmd in commands:
  73. txn.add(cmd)
  74. else:
  75. for cmd in commands:
  76. txn.add(cmd)
  77. def _get_allowed_addresses_from_port(self, port):
  78. if not port.get(psec.PORTSECURITY):
  79. return [], []
  80. if utils.is_lsp_trusted(port):
  81. return [], []
  82. allowed_addresses = set()
  83. new_macs = set()
  84. addresses = port['mac_address']
  85. for ip in port.get('fixed_ips', []):
  86. addresses += ' ' + ip['ip_address']
  87. for allowed_address in port.get('allowed_address_pairs', []):
  88. # If allowed address pair has same mac as the port mac,
  89. # append the allowed ip address to the 'addresses'.
  90. # Else we will have multiple entries for the same mac in
  91. # 'Logical_Switch_Port.port_security'.
  92. if allowed_address['mac_address'] == port['mac_address']:
  93. addresses += ' ' + allowed_address['ip_address']
  94. else:
  95. allowed_addresses.add(allowed_address['mac_address'] + ' ' +
  96. allowed_address['ip_address'])
  97. new_macs.add(allowed_address['mac_address'])
  98. allowed_addresses.add(addresses)
  99. return list(allowed_addresses), list(new_macs)
  100. def _get_subnet_dhcp_options_for_port(self, port, ip_version):
  101. """Returns the subnet dhcp options for the port.
  102. Return the first found DHCP options belong for the port.
  103. """
  104. subnets = [
  105. fixed_ip['subnet_id']
  106. for fixed_ip in port['fixed_ips']
  107. if netaddr.IPAddress(fixed_ip['ip_address']).version == ip_version]
  108. get_opts = self._nb_idl.get_subnets_dhcp_options(subnets)
  109. if get_opts:
  110. if ip_version == const.IP_VERSION_6:
  111. # Always try to find a dhcpv6 stateful v6 subnet to return.
  112. # This ensures port can get one stateful v6 address when port
  113. # has multiple dhcpv6 stateful and stateless subnets.
  114. for opts in get_opts:
  115. # We are setting ovn_const.DHCPV6_STATELESS_OPT to "true"
  116. # in _get_ovn_dhcpv6_opts, so entries in DHCP_Options table
  117. # should have unicode type 'true' if they were defined as
  118. # dhcpv6 stateless.
  119. if opts['options'].get(
  120. ovn_const.DHCPV6_STATELESS_OPT) != 'true':
  121. return opts
  122. return get_opts[0]
  123. def _get_port_dhcp_options(self, port, ip_version):
  124. """Return dhcp options for port.
  125. In case the port is dhcp disabled, or IP addresses it has belong
  126. to dhcp disabled subnets, returns None.
  127. Otherwise, returns a dict:
  128. - with content from a existing DHCP_Options row for subnet, if the
  129. port has no extra dhcp options.
  130. - with only one item ('cmd', AddDHCPOptionsCommand(..)), if the port
  131. has extra dhcp options. The command should be processed in the same
  132. transaction with port creating or updating command to avoid orphan
  133. row issue happen.
  134. """
  135. lsp_dhcp_disabled, lsp_dhcp_opts = utils.get_lsp_dhcp_opts(
  136. port, ip_version)
  137. if lsp_dhcp_disabled:
  138. return
  139. subnet_dhcp_options = self._get_subnet_dhcp_options_for_port(
  140. port, ip_version)
  141. if not subnet_dhcp_options:
  142. # NOTE(lizk): It's possible for Neutron to configure a port with IP
  143. # address belongs to subnet disabled dhcp. And no DHCP_Options row
  144. # will be inserted for such a subnet. So in that case, the subnet
  145. # dhcp options here will be None.
  146. return
  147. if not lsp_dhcp_opts:
  148. return subnet_dhcp_options
  149. # This port has extra DHCP options defined, so we will create a new
  150. # row in DHCP_Options table for it.
  151. subnet_dhcp_options['options'].update(lsp_dhcp_opts)
  152. subnet_dhcp_options['external_ids'].update(
  153. {'port_id': port['id']})
  154. subnet_id = subnet_dhcp_options['external_ids']['subnet_id']
  155. add_dhcp_opts_cmd = self._nb_idl.add_dhcp_options(
  156. subnet_id, port_id=port['id'],
  157. cidr=subnet_dhcp_options['cidr'],
  158. options=subnet_dhcp_options['options'],
  159. external_ids=subnet_dhcp_options['external_ids'])
  160. return {'cmd': add_dhcp_opts_cmd}
  161. def _get_port_options(self, port, qos_options=None):
  162. binding_prof = utils.validate_and_get_data_from_binding_profile(port)
  163. if qos_options is None:
  164. qos_options = self._qos_driver.get_qos_options(port)
  165. vtep_physical_switch = binding_prof.get('vtep-physical-switch')
  166. cidrs = ''
  167. if vtep_physical_switch:
  168. vtep_logical_switch = binding_prof.get('vtep-logical-switch')
  169. port_type = 'vtep'
  170. options = {'vtep-physical-switch': vtep_physical_switch,
  171. 'vtep-logical-switch': vtep_logical_switch}
  172. addresses = ["unknown"]
  173. parent_name = []
  174. tag = []
  175. port_security = []
  176. else:
  177. options = qos_options
  178. parent_name = binding_prof.get('parent_name', [])
  179. tag = binding_prof.get('tag', [])
  180. address = port['mac_address']
  181. for ip in port.get('fixed_ips', []):
  182. address += ' ' + ip['ip_address']
  183. subnet = self._plugin.get_subnet(n_context.get_admin_context(),
  184. ip['subnet_id'])
  185. cidrs += ' {}/{}'.format(ip['ip_address'],
  186. subnet['cidr'].split('/')[1])
  187. port_security, new_macs = \
  188. self._get_allowed_addresses_from_port(port)
  189. addresses = [address]
  190. addresses.extend(new_macs)
  191. port_type = ovn_const.OVN_NEUTRON_OWNER_TO_PORT_TYPE.get(
  192. port['device_owner'], '')
  193. dhcpv4_options = self._get_port_dhcp_options(port, const.IP_VERSION_4)
  194. dhcpv6_options = self._get_port_dhcp_options(port, const.IP_VERSION_6)
  195. options.update({'requested-chassis':
  196. port.get(portbindings.HOST_ID, '')})
  197. device_owner = port.get('device_owner', '')
  198. sg_ids = ' '.join(utils.get_lsp_security_groups(port))
  199. return OvnPortInfo(port_type, options, addresses, port_security,
  200. parent_name, tag, dhcpv4_options, dhcpv6_options,
  201. cidrs.strip(), device_owner, sg_ids)
  202. def create_port(self, port):
  203. if utils.is_lsp_ignored(port):
  204. return
  205. port_info = self._get_port_options(port)
  206. external_ids = {ovn_const.OVN_PORT_NAME_EXT_ID_KEY: port['name'],
  207. ovn_const.OVN_DEVID_EXT_ID_KEY: port['device_id'],
  208. ovn_const.OVN_PROJID_EXT_ID_KEY: port['project_id'],
  209. ovn_const.OVN_CIDRS_EXT_ID_KEY: port_info.cidrs,
  210. ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY:
  211. port_info.device_owner,
  212. ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY:
  213. utils.ovn_name(port['network_id']),
  214. ovn_const.OVN_SG_IDS_EXT_ID_KEY:
  215. port_info.security_group_ids,
  216. ovn_const.OVN_REV_NUM_EXT_ID_KEY: str(
  217. utils.get_revision_number(
  218. port, ovn_const.TYPE_PORTS))}
  219. lswitch_name = utils.ovn_name(port['network_id'])
  220. admin_context = n_context.get_admin_context()
  221. sg_cache = {}
  222. subnet_cache = {}
  223. # It's possible to have a network created on one controller and then a
  224. # port created on a different controller quickly enough that the second
  225. # controller does not yet see that network in its local cache of the
  226. # OVN northbound database. Check if the logical switch is present
  227. # or not in the idl's local copy of the database before creating
  228. # the lswitch port.
  229. self._nb_idl.check_for_row_by_value_and_retry(
  230. 'Logical_Switch', 'name', lswitch_name)
  231. with self._nb_idl.transaction(check_error=True) as txn:
  232. if not port_info.dhcpv4_options:
  233. dhcpv4_options = []
  234. elif 'cmd' in port_info.dhcpv4_options:
  235. dhcpv4_options = txn.add(port_info.dhcpv4_options['cmd'])
  236. else:
  237. dhcpv4_options = [port_info.dhcpv4_options['uuid']]
  238. if not port_info.dhcpv6_options:
  239. dhcpv6_options = []
  240. elif 'cmd' in port_info.dhcpv6_options:
  241. dhcpv6_options = txn.add(port_info.dhcpv6_options['cmd'])
  242. else:
  243. dhcpv6_options = [port_info.dhcpv6_options['uuid']]
  244. # The lport_name *must* be neutron port['id']. It must match the
  245. # iface-id set in the Interfaces table of the Open_vSwitch
  246. # database which nova sets to be the port ID.
  247. port_cmd = txn.add(self._nb_idl.create_lswitch_port(
  248. lport_name=port['id'],
  249. lswitch_name=lswitch_name,
  250. addresses=port_info.addresses,
  251. external_ids=external_ids,
  252. parent_name=port_info.parent_name,
  253. tag=port_info.tag,
  254. enabled=port.get('admin_state_up'),
  255. options=port_info.options,
  256. type=port_info.type,
  257. port_security=port_info.port_security,
  258. dhcpv4_options=dhcpv4_options,
  259. dhcpv6_options=dhcpv6_options))
  260. # Handle ACL's for this port. If we're not using Port Groups
  261. # because either the schema doesn't support it or we didn't
  262. # migrate old SGs from Address Sets to Port Groups, then we
  263. # keep the old behavior. For those SGs this port belongs to
  264. # that are modelled as a Port Group, we'll use it.
  265. sg_ids = utils.get_lsp_security_groups(port)
  266. if self._nb_idl.is_port_groups_supported():
  267. # If this is not a trusted port or port security is enabled,
  268. # add it to the default drop Port Group so that all traffic
  269. # is dropped by default.
  270. if not utils.is_lsp_trusted(port) or port_info.port_security:
  271. self._add_port_to_drop_port_group(port_cmd, txn)
  272. # For SGs modelled as OVN Port Groups, just add the port to
  273. # its Port Group.
  274. for sg in sg_ids:
  275. txn.add(self._nb_idl.pg_add_ports(
  276. utils.ovn_port_group_name(sg), port_cmd))
  277. else:
  278. # SGs modelled as Address Sets:
  279. acls_new = ovn_acl.add_acls(self._plugin, admin_context,
  280. port, sg_cache, subnet_cache,
  281. self._nb_idl)
  282. for acl in acls_new:
  283. txn.add(self._nb_idl.add_acl(**acl))
  284. if port.get('fixed_ips') and sg_ids:
  285. addresses = ovn_acl.acl_port_ips(port)
  286. # NOTE(rtheis): Fail port creation if the address set
  287. # doesn't exist. This prevents ports from being created on
  288. # any security groups out-of-sync between neutron and OVN.
  289. for sg_id in sg_ids:
  290. for ip_version in addresses:
  291. if addresses[ip_version]:
  292. txn.add(self._nb_idl.update_address_set(
  293. name=utils.ovn_addrset_name(sg_id,
  294. ip_version),
  295. addrs_add=addresses[ip_version],
  296. addrs_remove=None,
  297. if_exists=False))
  298. if self.is_dns_required_for_port(port):
  299. self.add_txns_to_sync_port_dns_records(txn, port)
  300. db_rev.bump_revision(port, ovn_const.TYPE_PORTS)
  301. # TODO(lucasagomes): Remove this helper method in the Rocky release
  302. def _get_lsp_backward_compat_sgs(self, ovn_port, port_object=None,
  303. skip_trusted_port=True):
  304. if ovn_const.OVN_SG_IDS_EXT_ID_KEY in ovn_port.external_ids:
  305. return utils.get_ovn_port_security_groups(
  306. ovn_port, skip_trusted_port=skip_trusted_port)
  307. elif port_object is not None:
  308. return utils.get_lsp_security_groups(
  309. port_object, skip_trusted_port=skip_trusted_port)
  310. return []
  311. # TODO(lucasagomes): The ``port_object`` parameter was added to
  312. # keep things backward compatible. Remove it in the Rocky release.
  313. def update_port(self, port, qos_options=None, port_object=None):
  314. if utils.is_lsp_ignored(port):
  315. return
  316. port_info = self._get_port_options(port, qos_options)
  317. external_ids = {ovn_const.OVN_PORT_NAME_EXT_ID_KEY: port['name'],
  318. ovn_const.OVN_DEVID_EXT_ID_KEY: port['device_id'],
  319. ovn_const.OVN_PROJID_EXT_ID_KEY: port['project_id'],
  320. ovn_const.OVN_CIDRS_EXT_ID_KEY: port_info.cidrs,
  321. ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY:
  322. port_info.device_owner,
  323. ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY:
  324. utils.ovn_name(port['network_id']),
  325. ovn_const.OVN_SG_IDS_EXT_ID_KEY:
  326. port_info.security_group_ids,
  327. ovn_const.OVN_REV_NUM_EXT_ID_KEY: str(
  328. utils.get_revision_number(
  329. port, ovn_const.TYPE_PORTS))}
  330. admin_context = n_context.get_admin_context()
  331. sg_cache = {}
  332. subnet_cache = {}
  333. check_rev_cmd = self._nb_idl.check_revision_number(
  334. port['id'], port, ovn_const.TYPE_PORTS)
  335. with self._nb_idl.transaction(check_error=True) as txn:
  336. txn.add(check_rev_cmd)
  337. columns_dict = {}
  338. if utils.is_lsp_router_port(port):
  339. port_info.options.update(
  340. self._nb_idl.get_router_port_options(port['id']))
  341. else:
  342. columns_dict['type'] = port_info.type
  343. columns_dict['addresses'] = port_info.addresses
  344. if not port_info.dhcpv4_options:
  345. dhcpv4_options = []
  346. elif 'cmd' in port_info.dhcpv4_options:
  347. dhcpv4_options = txn.add(port_info.dhcpv4_options['cmd'])
  348. else:
  349. dhcpv4_options = [port_info.dhcpv4_options['uuid']]
  350. if not port_info.dhcpv6_options:
  351. dhcpv6_options = []
  352. elif 'cmd' in port_info.dhcpv6_options:
  353. dhcpv6_options = txn.add(port_info.dhcpv6_options['cmd'])
  354. else:
  355. dhcpv6_options = [port_info.dhcpv6_options['uuid']]
  356. # NOTE(lizk): Fail port updating if port doesn't exist. This
  357. # prevents any new inserted resources to be orphan, such as port
  358. # dhcp options or ACL rules for port, e.g. a port was created
  359. # without extra dhcp options and security group, while updating
  360. # includes the new attributes setting to port.
  361. txn.add(self._nb_idl.set_lswitch_port(
  362. lport_name=port['id'],
  363. external_ids=external_ids,
  364. parent_name=port_info.parent_name,
  365. tag=port_info.tag,
  366. options=port_info.options,
  367. enabled=port['admin_state_up'],
  368. port_security=port_info.port_security,
  369. dhcpv4_options=dhcpv4_options,
  370. dhcpv6_options=dhcpv6_options,
  371. if_exists=False,
  372. **columns_dict))
  373. ovn_port = self._nb_idl.lookup('Logical_Switch_Port', port['id'])
  374. # Determine if security groups or fixed IPs are updated.
  375. old_sg_ids = set(self._get_lsp_backward_compat_sgs(
  376. ovn_port, port_object=port_object))
  377. new_sg_ids = set(utils.get_lsp_security_groups(port))
  378. detached_sg_ids = old_sg_ids - new_sg_ids
  379. attached_sg_ids = new_sg_ids - old_sg_ids
  380. old_fixed_ips = utils.remove_macs_from_lsp_addresses(
  381. ovn_port.addresses)
  382. new_fixed_ips = [x['ip_address'] for x in
  383. port.get('fixed_ips', [])]
  384. old_allowed_address_pairs = (
  385. utils.get_allowed_address_pairs_ip_addresses_from_ovn_port(
  386. ovn_port))
  387. new_allowed_address_pairs = (
  388. utils.get_allowed_address_pairs_ip_addresses(port))
  389. is_fixed_ips_updated = (
  390. sorted(old_fixed_ips) != sorted(new_fixed_ips))
  391. is_allowed_ips_updated = (sorted(old_allowed_address_pairs) !=
  392. sorted(new_allowed_address_pairs))
  393. port_security_changed = utils.is_port_security_enabled(port) != (
  394. bool(ovn_port.port_security))
  395. if self._nb_idl.is_port_groups_supported():
  396. for sg in detached_sg_ids:
  397. txn.add(self._nb_idl.pg_del_ports(
  398. utils.ovn_port_group_name(sg), port['id']))
  399. for sg in attached_sg_ids:
  400. txn.add(self._nb_idl.pg_add_ports(
  401. utils.ovn_port_group_name(sg), port['id']))
  402. if (not utils.is_lsp_trusted(port) and
  403. utils.is_port_security_enabled(port)):
  404. self._add_port_to_drop_port_group(port['id'], txn)
  405. # If the port doesn't belong to any security group and
  406. # port_security is disabled, or it's a trusted port, then
  407. # allow all traffic.
  408. elif ((not new_sg_ids and
  409. not utils.is_port_security_enabled(port)) or
  410. utils.is_lsp_trusted(port)):
  411. self._del_port_from_drop_port_group(port['id'], txn)
  412. else:
  413. # Refresh ACLs for changed security groups or fixed IPs.
  414. if (detached_sg_ids or attached_sg_ids or
  415. is_fixed_ips_updated or port_security_changed):
  416. # Note that update_acls will compare the port's ACLs to
  417. # ensure only the necessary ACLs are added and deleted
  418. # on the transaction.
  419. acls_new = ovn_acl.add_acls(self._plugin,
  420. admin_context,
  421. port,
  422. sg_cache,
  423. subnet_cache,
  424. self._nb_idl)
  425. txn.add(self._nb_idl.update_acls([port['network_id']],
  426. [port],
  427. {port['id']: acls_new},
  428. need_compare=True))
  429. # Refresh address sets for changed security groups or fixed
  430. # IPs.
  431. if len(old_fixed_ips) != 0 or len(new_fixed_ips) != 0:
  432. addresses = ovn_acl.acl_port_ips(port)
  433. addresses_old = utils.sort_ips_by_version(
  434. utils.get_ovn_port_addresses(ovn_port))
  435. # Add current addresses to attached security groups.
  436. for sg_id in attached_sg_ids:
  437. for ip_version in addresses:
  438. if addresses[ip_version]:
  439. txn.add(self._nb_idl.update_address_set(
  440. name=utils.ovn_addrset_name(sg_id,
  441. ip_version),
  442. addrs_add=addresses[ip_version],
  443. addrs_remove=None))
  444. # Remove old addresses from detached security groups.
  445. for sg_id in detached_sg_ids:
  446. for ip_version in addresses_old:
  447. if addresses_old[ip_version]:
  448. txn.add(self._nb_idl.update_address_set(
  449. name=utils.ovn_addrset_name(sg_id,
  450. ip_version),
  451. addrs_add=None,
  452. addrs_remove=addresses_old[ip_version]))
  453. if is_fixed_ips_updated or is_allowed_ips_updated:
  454. # We have refreshed address sets for attached and
  455. # detached security groups, so now we only need to take
  456. # care of unchanged security groups.
  457. unchanged_sg_ids = new_sg_ids & old_sg_ids
  458. for sg_id in unchanged_sg_ids:
  459. for ip_version in addresses:
  460. addr_add = ((set(addresses[ip_version]) -
  461. set(addresses_old[ip_version])) or
  462. None)
  463. addr_remove = (
  464. (set(addresses_old[ip_version]) -
  465. set(addresses[ip_version])) or None)
  466. if addr_add or addr_remove:
  467. txn.add(self._nb_idl.update_address_set(
  468. name=utils.ovn_addrset_name(
  469. sg_id, ip_version),
  470. addrs_add=addr_add,
  471. addrs_remove=addr_remove))
  472. if self.is_dns_required_for_port(port):
  473. self.add_txns_to_sync_port_dns_records(
  474. txn, port, original_port=port_object)
  475. elif port_object and self.is_dns_required_for_port(port_object):
  476. # We need to remove the old entries
  477. self.add_txns_to_remove_port_dns_records(txn, port_object)
  478. if check_rev_cmd.result == ovn_const.TXN_COMMITTED:
  479. db_rev.bump_revision(port, ovn_const.TYPE_PORTS)
  480. def _delete_port(self, port_id, port_object=None):
  481. ovn_port = self._nb_idl.lookup('Logical_Switch_Port', port_id)
  482. network_id = ovn_port.external_ids.get(
  483. ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY)
  484. # TODO(lucasagomes): For backward compatibility, if network_id
  485. # is not in the OVNDB, look at the port_object
  486. if not network_id and port_object:
  487. network_id = port_object['network_id']
  488. with self._nb_idl.transaction(check_error=True) as txn:
  489. txn.add(self._nb_idl.delete_lswitch_port(
  490. port_id, network_id))
  491. if not self._nb_idl.is_port_groups_supported():
  492. txn.add(self._nb_idl.delete_acl(network_id, port_id))
  493. addresses = utils.sort_ips_by_version(
  494. utils.get_ovn_port_addresses(ovn_port))
  495. sec_groups = self._get_lsp_backward_compat_sgs(
  496. ovn_port, port_object=port_object, skip_trusted_port=False)
  497. for sg_id in sec_groups:
  498. for ip_version, addr_list in addresses.items():
  499. if not addr_list:
  500. continue
  501. txn.add(self._nb_idl.update_address_set(
  502. name=utils.ovn_addrset_name(sg_id, ip_version),
  503. addrs_add=None,
  504. addrs_remove=addr_list))
  505. if port_object and self.is_dns_required_for_port(port_object):
  506. self.add_txns_to_remove_port_dns_records(txn, port_object)
  507. # TODO(lucasagomes): The ``port_object`` parameter was added to
  508. # keep things backward compatible. Remove it in the Rocky release.
  509. def delete_port(self, port_id, port_object=None):
  510. try:
  511. self._delete_port(port_id, port_object=port_object)
  512. except idlutils.RowNotFound:
  513. pass
  514. db_rev.delete_revision(port_id, ovn_const.TYPE_PORTS)
  515. def _create_or_update_floatingip(self, floatingip, txn=None):
  516. router_id = floatingip.get('router_id')
  517. if not router_id:
  518. return
  519. commands = []
  520. context = n_context.get_admin_context()
  521. fip_db = self._l3_plugin._get_floatingip(context, floatingip['id'])
  522. func = self._nb_idl.add_nat_rule_in_lrouter
  523. gw_lrouter_name = utils.ovn_name(router_id)
  524. nat_rule_args = (gw_lrouter_name,)
  525. # TODO(chandrav): Since the floating ip port is not
  526. # bound to any chassis, packets destined to floating ip
  527. # will be dropped. To overcome this, delete the floating
  528. # ip port. Proper fix for this would be to redirect packets
  529. # destined to floating ip to the router port. This would
  530. # require changes in ovn-northd.
  531. commands.append(self._nb_idl.delete_lswitch_port(
  532. fip_db['floating_port_id'],
  533. utils.ovn_name(floatingip['floating_network_id'])))
  534. # Get the list of nat rules and check if the external_ip
  535. # with type 'dnat_and_snat' already exists or not.
  536. # If exists, set the new value.
  537. # This happens when the port associated to a floating ip
  538. # is deleted before the disassociation.
  539. lrouter_nat_rules = self._nb_idl.get_lrouter_nat_rules(
  540. gw_lrouter_name)
  541. for nat_rule in lrouter_nat_rules:
  542. if (nat_rule['external_ip'] ==
  543. floatingip['floating_ip_address'] and
  544. nat_rule['type'] == 'dnat_and_snat'):
  545. func = self._nb_idl.set_nat_rule_in_lrouter
  546. nat_rule_args = (gw_lrouter_name, nat_rule['uuid'])
  547. break
  548. ext_ids = {
  549. ovn_const.OVN_FIP_EXT_ID_KEY: floatingip['id'],
  550. ovn_const.OVN_REV_NUM_EXT_ID_KEY: str(utils.get_revision_number(
  551. floatingip, ovn_const.TYPE_FLOATINGIPS)),
  552. ovn_const.OVN_FIP_PORT_EXT_ID_KEY: floatingip['port_id'],
  553. ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: gw_lrouter_name}
  554. columns = {'type': 'dnat_and_snat',
  555. 'logical_ip': floatingip['fixed_ip_address'],
  556. 'external_ip': floatingip['floating_ip_address']}
  557. if config.is_ovn_distributed_floating_ip():
  558. port = self._plugin.get_port(
  559. context, fip_db['floating_port_id'])
  560. columns['logical_port'] = floatingip['port_id']
  561. ext_ids[ovn_const.OVN_FIP_EXT_MAC_KEY] = port['mac_address']
  562. if self._nb_idl.lsp_get_up(floatingip['port_id']).execute():
  563. columns['external_mac'] = port['mac_address']
  564. # TODO(dalvarez): remove this check once the minimum OVS required
  565. # version contains the column (when OVS 2.8.2 is released).
  566. if self._nb_idl.is_col_present('NAT', 'external_ids'):
  567. columns['external_ids'] = ext_ids
  568. commands.append(func(*nat_rule_args, **columns))
  569. self._transaction(commands, txn=txn)
  570. def _delete_floatingip(self, fip, lrouter, txn=None):
  571. commands = [self._nb_idl.delete_nat_rule_in_lrouter(
  572. lrouter, type='dnat_and_snat',
  573. logical_ip=fip['logical_ip'],
  574. external_ip=fip['external_ip'])]
  575. self._transaction(commands, txn=txn)
  576. def update_floatingip_status(self, floatingip):
  577. # NOTE(lucasagomes): OVN doesn't care about the floating ip
  578. # status, this method just bumps the revision number
  579. check_rev_cmd = self._nb_idl.check_revision_number(
  580. floatingip['id'], floatingip, ovn_const.TYPE_FLOATINGIPS)
  581. with self._nb_idl.transaction(check_error=True) as txn:
  582. txn.add(check_rev_cmd)
  583. if check_rev_cmd.result == ovn_const.TXN_COMMITTED:
  584. db_rev.bump_revision(floatingip, ovn_const.TYPE_FLOATINGIPS)
  585. def create_floatingip(self, floatingip):
  586. try:
  587. self._create_or_update_floatingip(floatingip)
  588. except Exception as e:
  589. with excutils.save_and_reraise_exception():
  590. LOG.error('Unable to create floating ip in gateway '
  591. 'router. Error: %s', e)
  592. db_rev.bump_revision(floatingip, ovn_const.TYPE_FLOATINGIPS)
  593. # NOTE(lucasagomes): Revise the expected status
  594. # of floating ips, setting it to ACTIVE here doesn't
  595. # see consistent with other drivers (ODL here), see:
  596. # https://bugs.launchpad.net/networking-ovn/+bug/1657693
  597. if floatingip.get('router_id'):
  598. self._l3_plugin.update_floatingip_status(
  599. n_context.get_admin_context(), floatingip['id'],
  600. const.FLOATINGIP_STATUS_ACTIVE)
  601. # TODO(lucasagomes): The ``fip_object`` parameter was added to
  602. # keep things backward compatible since old FIPs might not have
  603. # the OVN_FIP_EXT_ID_KEY in their external_ids field. Remove it
  604. # in the Rocky release.
  605. def update_floatingip(self, floatingip, fip_object=None):
  606. fip_status = None
  607. router_id = None
  608. ovn_fip = self._nb_idl.get_floatingip(floatingip['id'])
  609. if not ovn_fip and fip_object:
  610. router_id = fip_object.get('router_id')
  611. ovn_fip = self._nb_idl.get_floatingip_by_ips(
  612. router_id, fip_object['fixed_ip_address'],
  613. fip_object['floating_ip_address'])
  614. check_rev_cmd = self._nb_idl.check_revision_number(
  615. floatingip['id'], floatingip, ovn_const.TYPE_FLOATINGIPS)
  616. with self._nb_idl.transaction(check_error=True) as txn:
  617. txn.add(check_rev_cmd)
  618. if (ovn_fip and
  619. (floatingip['fixed_ip_address'] != ovn_fip['logical_ip'] or
  620. floatingip['port_id'] != ovn_fip['external_ids'].get(
  621. ovn_const.OVN_FIP_PORT_EXT_ID_KEY))):
  622. lrouter = ovn_fip['external_ids'].get(
  623. ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY,
  624. utils.ovn_name(router_id))
  625. self._delete_floatingip(ovn_fip, lrouter, txn=txn)
  626. fip_status = const.FLOATINGIP_STATUS_DOWN
  627. if floatingip.get('port_id'):
  628. self._create_or_update_floatingip(floatingip, txn=txn)
  629. fip_status = const.FLOATINGIP_STATUS_ACTIVE
  630. if check_rev_cmd.result == ovn_const.TXN_COMMITTED:
  631. db_rev.bump_revision(floatingip, ovn_const.TYPE_FLOATINGIPS)
  632. if fip_status:
  633. self._l3_plugin.update_floatingip_status(
  634. n_context.get_admin_context(), floatingip['id'], fip_status)
  635. # TODO(lucasagomes): The ``fip_object`` parameter was added to
  636. # keep things backward compatible since old FIPs might not have
  637. # the OVN_FIP_EXT_ID_KEY in their external_ids field. Remove it
  638. # in the Rocky release.
  639. def delete_floatingip(self, fip_id, fip_object=None):
  640. router_id = None
  641. ovn_fip = self._nb_idl.get_floatingip(fip_id)
  642. if not ovn_fip and fip_object:
  643. router_id = fip_object.get('router_id')
  644. ovn_fip = self._nb_idl.get_floatingip_by_ips(
  645. router_id, fip_object['fixed_ip_address'],
  646. fip_object['floating_ip_address'])
  647. if ovn_fip:
  648. lrouter = ovn_fip['external_ids'].get(
  649. ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY,
  650. utils.ovn_name(router_id))
  651. try:
  652. self._delete_floatingip(ovn_fip, lrouter)
  653. except Exception as e:
  654. with excutils.save_and_reraise_exception():
  655. LOG.error('Unable to delete floating ip in gateway '
  656. 'router. Error: %s', e)
  657. db_rev.delete_revision(fip_id, ovn_const.TYPE_FLOATINGIPS)
  658. def disassociate_floatingip(self, floatingip, router_id):
  659. lrouter = utils.ovn_name(router_id)
  660. try:
  661. self._delete_floatingip(floatingip, lrouter)
  662. except Exception as e:
  663. with excutils.save_and_reraise_exception():
  664. LOG.error('Unable to disassociate floating ip in gateway '
  665. 'router. Error: %s', e)
  666. def _get_gw_info(self, context, router):
  667. ext_gw_info = router.get(l3.EXTERNAL_GW_INFO, {})
  668. network_id = ext_gw_info.get('network_id', '')
  669. for ext_fixed_ip in ext_gw_info.get('external_fixed_ips', []):
  670. subnet_id = ext_fixed_ip['subnet_id']
  671. subnet = self._plugin.get_subnet(context, subnet_id)
  672. if subnet['ip_version'] == 4:
  673. return GW_INFO(
  674. network_id, subnet_id, ext_fixed_ip['ip_address'],
  675. subnet.get('gateway_ip'))
  676. return GW_INFO('', '', '', '')
  677. def _delete_router_ext_gw(self, context, router, networks, txn):
  678. if not networks:
  679. networks = []
  680. router_id = router['id']
  681. gw_port_id = router['gw_port_id']
  682. gw_lrouter_name = utils.ovn_name(router_id)
  683. gw_info = self._get_gw_info(context, router)
  684. txn.add(self._nb_idl.delete_static_route(gw_lrouter_name,
  685. ip_prefix='0.0.0.0/0',
  686. nexthop=gw_info.gateway_ip))
  687. txn.add(self._nb_idl.delete_lrouter_port(
  688. utils.ovn_lrouter_port_name(gw_port_id),
  689. gw_lrouter_name))
  690. for network in networks:
  691. txn.add(self._nb_idl.delete_nat_rule_in_lrouter(
  692. gw_lrouter_name, type='snat', logical_ip=network,
  693. external_ip=gw_info.router_ip))
  694. def _get_nets_and_ipv6_ra_confs_for_router_port(
  695. self, port_fixed_ips):
  696. context = n_context.get_admin_context()
  697. networks = set()
  698. ipv6_ra_configs = {}
  699. ipv6_ra_configs_supported = self._nb_idl.is_col_present(
  700. 'Logical_Router_Port', 'ipv6_ra_configs')
  701. for fixed_ip in port_fixed_ips:
  702. subnet_id = fixed_ip['subnet_id']
  703. subnet = self._plugin.get_subnet(context, subnet_id)
  704. cidr = netaddr.IPNetwork(subnet['cidr'])
  705. networks.add("%s/%s" % (fixed_ip['ip_address'],
  706. str(cidr.prefixlen)))
  707. if subnet.get('ipv6_address_mode') and not ipv6_ra_configs and (
  708. ipv6_ra_configs_supported):
  709. ipv6_ra_configs['address_mode'] = (
  710. utils.get_ovn_ipv6_address_mode(
  711. subnet['ipv6_address_mode']))
  712. ipv6_ra_configs['send_periodic'] = 'true'
  713. net = self._plugin.get_network(context, subnet['network_id'])
  714. ipv6_ra_configs['mtu'] = str(net['mtu'])
  715. return list(networks), ipv6_ra_configs
  716. def _add_router_ext_gw(self, context, router, networks, txn):
  717. router_id = router['id']
  718. # 1. Add the external gateway router port.
  719. gw_info = self._get_gw_info(context, router)
  720. gw_port_id = router['gw_port_id']
  721. port = self._plugin.get_port(context, gw_port_id)
  722. self._create_lrouter_port(router_id, port, txn=txn)
  723. # TODO(lucasagomes): Remove this check after OVS 2.8.2 is tagged
  724. # (prior to that, the external_ids column didn't exist in this
  725. # table).
  726. columns = {}
  727. if self._nb_idl.is_col_present('Logical_Router_Static_Route',
  728. 'external_ids'):
  729. columns['external_ids'] = {
  730. ovn_const.OVN_ROUTER_IS_EXT_GW: 'true',
  731. ovn_const.OVN_SUBNET_EXT_ID_KEY: gw_info.subnet_id}
  732. # 2. Add default route with nexthop as gateway ip
  733. lrouter_name = utils.ovn_name(router_id)
  734. txn.add(self._nb_idl.add_static_route(
  735. lrouter_name, ip_prefix='0.0.0.0/0', nexthop=gw_info.gateway_ip,
  736. **columns))
  737. # 3. Add snat rules for tenant networks in lrouter if snat is enabled
  738. if utils.is_snat_enabled(router) and networks:
  739. self.update_nat_rules(router, networks, enable_snat=True, txn=txn)
  740. return port
  741. def _check_external_ips_changed(self, context, ovn_snats, ovn_static_route,
  742. router):
  743. gw_info = self._get_gw_info(context, router)
  744. if self._nb_idl.is_col_present('Logical_Router_Static_Route',
  745. 'external_ids'):
  746. ext_ids = getattr(ovn_static_route, 'external_ids', {})
  747. if (ext_ids.get(ovn_const.OVN_SUBNET_EXT_ID_KEY) !=
  748. gw_info.subnet_id):
  749. return True
  750. for snat in ovn_snats:
  751. if snat.external_ip != gw_info.router_ip:
  752. return True
  753. return False
  754. def update_router_routes(self, context, router_id, add, remove,
  755. txn=None):
  756. if not any([add, remove]):
  757. return
  758. lrouter_name = utils.ovn_name(router_id)
  759. commands = []
  760. for route in add:
  761. commands.append(
  762. self._nb_idl.add_static_route(
  763. lrouter_name, ip_prefix=route['destination'],
  764. nexthop=route['nexthop']))
  765. for route in remove:
  766. commands.append(
  767. self._nb_idl.delete_static_route(
  768. lrouter_name, ip_prefix=route['destination'],
  769. nexthop=route['nexthop']))
  770. self._transaction(commands, txn=txn)
  771. def _get_router_ports(self, context, router_id, get_gw_port=False):
  772. router_db = self._l3_plugin._get_router(context, router_id)
  773. if get_gw_port:
  774. return [p.port for p in router_db.attached_ports]
  775. else:
  776. # When the existing deployment is migrated to OVN
  777. # we may need to consider other port types - DVR_INTERFACE/HA_INTF.
  778. return [p.port for p in router_db.attached_ports
  779. if p.port_type in [const.DEVICE_OWNER_ROUTER_INTF,
  780. const.DEVICE_OWNER_DVR_INTERFACE,
  781. const.DEVICE_OWNER_HA_REPLICATED_INT,
  782. const.DEVICE_OWNER_ROUTER_HA_INTF]]
  783. def _get_v4_network_for_router_port(self, context, port):
  784. cidr = None
  785. for fixed_ip in port['fixed_ips']:
  786. subnet_id = fixed_ip['subnet_id']
  787. subnet = self._plugin.get_subnet(context, subnet_id)
  788. if subnet['ip_version'] != 4:
  789. continue
  790. cidr = subnet['cidr']
  791. return cidr
  792. def _get_v4_network_of_all_router_ports(self, context, router_id,
  793. ports=None):
  794. networks = []
  795. ports = ports or self._get_router_ports(context, router_id)
  796. for port in ports:
  797. network = self._get_v4_network_for_router_port(context, port)
  798. if network:
  799. networks.append(network)
  800. return networks
  801. def _gen_router_ext_ids(self, router):
  802. return {
  803. ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY:
  804. router.get('name', 'no_router_name'),
  805. ovn_const.OVN_GW_PORT_EXT_ID_KEY:
  806. router.get('gw_port_id') or '',
  807. ovn_const.OVN_REV_NUM_EXT_ID_KEY: str(utils.get_revision_number(
  808. router, ovn_const.TYPE_ROUTERS))}
  809. def create_router(self, router, add_external_gateway=True):
  810. """Create a logical router."""
  811. context = n_context.get_admin_context()
  812. external_ids = self._gen_router_ext_ids(router)
  813. enabled = router.get('admin_state_up')
  814. lrouter_name = utils.ovn_name(router['id'])
  815. added_gw_port = None
  816. with self._nb_idl.transaction(check_error=True) as txn:
  817. txn.add(self._nb_idl.create_lrouter(lrouter_name,
  818. external_ids=external_ids,
  819. enabled=enabled,
  820. options={}))
  821. # TODO(lucasagomes): add_external_gateway is being only used
  822. # by the ovn_db_sync.py script, remove it after the database
  823. # synchronization work
  824. if add_external_gateway:
  825. networks = self._get_v4_network_of_all_router_ports(
  826. context, router['id'])
  827. if router.get(l3.EXTERNAL_GW_INFO) and networks is not None:
  828. added_gw_port = self._add_router_ext_gw(context, router,
  829. networks, txn)
  830. if added_gw_port:
  831. db_rev.bump_revision(added_gw_port,
  832. ovn_const.TYPE_ROUTER_PORTS)
  833. db_rev.bump_revision(router, ovn_const.TYPE_ROUTERS)
  834. # TODO(lucasagomes): The ``router_object`` parameter was added to
  835. # keep things backward compatible with old routers created prior to
  836. # the database sync work. Remove it in the Rocky release.
  837. def update_router(self, new_router, router_object=None):
  838. """Update a logical router."""
  839. context = n_context.get_admin_context()
  840. router_id = new_router['id']
  841. router_name = utils.ovn_name(router_id)
  842. ovn_router = self._nb_idl.get_lrouter(router_name)
  843. gateway_new = new_router.get(l3.EXTERNAL_GW_INFO)
  844. gateway_old = utils.get_lrouter_ext_gw_static_route(ovn_router)
  845. added_gw_port = None
  846. deleted_gw_port_id = None
  847. if router_object:
  848. gateway_old = gateway_old or router_object.get(l3.EXTERNAL_GW_INFO)
  849. ovn_snats = utils.get_lrouter_snats(ovn_router)
  850. networks = self._get_v4_network_of_all_router_ports(context, router_id)
  851. try:
  852. check_rev_cmd = self._nb_idl.check_revision_number(
  853. router_name, new_router, ovn_const.TYPE_ROUTERS)
  854. with self._nb_idl.transaction(check_error=True) as txn:
  855. txn.add(check_rev_cmd)
  856. if gateway_new and not gateway_old:
  857. # Route gateway is set
  858. added_gw_port = self._add_router_ext_gw(
  859. context, new_router, networks, txn)
  860. elif gateway_old and not gateway_new:
  861. # router gateway is removed
  862. txn.add(self._nb_idl.delete_lrouter_ext_gw(router_name))
  863. if router_object:
  864. self._delete_router_ext_gw(context, router_object,
  865. networks, txn)
  866. deleted_gw_port_id = router_object['gw_port_id']
  867. elif gateway_new and gateway_old:
  868. # Check if external gateway has changed, if yes, delete
  869. # the old gateway and add the new gateway
  870. if self._check_external_ips_changed(
  871. context, ovn_snats, gateway_old, new_router):
  872. txn.add(self._nb_idl.delete_lrouter_ext_gw(
  873. router_name))
  874. if router_object:
  875. self._delete_router_ext_gw(context, router_object,
  876. networks, txn)
  877. deleted_gw_port_id = router_object['gw_port_id']
  878. added_gw_port = self._add_router_ext_gw(
  879. context, new_router, networks, txn)
  880. else:
  881. # Check if snat has been enabled/disabled and update
  882. new_snat_state = gateway_new.get('enable_snat', True)
  883. if bool(ovn_snats) != new_snat_state:
  884. if utils.is_snat_enabled(new_router) and networks:
  885. self.update_nat_rules(
  886. new_router, networks,
  887. enable_snat=new_snat_state, txn=txn)
  888. update = {'external_ids': self._gen_router_ext_ids(new_router)}
  889. update['enabled'] = new_router.get('admin_state_up') or False
  890. txn.add(self._nb_idl.update_lrouter(router_name, **update))
  891. # Check for route updates
  892. routes = new_router.get('routes')
  893. if routes:
  894. old_routes = utils.get_lrouter_non_gw_routes(ovn_router)
  895. added, removed = helpers.diff_list_of_dict(
  896. old_routes, routes)
  897. self.update_router_routes(
  898. context, router_id, added, removed, txn=txn)
  899. if check_rev_cmd.result == ovn_const.TXN_COMMITTED:
  900. db_rev.bump_revision(new_router, ovn_const.TYPE_ROUTERS)
  901. if added_gw_port:
  902. db_rev.bump_revision(added_gw_port,
  903. ovn_const.TYPE_ROUTER_PORTS)
  904. if deleted_gw_port_id:
  905. db_rev.delete_revision(deleted_gw_port_id,
  906. ovn_const.TYPE_ROUTER_PORTS)
  907. except Exception as e:
  908. with excutils.save_and_reraise_exception():
  909. LOG.error('Unable to update router %(router)s. '
  910. 'Error: %(error)s', {'router': router_id,
  911. 'error': e})
  912. def delete_router(self, router_id):
  913. """Delete a logical router."""
  914. lrouter_name = utils.ovn_name(router_id)
  915. with self._nb_idl.transaction(check_error=True) as txn:
  916. txn.add(self._nb_idl.delete_lrouter(lrouter_name))
  917. db_rev.delete_revision(router_id, ovn_const.TYPE_ROUTERS)
  918. def get_candidates_for_scheduling(self, physnet, cms=None,
  919. chassis_physnets=None):
  920. """Return chassis for scheduling gateway router.
  921. Criteria for selecting chassis as candidates
  922. 1) chassis from cms with proper bridge mappings
  923. 2) if no chassis is available from 1) then,
  924. select chassis with proper bridge mappings
  925. """
  926. cms = cms or self._sb_idl.get_gateway_chassis_from_cms_options()
  927. chassis_physnets = (chassis_physnets or
  928. self._sb_idl.get_chassis_and_physnets())
  929. cms_bmaps = []
  930. bmaps = []
  931. for chassis, physnets in chassis_physnets.items():
  932. if physnet and physnet in physnets:
  933. if chassis in cms:
  934. cms_bmaps.append(chassis)
  935. else:
  936. bmaps.append(chassis)
  937. candidates = cms_bmaps or bmaps
  938. if not cms_bmaps:
  939. LOG.debug("No eligible chassis with external connectivity"
  940. " through ovn-cms-options.")
  941. LOG.debug("Chassis candidates with external connectivity: %s",
  942. candidates)
  943. return candidates
  944. def _get_physnet(self, net_id):
  945. extnet = self._plugin.get_network(n_context.get_admin_context(),
  946. net_id)
  947. if extnet.get(pnet.NETWORK_TYPE) in [const.TYPE_FLAT,
  948. const.TYPE_VLAN]:
  949. return extnet.get(pnet.PHYSICAL_NETWORK)
  950. def _gen_router_port_ext_ids(self, port):
  951. ext_ids = {
  952. ovn_const.OVN_REV_NUM_EXT_ID_KEY: str(utils.get_revision_number(
  953. port, ovn_const.TYPE_ROUTER_PORTS)),
  954. ovn_const.OVN_SUBNET_EXT_IDS_KEY:
  955. ' '.join(utils.get_port_subnet_ids(port))}
  956. router_id = port.get('device_id')
  957. if router_id:
  958. ext_ids[ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY] = router_id
  959. return ext_ids
  960. def _create_lrouter_port(self, router_id, port, txn=None):
  961. """Create a logical router port."""
  962. lrouter = utils.ovn_name(router_id)
  963. networks, ipv6_ra_configs = (
  964. self._get_nets_and_ipv6_ra_confs_for_router_port(
  965. port['fixed_ips']))
  966. lrouter_port_name = utils.ovn_lrouter_port_name(port['id'])
  967. is_gw_port = const.DEVICE_OWNER_ROUTER_GW == port.get(
  968. 'device_owner')
  969. columns = {}
  970. if is_gw_port:
  971. physnet = self._get_physnet(port['network_id'])
  972. candidates = self.get_candidates_for_scheduling(physnet)
  973. selected_chassis = self._ovn_scheduler.select(
  974. self._nb_idl, self._sb_idl, lrouter_port_name,
  975. candidates=candidates)
  976. if selected_chassis:
  977. columns['gateway_chassis'] = selected_chassis
  978. lsp_address = ovn_const.DEFAULT_ADDR_FOR_LSP_WITH_PEER
  979. if ipv6_ra_configs:
  980. columns['ipv6_ra_configs'] = ipv6_ra_configs
  981. commands = [
  982. self._nb_idl.add_lrouter_port(
  983. name=lrouter_port_name,
  984. lrouter=lrouter,
  985. mac=port['mac_address'],
  986. networks=networks,
  987. may_exist=True,
  988. external_ids=self._gen_router_port_ext_ids(port),
  989. **columns),
  990. self._nb_idl.set_lrouter_port_in_lswitch_port(
  991. port['id'], lrouter_port_name, is_gw_port=is_gw_port,
  992. lsp_address=lsp_address)]
  993. self._transaction(commands, txn=txn)
  994. def create_router_port(self, router_id, router_interface):
  995. context = n_context.get_admin_context()
  996. port = self._plugin.get_port(context, router_interface['port_id'])
  997. with self._nb_idl.transaction(check_error=True) as txn:
  998. multi_prefix = False
  999. if (len(router_interface.get('subnet_ids', [])) == 1 and
  1000. len(port['fixed_ips']) > 1):
  1001. # NOTE(lizk) It's adding a subnet onto an already
  1002. # existing router interface port, try to update lrouter port
  1003. # 'networks' column.
  1004. self._update_lrouter_port(port, txn=txn)
  1005. multi_prefix = True
  1006. else:
  1007. self._create_lrouter_port(router_id, port, txn=txn)
  1008. router = self._l3_plugin.get_router(context, router_id)
  1009. if router.get(l3.EXTERNAL_GW_INFO):
  1010. cidr = None
  1011. for fixed_ip in port['fixed_ips']:
  1012. subnet = self._plugin.get_subnet(context,
  1013. fixed_ip['subnet_id'])
  1014. if multi_prefix:
  1015. if 'subnet_id' in router_interface:
  1016. if subnet['id'] != router_interface['subnet_id']:
  1017. continue
  1018. if subnet['ip_version'] == 4:
  1019. cidr = subnet['cidr']
  1020. if utils.is_snat_enabled(router) and cidr:
  1021. self.update_nat_rules(router, networks=[cidr],
  1022. enable_snat=True, txn=txn)
  1023. db_rev.bump_revision(port, ovn_const.TYPE_ROUTER_PORTS)
  1024. def _update_lrouter_port(self, port, if_exists=False, txn=None):
  1025. """Update a logical router port."""
  1026. networks, ipv6_ra_configs = (
  1027. self._get_nets_and_ipv6_ra_confs_for_router_port(
  1028. port['fixed_ips']))
  1029. lsp_address = ovn_const.DEFAULT_ADDR_FOR_LSP_WITH_PEER
  1030. lrp_name = utils.ovn_lrouter_port_name(port['id'])
  1031. update = {'networks': networks, 'ipv6_ra_configs': ipv6_ra_configs}
  1032. is_gw_port = const.DEVICE_OWNER_ROUTER_GW == port.get(
  1033. 'device_owner')
  1034. commands = [
  1035. self._nb_idl.update_lrouter_port(
  1036. name=lrp_name,
  1037. external_ids=self._gen_router_port_ext_ids(port),
  1038. if_exists=if_exists,
  1039. **update),
  1040. self._nb_idl.set_lrouter_port_in_lswitch_port(
  1041. port['id'], lrp_name, is_gw_port=is_gw_port,
  1042. lsp_address=lsp_address)]
  1043. self._transaction(commands, txn=txn)
  1044. def update_router_port(self, port, if_exists=False):
  1045. lrp_name = utils.ovn_lrouter_port_name(port['id'])
  1046. check_rev_cmd = self._nb_idl.check_revision_number(
  1047. lrp_name, port, ovn_const.TYPE_ROUTER_PORTS)
  1048. with self._nb_idl.transaction(check_error=True) as txn:
  1049. txn.add(check_rev_cmd)
  1050. self._update_lrouter_port(port, if_exists=if_exists, txn=txn)
  1051. if check_rev_cmd.result == ovn_const.TXN_COMMITTED:
  1052. db_rev.bump_revision(port, ovn_const.TYPE_ROUTER_PORTS)
  1053. def _delete_lrouter_port(self, port_id, router_id=None, txn=None):
  1054. """Delete a logical router port."""
  1055. commands = [self._nb_idl.lrp_del(
  1056. utils.ovn_lrouter_port_name(port_id),
  1057. utils.ovn_name(router_id) if router_id else None,
  1058. if_exists=True)]
  1059. self._transaction(commands, txn=txn)
  1060. db_rev.delete_revision(port_id, ovn_const.TYPE_ROUTER_PORTS)
  1061. def delete_router_port(self, port_id, router_id=None, subnet_ids=None):
  1062. try:
  1063. ovn_port = self._nb_idl.lookup(
  1064. 'Logical_Router_Port', utils.ovn_lrouter_port_name(port_id))
  1065. except idlutils.RowNotFound:
  1066. return
  1067. subnet_ids = subnet_ids or []
  1068. context = n_context.get_admin_context()
  1069. port_removed = False
  1070. with self._nb_idl.transaction(check_error=True) as txn:
  1071. port = None
  1072. try:
  1073. port = self._plugin.get_port(context, port_id)
  1074. # The router interface port still exists, call ovn to
  1075. # update it
  1076. self._update_lrouter_port(port, txn=txn)
  1077. except n_exc.PortNotFound:
  1078. # The router interface port doesn't exist any more,
  1079. # we will call ovn to delete it once we remove the snat
  1080. # rules in the router itself if we have to
  1081. port_removed = True
  1082. router_id = router_id or ovn_port.external_ids.get(
  1083. ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY)
  1084. if not router_id:
  1085. router_id = port.get('device_id')
  1086. router = None
  1087. if router_id:
  1088. router = self._l3_plugin.get_router(context, router_id)
  1089. if not router.get(l3.EXTERNAL_GW_INFO):
  1090. if port_removed:
  1091. self._delete_lrouter_port(port_id, router_id, txn=txn)
  1092. return
  1093. if not subnet_ids:
  1094. subnet_ids = ovn_port.external_ids.get(
  1095. ovn_const.OVN_SUBNET_EXT_IDS_KEY, [])
  1096. subnet_ids = subnet_ids.split()
  1097. elif port:
  1098. subnet_ids = utils.get_port_subnet_ids(port)
  1099. cidr = None
  1100. for sid in subnet_ids:
  1101. subnet = self._plugin.get_subnet(context, sid)
  1102. if subnet['ip_version'] == 4:
  1103. cidr = subnet['cidr']
  1104. break
  1105. if router and utils.is_snat_enabled(router) and cidr:
  1106. self.update_nat_rules(
  1107. router, networks=[cidr], enable_snat=False, txn=txn)
  1108. # NOTE(mangelajo): If the port doesn't exist anymore, we
  1109. # delete the router port as the last operation and update the
  1110. # revision database to ensure consistency
  1111. if port_removed:
  1112. self._delete_lrouter_port(port_id, router_id, txn=txn)
  1113. else:
  1114. # otherwise, we just update the revision database
  1115. db_rev.bump_revision(port, ovn_const.TYPE_ROUTER_PORTS)
  1116. def update_nat_rules(self, router, networks, enable_snat, txn=None):
  1117. """Update the NAT rules in a logical router."""
  1118. context = n_context.get_admin_context()
  1119. func = (self._nb_idl.add_nat_rule_in_lrouter if enable_snat else
  1120. self._nb_idl.delete_nat_rule_in_lrouter)
  1121. gw_lrouter_name = utils.ovn_name(router['id'])
  1122. gw_info = self._get_gw_info(context, router)
  1123. commands = []
  1124. for network in networks:
  1125. commands.append(
  1126. func(gw_lrouter_name, type='snat', logical_ip=network,
  1127. external_ip=gw_info.router_ip))
  1128. self._transaction(commands, txn=txn)
  1129. def _create_provnet_port(self, txn, network, physnet, tag):
  1130. txn.add(self._nb_idl.create_lswitch_port(
  1131. lport_name=utils.ovn_provnet_port_name(network['id']),
  1132. lswitch_name=utils.ovn_name(network['id']),
  1133. addresses=['unknown'],
  1134. external_ids={},
  1135. type='localnet',
  1136. tag=tag if tag else [],
  1137. options={'network_name': physnet}))
  1138. def _gen_network_external_ids(self, network):
  1139. ext_ids = {
  1140. ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY: network['name'],
  1141. ovn_const.OVN_REV_NUM_EXT_ID_KEY: str(
  1142. utils.get_revision_number(network, ovn_const.TYPE_NETWORKS))}
  1143. # NOTE(lucasagomes): There's a difference between the
  1144. # "qos_policy_id" key existing and it being None, the latter is a
  1145. # valid value. Since we can't save None in OVSDB, we are converting
  1146. # it to "null" as a placeholder.
  1147. if 'qos_policy_id' in network:
  1148. ext_ids[ovn_const.OVN_QOS_POLICY_EXT_ID_KEY] = (
  1149. network['qos_policy_id'] or 'null')
  1150. return ext_ids
  1151. def create_network(self, network):
  1152. # Create a logical switch with a name equal to the Neutron network
  1153. # UUID. This provides an easy way to refer to the logical switch
  1154. # without having to track what UUID OVN assigned to it.
  1155. ext_ids = self._gen_network_external_ids(network)
  1156. lswitch_name = utils.ovn_name(network['id'])
  1157. with self._nb_idl.transaction(check_error=True) as txn:
  1158. txn.add(self._nb_idl.ls_add(lswitch_name, external_ids=ext_ids,
  1159. may_exist=True))
  1160. physnet = network.get(pnet.PHYSICAL_NETWORK)
  1161. if physnet:
  1162. self._create_provnet_port(txn, network, physnet,
  1163. network.get(pnet.SEGMENTATION_ID))
  1164. db_rev.bump_revision(network, ovn_const.TYPE_NETWORKS)
  1165. self.create_metadata_port(n_context.get_admin_context(), network)
  1166. return network
  1167. def delete_network(self, network_id):
  1168. with self._nb_idl.transaction(check_error=True) as txn:
  1169. ls, ls_dns_record = self._nb_idl.get_ls_and_dns_record(
  1170. utils.ovn_name(network_id))
  1171. txn.add(self._nb_idl.ls_del(utils.ovn_name(network_id),
  1172. if_exists=True))
  1173. if ls_dns_record:
  1174. txn.add(self._nb_idl.dns_del(ls_dns_record.uuid))
  1175. db_rev.delete_revision(network_id, ovn_const.TYPE_NETWORKS)
  1176. def _is_qos_update_required(self, network):
  1177. # Is qos service enabled
  1178. if 'qos_policy_id' not in network:
  1179. return False
  1180. # Check if qos service wasn't enabled before
  1181. ovn_net = self._nb_idl.get_lswitch(utils.ovn_name(network['id']))
  1182. if ovn_const.OVN_QOS_POLICY_EXT_ID_KEY not in ovn_net.external_ids:
  1183. return True
  1184. # Check if the policy_id has changed
  1185. new_qos_id = network['qos_policy_id'] or 'null'
  1186. return new_qos_id != ovn_net.external_ids[
  1187. ovn_const.OVN_QOS_POLICY_EXT_ID_KEY]
  1188. def update_network(self, network):
  1189. lswitch_name = utils.ovn_name(network['id'])
  1190. # Check if QoS needs to be update, before updating OVNDB
  1191. qos_update_required = self._is_qos_update_required(network)
  1192. check_rev_cmd = self._nb_idl.check_revision_number(
  1193. lswitch_name, network, ovn_const.TYPE_NETWORKS)
  1194. # TODO(numans) - When a network's dns domain name is updated, we need
  1195. # to update the DNS records for this network in DNS OVN NB DB table.
  1196. # (https://bugs.launchpad.net/networking-ovn/+bug/1777978)
  1197. # Eg. if the network n1's dns domain name was "test1" and if it has
  1198. # 2 bound ports - p1 and p2, we would have created the below dns
  1199. # records
  1200. # ===========================
  1201. # p1 = P1_IP
  1202. # p1.test1 = P1_IP
  1203. # p1.default_domain = P1_IP
  1204. # p2 = P2_IP
  1205. # p2.test1 = P2_IP
  1206. # p2.default_domain = P2_IP
  1207. # ===========================
  1208. # if the network n1's dns domain name is updated to test2, then we need
  1209. # to delete the below DNS records
  1210. # ===========================
  1211. # p1.test1 = P1_IP
  1212. # p2.test1 = P2_IP
  1213. # ===========================
  1214. # and add the new ones
  1215. # ===========================
  1216. # p1.test2 = P1_IP
  1217. # p2.test2 = P2_IP
  1218. # ===========================
  1219. # in the DNS row for this network.
  1220. with self._nb_idl.transaction(check_error=True) as txn:
  1221. txn.add(check_rev_cmd)
  1222. ext_ids = self._gen_network_external_ids(network)
  1223. txn.add(self._nb_idl.set_lswitch_ext_ids(lswitch_name, ext_ids))
  1224. if check_rev_cmd.result == ovn_const.TXN_COMMITTED:
  1225. if qos_update_required:
  1226. self._qos_driver.update_network(network)
  1227. db_rev.bump_revision(network, ovn_const.TYPE_NETWORKS)
  1228. def _add_subnet_dhcp_options(self, subnet, network,
  1229. ovn_dhcp_options=None):
  1230. if utils.is_dhcp_options_ignored(subnet):
  1231. return
  1232. if not ovn_dhcp_options:
  1233. ovn_dhcp_options = self._get_ovn_dhcp_options(subnet, network)
  1234. with self._nb_idl.transaction(check_error=True) as txn:
  1235. rev_num = {ovn_const.OVN_REV_NUM_EXT_ID_KEY: str(
  1236. utils.get_revision_number(subnet, ovn_const.TYPE_SUBNETS))}
  1237. ovn_dhcp_options['external_ids'].update(rev_num)
  1238. txn.add(self._nb_idl.add_dhcp_options(subnet['id'],
  1239. **ovn_dhcp_options))
  1240. def _get_ovn_dhcp_options(self, subnet, network, server_mac=None):
  1241. external_ids = {
  1242. 'subnet_id': subnet['id'],
  1243. ovn_const.OVN_REV_NUM_EXT_ID_KEY: str(utils.get_revision_number(
  1244. subnet, ovn_const.TYPE_SUBNETS))}
  1245. dhcp_options = {'cidr': subnet['cidr'], 'options': {},
  1246. 'external_ids': external_ids}
  1247. if subnet['enable_dhcp']:
  1248. if subnet['ip_version'] == const.IP_VERSION_4:
  1249. dhcp_options['options'] = self._get_ovn_dhcpv4_opts(
  1250. subnet, network, server_mac=server_mac)
  1251. else:
  1252. dhcp_options['options'] = self._get_ovn_dhcpv6_opts(
  1253. subnet, server_id=server_mac)
  1254. return dhcp_options
  1255. def _get_ovn_dhcpv4_opts(self, subnet, network, server_mac=None):
  1256. metadata_port_ip = self._find_metadata_port_ip(
  1257. n_context.get_admin_context(), subnet)
  1258. # TODO(dongj): Currently the metadata port is created only when
  1259. # ovn_metadata_enabled is true, therefore this is a restriction for
  1260. # supporting DHCP of subnet without gateway IP.
  1261. # We will remove this restriction later.
  1262. service_id = subnet['gateway_ip'] or metadata_port_ip
  1263. if not service_id:
  1264. return {}
  1265. default_lease_time = str(config.get_ovn_dhcp_default_lease_time())
  1266. mtu = network['mtu']
  1267. options = {
  1268. 'server_id': service_id,
  1269. 'lease_time': default_lease_time,
  1270. 'mtu': str(mtu),
  1271. }
  1272. if subnet['gateway_ip']:
  1273. options['router'] = subnet['gateway_ip']
  1274. if server_mac:
  1275. options['server_mac'] = server_mac
  1276. else:
  1277. options['server_mac'] = n_net.get_random_mac(
  1278. cfg.CONF.base_mac.split(':'))
  1279. dns_servers = (subnet.get('dns_nameservers') or
  1280. config.get_dns_servers() or
  1281. utils.get_system_dns_resolvers())
  1282. if dns_servers:
  1283. options['dns_server'] = '{%s}' % ', '.join(dns_servers)
  1284. else:
  1285. LOG.warning("No relevant dns_servers defined for subnet %s. Check "
  1286. "the /etc/resolv.conf file",
  1287. subnet['id'])
  1288. routes = []
  1289. if metadata_port_ip:
  1290. routes.append('%s/32,%s' % (
  1291. metadata_agent.METADATA_DEFAULT_IP, metadata_port_ip))
  1292. # Add subnet host_routes to 'classless_static_route' dhcp option
  1293. routes.extend(['%s,%s' % (route['destination'], route['nexthop'])
  1294. for route in subnet['host_routes']])
  1295. if routes:
  1296. # if there are static routes, then we need to add the
  1297. # default route in this option. As per RFC 3442 dhcp clients
  1298. # should ignore 'router' dhcp option (option 3)
  1299. # if option 121 is present.
  1300. if subnet['gateway_ip']:
  1301. routes.append('0.0.0.0/0,%s' % subnet['gateway_ip'])
  1302. options['classless_static_route'] = '{' + ', '.join(routes) + '}'
  1303. return options
  1304. def _get_ovn_dhcpv6_opts(self, subnet, server_id=None):
  1305. """Returns the DHCPv6 options"""
  1306. dhcpv6_opts = {
  1307. 'server_id': server_id or n_net.get_random_mac(
  1308. cfg.CONF.base_mac.split(':'))
  1309. }
  1310. if subnet['dns_nameservers']:
  1311. dns_servers = '{%s}' % ', '.join(subnet['dns_nameservers'])
  1312. dhcpv6_opts['dns_server'] = dns_servers
  1313. if subnet.get('ipv6_address_mode') == const.DHCPV6_STATELESS:
  1314. dhcpv6_opts[ovn_const.DHCPV6_STATELESS_OPT] = 'true'
  1315. return dhcpv6_opts
  1316. def _remove_subnet_dhcp_options(self, subnet_id, txn):
  1317. dhcp_options = self._nb_idl.get_subnet_dhcp_options(
  1318. subnet_id, with_ports=True)
  1319. if dhcp_options['subnet'] is not None:
  1320. txn.add(self._nb_idl.delete_dhcp_options(
  1321. dhcp_options['subnet']['uuid']))
  1322. # Remove subnet and port DHCP_Options rows, the DHCP options in
  1323. # lsp rows will be removed by related UUID
  1324. for opt in dhcp_options['ports']:
  1325. txn.add(self._nb_idl.delete_dhcp_options(opt['uuid']))
  1326. def _enable_subnet_dhcp_options(self, subnet, network, txn):
  1327. if utils.is_dhcp_options_ignored(subnet):
  1328. return
  1329. filters = {'fixed_ips': {'subnet_id': [subnet['id']]}}
  1330. all_ports = self._plugin.get_ports(n_context.get_admin_context(),
  1331. filters=filters)
  1332. ports = [p for p in all_ports if not utils.is_network_device_port(p)]
  1333. dhcp_options = self._get_ovn_dhcp_options(subnet, network)
  1334. subnet_dhcp_cmd = self._nb_idl.add_dhcp_options(subnet['id'],
  1335. **dhcp_options)
  1336. subnet_dhcp_option = txn.add(subnet_dhcp_cmd)
  1337. # Traverse ports to add port DHCP_Options rows
  1338. for port in ports:
  1339. lsp_dhcp_disabled, lsp_dhcp_opts = utils.get_lsp_dhcp_opts(
  1340. port, subnet['ip_version'])
  1341. if lsp_dhcp_disabled:
  1342. continue
  1343. elif not lsp_dhcp_opts:
  1344. lsp_dhcp_options = subnet_dhcp_option
  1345. else:
  1346. port_dhcp_options = copy.deepcopy(dhcp_options)
  1347. port_dhcp_options['options'].update(lsp_dhcp_opts)
  1348. port_dhcp_options['external_ids'].update(
  1349. {'port_id': port['id']})
  1350. lsp_dhcp_options = txn.add(self._nb_idl.add_dhcp_options(
  1351. subnet['id'], port_id=port['id'],
  1352. **port_dhcp_options))
  1353. columns = {'dhcpv6_options': lsp_dhcp_options} if \
  1354. subnet['ip_version'] == const.IP_VERSION_6 else {
  1355. 'dhcpv4_options': lsp_dhcp_options}
  1356. # Set lsp DHCP options
  1357. txn.add(self._nb_idl.set_lswitch_port(
  1358. lport_name=port['id'],
  1359. **columns))
  1360. def _update_subnet_dhcp_options(self, subnet, network, txn):
  1361. if utils.is_dhcp_options_ignored(subnet):
  1362. return
  1363. original_options = self._nb_idl.get_subnet_dhcp_options(
  1364. subnet['id'])['subnet']
  1365. mac = None
  1366. if original_options:
  1367. if subnet['ip_version'] == const.IP_VERSION_6:
  1368. mac = original_options['options'].get('server_id')
  1369. else:
  1370. mac = original_options['options'].get('server_mac')
  1371. new_options = self._get_ovn_dhcp_options(subnet, network, mac)
  1372. # Check whether DHCP changed
  1373. if (original_options and
  1374. original_options['cidr'] == new_options['cidr'] and
  1375. original_options['options'] == new_options['options']):
  1376. return
  1377. txn.add(self._nb_idl.add_dhcp_options(subnet['id'], **new_options))
  1378. dhcp_options = self._nb_idl.get_subnet_dhcp_options(
  1379. subnet['id'], with_ports=True)
  1380. # When a subnet dns_nameserver is updated, then we should update
  1381. # the port dhcp options for ports (with no port specific dns_server
  1382. # defined).
  1383. if 'options' in new_options and 'options' in original_options:
  1384. orig_dns_server = original_options['options'].get('dns_server')
  1385. new_dns_server = new_options['options'].get('dns_server')
  1386. dns_server_changed = (orig_dns_server != new_dns_server)
  1387. else:
  1388. dns_server_changed = False
  1389. for opt in dhcp_options['ports']:
  1390. if not new_options.get('options'):
  1391. continue
  1392. options = dict(new_options['options'])
  1393. p_dns_server = opt['options'].get('dns_server')
  1394. if dns_server_changed and (orig_dns_server == p_dns_server):
  1395. # If port has its own dns_server option defined, then
  1396. # orig_dns_server and p_dns_server will not match.
  1397. opt['options']['dns_server'] = new_dns_server
  1398. options.update(opt['options'])
  1399. port_id = opt['external_ids']['port_id']
  1400. txn.add(self._nb_idl.add_dhcp_options(
  1401. subnet['id'], port_id=port_id, options=options))
  1402. def create_subnet(self, subnet, network):
  1403. if subnet['enable_dhcp']:
  1404. if subnet['ip_version'] == 4:
  1405. context = n_context.get_admin_context()
  1406. self.update_metadata_port(context, network['id'])
  1407. self._add_subnet_dhcp_options(subnet, network)
  1408. db_rev.bump_revision(subnet, ovn_const.TYPE_SUBNETS)
  1409. def update_subnet(self, subnet, network):
  1410. ovn_subnet = self._nb_idl.get_subnet_dhcp_options(
  1411. subnet['id'])['subnet']
  1412. if subnet['enable_dhcp'] or ovn_subnet:
  1413. context = n_context.get_admin_context()
  1414. self.update_metadata_port(context, network['id'])
  1415. check_rev_cmd = self._nb_idl.check_revision_number(
  1416. subnet['id'], subnet, ovn_const.TYPE_SUBNETS)
  1417. with self._nb_idl.transaction(check_error=True) as txn:
  1418. txn.add(check_rev_cmd)
  1419. if subnet['enable_dhcp'] and not ovn_subnet:
  1420. self._enable_subnet_dhcp_options(subnet, network, txn)
  1421. elif not subnet['enable_dhcp'] and ovn_subnet:
  1422. self._remove_subnet_dhcp_options(subnet['id'], txn)
  1423. elif subnet['enable_dhcp'] and ovn_subnet:
  1424. self._update_subnet_dhcp_options(subnet, network, txn)
  1425. if check_rev_cmd.result == ovn_const.TXN_COMMITTED:
  1426. db_rev.bump_revision(subnet, ovn_const.TYPE_SUBNETS)
  1427. def delete_subnet(self, subnet_id):
  1428. with self._nb_idl.transaction(check_error=True) as txn:
  1429. self._remove_subnet_dhcp_options(subnet_id, txn)
  1430. db_rev.delete_revision(subnet_id, ovn_const.TYPE_SUBNETS)
  1431. def create_security_group(self, security_group):
  1432. # If the OVN schema supports Port Groups, we'll model security groups
  1433. # as such. Otherwise, for backwards compatibility, we'll keep creating
  1434. # two Address Sets for each Neutron SG (one for IPv4 and one for
  1435. # IPv6).
  1436. with self._nb_idl.transaction(check_error=True) as txn:
  1437. ext_ids = {ovn_const.OVN_SG_EXT_ID_KEY: security_group['id']}
  1438. if self._nb_idl.is_port_groups_supported():
  1439. name = utils.ovn_port_group_name(security_group['id'])
  1440. txn.add(self._nb_idl.pg_add(
  1441. name=name, acls=[], external_ids=ext_ids))
  1442. # When a SG is created, it comes with some default rules,
  1443. # so we'll apply them to the Port Group.
  1444. ovn_acl.add_acls_for_sg_port_group(self._nb_idl,
  1445. security_group, txn)
  1446. else:
  1447. for ip_version in ('ip4', 'ip6'):
  1448. name = utils.ovn_addrset_name(security_group['id'],
  1449. ip_version)
  1450. txn.add(self._nb_idl.create_address_set(
  1451. name=name, external_ids=ext_ids))
  1452. db_rev.bump_revision(security_group, ovn_const.TYPE_SECURITY_GROUPS)
  1453. def create_default_drop_port_group(self, ports=None):
  1454. pg_name = ovn_const.OVN_DROP_PORT_GROUP_NAME
  1455. with self._nb_idl.transaction(check_error=True) as txn:
  1456. if not self._nb_idl.get_port_group(pg_name):
  1457. # If drop Port Group doesn't exist yet, create it.
  1458. txn.add(self._nb_idl.pg_add(pg_name, acls=[]))
  1459. # Add ACLs to this Port Group so that all traffic is dropped.
  1460. acls = ovn_acl.add_acls_for_drop_port_group(pg_name)
  1461. for acl in acls:
  1462. txn.add(self._nb_idl.pg_acl_add(**acl))
  1463. if ports:
  1464. ports_ids = [port['id'] for port in ports]
  1465. # Add the ports to the default Port Group
  1466. txn.add(self._nb_idl.pg_add_ports(pg_name, ports_ids))
  1467. def _add_port_to_drop_port_group(self, port, txn):
  1468. self.create_default_drop_port_group()
  1469. txn.add(self._nb_idl.pg_add_ports(ovn_const.OVN_DROP_PORT_GROUP_NAME,
  1470. port))
  1471. def _del_port_from_drop_port_group(self, port, txn):
  1472. pg_name = ovn_const.OVN_DROP_PORT_GROUP_NAME
  1473. if self._nb_idl.get_port_group(pg_name):
  1474. txn.add(self._nb_idl.pg_del_ports(pg_name, port))
  1475. def delete_security_group(self, security_group_id):
  1476. with self._nb_idl.transaction(check_error=True) as txn:
  1477. if self._nb_idl.is_port_groups_supported():
  1478. name = utils.ovn_port_group_name(security_group_id)
  1479. txn.add(self._nb_idl.pg_del(name=name))
  1480. else:
  1481. for ip_version in ('ip4', 'ip6'):
  1482. name = utils.ovn_addrset_name(security_group_id,
  1483. ip_version)
  1484. txn.add(self._nb_idl.delete_address_set(name=name))
  1485. db_rev.delete_revision(security_group_id,
  1486. ovn_const.TYPE_SECURITY_GROUPS)
  1487. def _process_security_group_rule(self, rule, is_add_acl=True):
  1488. admin_context = n_context.get_admin_context()
  1489. ovn_acl.update_acls_for_security_group(
  1490. self._plugin, admin_context, self._nb_idl,
  1491. rule['security_group_id'], rule, is_add_acl=is_add_acl)
  1492. def create_security_group_rule(self, rule):
  1493. self._process_security_group_rule(rule)
  1494. db_rev.bump_revision(rule, ovn_const.TYPE_SECURITY_GROUP_RULES)
  1495. def delete_security_group_rule(self, rule):
  1496. self._process_security_group_rule(rule, is_add_acl=False)
  1497. db_rev.delete_revision(rule['id'], ovn_const.TYPE_SECURITY_GROUP_RULES)
  1498. def _find_metadata_port(self, context, network_id):
  1499. if not config.is_ovn_metadata_enabled():
  1500. return
  1501. ports = self._plugin.get_ports(context, filters=dict(
  1502. network_id=[network_id], device_owner=[const.DEVICE_OWNER_DHCP]))
  1503. # There should be only one metadata port per network
  1504. if len(ports) == 1:
  1505. return ports[0]
  1506. def _find_metadata_port_ip(self, context, subnet):
  1507. metadata_port = self._find_metadata_port(context, subnet['network_id'])
  1508. if metadata_port:
  1509. for fixed_ip in metadata_port['fixed_ips']:
  1510. if fixed_ip['subnet_id'] == subnet['id']:
  1511. return fixed_ip['ip_address']
  1512. def _get_metadata_ports(self, context, network_id):
  1513. if not config.is_ovn_metadata_enabled():
  1514. return
  1515. return self._plugin.get_ports(context, filters=dict(
  1516. network_id=[network_id], device_owner=[const.DEVICE_OWNER_DHCP]))
  1517. def create_metadata_port(self, context, network):
  1518. if config.is_ovn_metadata_enabled():
  1519. metadata_ports = self._get_metadata_ports(context, network['id'])
  1520. if not metadata_ports:
  1521. # Create a neutron port for DHCP/metadata services
  1522. port = {'port':
  1523. {'network_id': network['id'],
  1524. 'tenant_id': network['project_id'],
  1525. 'device_owner': const.DEVICE_OWNER_DHCP}}
  1526. # TODO(boden): rehome create_port into neutron-lib
  1527. p_utils.create_port(self._plugin, context, port)
  1528. elif len(metadata_ports) > 1:
  1529. LOG.error("More than one metadata ports found for network %s. "
  1530. "Please run the neutron-ovn-db-sync-util to fix it.",
  1531. network['id'])
  1532. def update_metadata_port(self, context, network_id):
  1533. """Update metadata port.
  1534. This function will allocate an IP address for the metadata port of
  1535. the given network in all its IPv4 subnets.
  1536. """
  1537. if not config.is_ovn_metadata_enabled():
  1538. return
  1539. # Retrieve the metadata port of this network
  1540. metadata_port = self._find_metadata_port(context, network_id)
  1541. if not metadata_port:
  1542. LOG.error("Metadata port couldn't be found for network %s",
  1543. network_id)
  1544. return
  1545. # Retrieve all subnets in this network
  1546. subnets = self._plugin.get_subnets(context, filters=dict(
  1547. network_id=[network_id], ip_version=[4]))
  1548. subnet_ids = set(s['id'] for s in subnets)
  1549. port_subnet_ids = set(ip['subnet_id'] for ip in
  1550. metadata_port['fixed_ips'])
  1551. # Find all subnets where metadata port doesn't have an IP in and
  1552. # allocate one.
  1553. if subnet_ids != port_subnet_ids:
  1554. wanted_fixed_ips = []
  1555. for fixed_ip in metadata_port['fixed_ips']:
  1556. wanted_fixed_ips.append(
  1557. {'subnet_id': fixed_ip['subnet_id'],
  1558. 'ip_address': fixed_ip['ip_address']})
  1559. wanted_fixed_ips.extend(
  1560. dict(subnet_id=s)
  1561. for s in subnet_ids - port_subnet_ids)
  1562. port = {'id': metadata_port['id'],
  1563. 'port': {'network_id': network_id,
  1564. 'fixed_ips': wanted_fixed_ips}}
  1565. self._plugin.update_port(n_context.get_admin_context(),
  1566. metadata_port['id'], port)
  1567. def get_parent_port(self, port_id):
  1568. return self._nb_idl.get_parent_port(port_id)
  1569. def is_dns_required_for_port(self, port):
  1570. try:
  1571. if not all([port['dns_name'], port['dns_assignment'],
  1572. port['device_id']]):
  1573. return False
  1574. except KeyError:
  1575. # Possible that dns extension is not enabled.
  1576. return False
  1577. if not self._nb_idl.is_table_present('DNS'):
  1578. return False
  1579. return True
  1580. def get_port_dns_records(self, port):
  1581. port_dns_records = {}
  1582. net = port.get('network', {})
  1583. net_dns_domain = net.get('dns_domain', '').rstrip('.')
  1584. for dns_assignment in port.get('dns_assignment', []):
  1585. hostname = dns_assignment['hostname']
  1586. fqdn = dns_assignment['fqdn'].rstrip('.')
  1587. net_dns_fqdn = hostname + '.' + net_dns_domain
  1588. if hostname not in port_dns_records:
  1589. port_dns_records[hostname] = dns_assignment['ip_address']
  1590. if net_dns_domain and net_dns_fqdn != fqdn:
  1591. port_dns_records[net_dns_fqdn] = (
  1592. dns_assignment['ip_address'])
  1593. else:
  1594. port_dns_records[hostname] += " " + (
  1595. dns_assignment['ip_address'])
  1596. if net_dns_domain and net_dns_fqdn != fqdn:
  1597. port_dns_records[hostname + '.' + net_dns_domain] += (
  1598. " " + dns_assignment['ip_address'])
  1599. if fqdn not in port_dns_records:
  1600. port_dns_records[fqdn] = dns_assignment['ip_address']
  1601. else:
  1602. port_dns_records[fqdn] += " " + dns_assignment['ip_address']
  1603. return port_dns_records
  1604. def add_txns_to_sync_port_dns_records(self, txn, port, original_port=None):
  1605. # NOTE(numans): - This implementation has certain known limitations
  1606. # and that will be addressed in the future patches
  1607. # https://bugs.launchpad.net/networking-ovn/+bug/1739257.
  1608. # Please see the bug report for more information, but just to sum up
  1609. # here
  1610. # - We will have issues if two ports have same dns name
  1611. # - If a port is deleted with dns name 'd1' and a new port is
  1612. # added with the same dns name 'd1'.
  1613. records_to_add = self.get_port_dns_records(port)
  1614. lswitch_name = utils.ovn_name(port['network_id'])
  1615. ls, ls_dns_record = self._nb_idl.get_ls_and_dns_record(lswitch_name)
  1616. # If ls_dns_record is None, then we need to create a DNS row for the
  1617. # logical switch.
  1618. if ls_dns_record is None:
  1619. dns_add_txn = txn.add(self._nb_idl.dns_add(
  1620. external_ids={'ls_name': ls.name}, records=records_to_add))
  1621. txn.add(self._nb_idl.ls_set_dns_records(ls.uuid, dns_add_txn))
  1622. return
  1623. if original_port:
  1624. old_records = self.get_port_dns_records(original_port)
  1625. for old_hostname, old_ips in old_records.items():
  1626. if records_to_add.get(old_hostname) != old_ips:
  1627. txn.add(self._nb_idl.dns_remove_record(
  1628. ls_dns_record.uuid, old_hostname))
  1629. for hostname, ips in records_to_add.items():
  1630. if ls_dns_record.records.get(hostname) != ips:
  1631. txn.add(self._nb_idl.dns_add_record(
  1632. ls_dns_record.uuid, hostname, ips))
  1633. def add_txns_to_remove_port_dns_records(self, txn, port):
  1634. lswitch_name = utils.ovn_name(port['network_id'])
  1635. ls, ls_dns_record = self._nb_idl.get_ls_and_dns_record(lswitch_name)
  1636. if ls_dns_record is None:
  1637. return
  1638. net = port.get('network', {})
  1639. net_dns_domain = net.get('dns_domain', '').rstrip('.')
  1640. hostnames = []
  1641. for dns_assignment in port['dns_assignment']:
  1642. hostname = dns_assignment['hostname']
  1643. fqdn = dns_assignment['fqdn'].rstrip('.')
  1644. if hostname not in hostnames:
  1645. hostnames.append(hostname)
  1646. net_dns_fqdn = hostname + '.' + net_dns_domain
  1647. if net_dns_domain and net_dns_fqdn != fqdn:
  1648. hostnames.append(net_dns_fqdn)
  1649. if fqdn not in hostnames:
  1650. hostnames.append(fqdn)
  1651. for hostname in hostnames:
  1652. if ls_dns_record.records.get(hostname):
  1653. txn.add(self._nb_idl.dns_remove_record(
  1654. ls_dns_record.uuid, hostname))