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.

837 lines
36 KiB

  1. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  2. # not use this file except in compliance with the License. You may obtain
  3. # a copy of the License at
  4. #
  5. # http://www.apache.org/licenses/LICENSE-2.0
  6. #
  7. # Unless required by applicable law or agreed to in writing, software
  8. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  9. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  10. # License for the specific language governing permissions and limitations
  11. # under the License.
  12. import contextlib
  13. import uuid
  14. from neutron_lib import exceptions as n_exc
  15. from oslo_log import log
  16. import tenacity
  17. from neutron_lib.utils import helpers
  18. from oslo_utils import uuidutils
  19. from ovsdbapp.backend import ovs_idl
  20. from ovsdbapp.backend.ovs_idl import connection
  21. from ovsdbapp.backend.ovs_idl import idlutils
  22. from ovsdbapp.backend.ovs_idl import transaction as idl_trans
  23. from ovsdbapp.backend.ovs_idl import vlog
  24. from ovsdbapp.schema.ovn_northbound import impl_idl as nb_impl_idl
  25. from ovsdbapp.schema.ovn_southbound import impl_idl as sb_impl_idl
  26. from networking_ovn._i18n import _
  27. from networking_ovn.common import config as cfg
  28. from networking_ovn.common import constants as ovn_const
  29. from networking_ovn.common import exceptions as ovn_exc
  30. from networking_ovn.common import utils
  31. from networking_ovn.ovsdb import commands as cmd
  32. from networking_ovn.ovsdb import ovsdb_monitor
  33. LOG = log.getLogger(__name__)
  34. class OvnNbTransaction(idl_trans.Transaction):
  35. def __init__(self, *args, **kwargs):
  36. # NOTE(lucasagomes): The bump_nb_cfg parameter is only used by
  37. # the agents health status check
  38. self.bump_nb_cfg = kwargs.pop('bump_nb_cfg', False)
  39. super(OvnNbTransaction, self).__init__(*args, **kwargs)
  40. def pre_commit(self, txn):
  41. if not self.bump_nb_cfg:
  42. return
  43. self.api.nb_global.increment('nb_cfg')
  44. # This version of Backend doesn't use a class variable for ovsdb_connection
  45. # and therefor allows networking-ovn to manage connection scope on its own
  46. class Backend(ovs_idl.Backend):
  47. lookup_table = {}
  48. def __init__(self, connection):
  49. self.ovsdb_connection = connection
  50. super(Backend, self).__init__(connection)
  51. def start_connection(self, connection):
  52. try:
  53. self.ovsdb_connection.start()
  54. except Exception as e:
  55. connection_exception = OvsdbConnectionUnavailable(
  56. db_schema=self.schema, error=e)
  57. LOG.exception(connection_exception)
  58. raise connection_exception
  59. @property
  60. def idl(self):
  61. return self.ovsdb_connection.idl
  62. @property
  63. def tables(self):
  64. return self.idl.tables
  65. _tables = tables
  66. def is_table_present(self, table_name):
  67. return table_name in self._tables
  68. def is_col_present(self, table_name, col_name):
  69. return self.is_table_present(table_name) and (
  70. col_name in self._tables[table_name].columns)
  71. def create_transaction(self, check_error=False, log_errors=True):
  72. return idl_trans.Transaction(
  73. self, self.ovsdb_connection, self.ovsdb_connection.timeout,
  74. check_error, log_errors)
  75. # Check for a column match in the table. If not found do a retry with
  76. # a stop delay of 10 secs. This function would be useful if the caller
  77. # wants to verify for the presence of a particular row in the table
  78. # with the column match before doing any transaction.
  79. # Eg. We can check if Logical_Switch row is present before adding a
  80. # logical switch port to it.
  81. @tenacity.retry(retry=tenacity.retry_if_exception_type(RuntimeError),
  82. wait=tenacity.wait_exponential(),
  83. stop=tenacity.stop_after_delay(10),
  84. reraise=True)
  85. def check_for_row_by_value_and_retry(self, table, column, match):
  86. try:
  87. idlutils.row_by_value(self.idl, table, column, match)
  88. except idlutils.RowNotFound:
  89. msg = (_("%(match)s does not exist in %(column)s of %(table)s")
  90. % {'match': match, 'column': column, 'table': table})
  91. raise RuntimeError(msg)
  92. class OvsdbConnectionUnavailable(n_exc.ServiceUnavailable):
  93. message = _("OVS database connection to %(db_schema)s failed with error: "
  94. "'%(error)s'. Verify that the OVS and OVN services are "
  95. "available and that the 'ovn_nb_connection' and "
  96. "'ovn_sb_connection' configuration options are correct.")
  97. # Retry forever to get the OVN NB and SB IDLs. Wait 2^x * 1 seconds between
  98. # each retry, up to 180 seconds, then 180 seconds afterwards.
  99. def get_ovn_idls(driver, trigger):
  100. @tenacity.retry(
  101. wait=tenacity.wait_exponential(max=180),
  102. reraise=True)
  103. def get_ovn_idl_retry(cls):
  104. trigger_class = utils.get_method_class(trigger)
  105. LOG.info('Getting %(cls)s for %(trigger)s with retry',
  106. {'cls': cls.__name__, 'trigger': trigger_class.__name__})
  107. return cls(get_connection(cls, trigger, driver))
  108. vlog.use_python_logger(max_level=cfg.get_ovn_ovsdb_log_level())
  109. return tuple(get_ovn_idl_retry(c) for c in (OvsdbNbOvnIdl, OvsdbSbOvnIdl))
  110. def get_connection(db_class, trigger=None, driver=None):
  111. # The trigger is the start() method of the worker class
  112. if db_class == OvsdbNbOvnIdl:
  113. args = (cfg.get_ovn_nb_connection(), 'OVN_Northbound')
  114. cls = ovsdb_monitor.OvnNbIdl
  115. elif db_class == OvsdbSbOvnIdl:
  116. args = (cfg.get_ovn_sb_connection(), 'OVN_Southbound')
  117. cls = ovsdb_monitor.OvnSbIdl
  118. if trigger and utils.get_method_class(trigger) == ovsdb_monitor.OvnWorker:
  119. idl_ = cls.from_server(*args, driver=driver)
  120. else:
  121. if db_class == OvsdbSbOvnIdl:
  122. idl_ = ovsdb_monitor.BaseOvnSbIdl.from_server(*args)
  123. else:
  124. idl_ = ovsdb_monitor.BaseOvnIdl.from_server(*args)
  125. return connection.Connection(idl_, timeout=cfg.get_ovn_ovsdb_timeout())
  126. class OvsdbNbOvnIdl(nb_impl_idl.OvnNbApiIdlImpl, Backend):
  127. def __init__(self, connection):
  128. super(OvsdbNbOvnIdl, self).__init__(connection)
  129. self.idl._session.reconnect.set_probe_interval(
  130. cfg.get_ovn_ovsdb_probe_interval())
  131. @property
  132. def nb_global(self):
  133. return next(iter(self.tables['NB_Global'].rows.values()))
  134. def create_transaction(self, check_error=False, log_errors=True,
  135. bump_nb_cfg=False):
  136. return OvnNbTransaction(
  137. self, self.ovsdb_connection, self.ovsdb_connection.timeout,
  138. check_error, log_errors, bump_nb_cfg=bump_nb_cfg)
  139. @contextlib.contextmanager
  140. def transaction(self, *args, **kwargs):
  141. """A wrapper on the ovsdbapp transaction to work with revisions.
  142. This method is just a wrapper around the ovsdbapp transaction
  143. to handle revision conflicts correctly.
  144. """
  145. try:
  146. with super(OvsdbNbOvnIdl, self).transaction(*args, **kwargs) as t:
  147. yield t
  148. except ovn_exc.RevisionConflict as e:
  149. LOG.info('Transaction aborted. Reason: %s', e)
  150. def set_lswitch_ext_ids(self, lswitch_id, ext_ids, if_exists=True):
  151. return cmd.LSwitchSetExternalIdsCommand(self, lswitch_id, ext_ids,
  152. if_exists)
  153. def create_lswitch_port(self, lport_name, lswitch_name, may_exist=True,
  154. **columns):
  155. return cmd.AddLSwitchPortCommand(self, lport_name, lswitch_name,
  156. may_exist, **columns)
  157. def set_lswitch_port(self, lport_name, if_exists=True, **columns):
  158. return cmd.SetLSwitchPortCommand(self, lport_name,
  159. if_exists, **columns)
  160. def delete_lswitch_port(self, lport_name=None, lswitch_name=None,
  161. ext_id=None, if_exists=True):
  162. if lport_name is not None:
  163. return cmd.DelLSwitchPortCommand(self, lport_name,
  164. lswitch_name, if_exists)
  165. else:
  166. raise RuntimeError(_("Currently only supports "
  167. "delete by lport-name"))
  168. def get_all_logical_switches_with_ports(self):
  169. result = []
  170. for lswitch in self._tables['Logical_Switch'].rows.values():
  171. if ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY not in (
  172. lswitch.external_ids):
  173. continue
  174. ports = []
  175. provnet_port = None
  176. for lport in getattr(lswitch, 'ports', []):
  177. if ovn_const.OVN_PORT_NAME_EXT_ID_KEY in lport.external_ids:
  178. ports.append(lport.name)
  179. # Handle provider network port
  180. elif lport.name.startswith(
  181. ovn_const.OVN_PROVNET_PORT_NAME_PREFIX):
  182. provnet_port = lport.name
  183. result.append({'name': lswitch.name,
  184. 'ports': ports,
  185. 'provnet_port': provnet_port})
  186. return result
  187. def get_all_logical_routers_with_rports(self):
  188. """Get logical Router ports associated with all logical Routers
  189. @return: list of dict, each dict has key-value:
  190. - 'name': string router_id in neutron.
  191. - 'static_routes': list of static routes dict.
  192. - 'ports': dict of port_id in neutron (key) and networks on
  193. port (value).
  194. - 'snats': list of snats dict
  195. - 'dnat_and_snats': list of dnat_and_snats dict
  196. """
  197. result = []
  198. for lrouter in self._tables['Logical_Router'].rows.values():
  199. if ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY not in (
  200. lrouter.external_ids):
  201. continue
  202. lrports = {lrport.name.replace('lrp-', ''): lrport.networks
  203. for lrport in getattr(lrouter, 'ports', [])}
  204. sroutes = [{'destination': sroute.ip_prefix,
  205. 'nexthop': sroute.nexthop}
  206. for sroute in getattr(lrouter, 'static_routes', [])]
  207. dnat_and_snats = []
  208. snat = []
  209. for nat in getattr(lrouter, 'nat', []):
  210. columns = {'logical_ip': nat.logical_ip,
  211. 'external_ip': nat.external_ip,
  212. 'type': nat.type}
  213. if nat.type == 'dnat_and_snat':
  214. if nat.external_mac:
  215. columns['external_mac'] = nat.external_mac[0]
  216. if nat.logical_port:
  217. columns['logical_port'] = nat.logical_port[0]
  218. dnat_and_snats.append(columns)
  219. elif nat.type == 'snat':
  220. snat.append(columns)
  221. result.append({'name': lrouter.name.replace('neutron-', ''),
  222. 'static_routes': sroutes,
  223. 'ports': lrports,
  224. 'snats': snat,
  225. 'dnat_and_snats': dnat_and_snats})
  226. return result
  227. def get_acl_by_id(self, acl_id):
  228. try:
  229. return self.lookup('ACL', uuid.UUID(acl_id))
  230. except idlutils.RowNotFound:
  231. return
  232. def get_acls_for_lswitches(self, lswitch_names):
  233. """Get the existing set of acls that belong to the logical switches
  234. @param lswitch_names: List of logical switch names
  235. @type lswitch_names: []
  236. @var acl_values_dict: A dictionary indexed by port_id containing the
  237. list of acl values in string format that belong
  238. to that port
  239. @var acl_obj_dict: A dictionary indexed by acl value containing the
  240. corresponding acl idl object.
  241. @var lswitch_ovsdb_dict: A dictionary mapping from logical switch
  242. name to lswitch idl object
  243. @return: (acl_values_dict, acl_obj_dict, lswitch_ovsdb_dict)
  244. """
  245. acl_values_dict = {}
  246. acl_obj_dict = {}
  247. lswitch_ovsdb_dict = {}
  248. for lswitch_name in lswitch_names:
  249. try:
  250. lswitch = idlutils.row_by_value(self.idl,
  251. 'Logical_Switch',
  252. 'name',
  253. utils.ovn_name(lswitch_name))
  254. except idlutils.RowNotFound:
  255. # It is possible for the logical switch to be deleted
  256. # while we are searching for it by name in idl.
  257. continue
  258. lswitch_ovsdb_dict[lswitch_name] = lswitch
  259. acls = getattr(lswitch, 'acls', [])
  260. # Iterate over each acl in a lswitch and store the acl in
  261. # a key:value representation for e.g. acl_string. This
  262. # key:value representation can invoke the code -
  263. # self._ovn.add_acl(**acl_string)
  264. for acl in acls:
  265. ext_ids = getattr(acl, 'external_ids', {})
  266. port_id = ext_ids.get('neutron:lport')
  267. acl_list = acl_values_dict.setdefault(port_id, [])
  268. acl_string = {'lport': port_id,
  269. 'lswitch': utils.ovn_name(lswitch_name)}
  270. for acl_key in getattr(acl, "_data", {}):
  271. try:
  272. acl_string[acl_key] = getattr(acl, acl_key)
  273. except AttributeError:
  274. pass
  275. acl_obj_dict[str(acl_string)] = acl
  276. acl_list.append(acl_string)
  277. return acl_values_dict, acl_obj_dict, lswitch_ovsdb_dict
  278. def create_lrouter(self, name, may_exist=True, **columns):
  279. return cmd.AddLRouterCommand(self, name,
  280. may_exist, **columns)
  281. def update_lrouter(self, name, if_exists=True, **columns):
  282. return cmd.UpdateLRouterCommand(self, name,
  283. if_exists, **columns)
  284. def delete_lrouter(self, name, if_exists=True):
  285. return cmd.DelLRouterCommand(self, name, if_exists)
  286. def add_lrouter_port(self, name, lrouter, may_exist=False, **columns):
  287. return cmd.AddLRouterPortCommand(self, name, lrouter,
  288. may_exist, **columns)
  289. def update_lrouter_port(self, name, if_exists=True, **columns):
  290. return cmd.UpdateLRouterPortCommand(self, name, if_exists, **columns)
  291. def delete_lrouter_port(self, name, lrouter, if_exists=True):
  292. return cmd.DelLRouterPortCommand(self, name, lrouter,
  293. if_exists)
  294. def set_lrouter_port_in_lswitch_port(
  295. self, lswitch_port, lrouter_port, is_gw_port=False, if_exists=True,
  296. lsp_address=ovn_const.DEFAULT_ADDR_FOR_LSP_WITH_PEER):
  297. return cmd.SetLRouterPortInLSwitchPortCommand(self, lswitch_port,
  298. lrouter_port, is_gw_port,
  299. if_exists,
  300. lsp_address)
  301. def add_acl(self, lswitch, lport, **columns):
  302. return cmd.AddACLCommand(self, lswitch, lport, **columns)
  303. def delete_acl(self, lswitch, lport, if_exists=True):
  304. return cmd.DelACLCommand(self, lswitch, lport, if_exists)
  305. def update_acls(self, lswitch_names, port_list, acl_new_values_dict,
  306. need_compare=True, is_add_acl=True):
  307. return cmd.UpdateACLsCommand(self, lswitch_names,
  308. port_list, acl_new_values_dict,
  309. need_compare=need_compare,
  310. is_add_acl=is_add_acl)
  311. def add_static_route(self, lrouter, **columns):
  312. return cmd.AddStaticRouteCommand(self, lrouter, **columns)
  313. def delete_static_route(self, lrouter, ip_prefix, nexthop, if_exists=True):
  314. return cmd.DelStaticRouteCommand(self, lrouter, ip_prefix, nexthop,
  315. if_exists)
  316. def create_address_set(self, name, may_exist=True, **columns):
  317. return cmd.AddAddrSetCommand(self, name, may_exist, **columns)
  318. def delete_address_set(self, name, if_exists=True, **columns):
  319. return cmd.DelAddrSetCommand(self, name, if_exists)
  320. def update_address_set(self, name, addrs_add, addrs_remove,
  321. if_exists=True):
  322. return cmd.UpdateAddrSetCommand(self, name, addrs_add, addrs_remove,
  323. if_exists)
  324. def update_address_set_ext_ids(self, name, external_ids, if_exists=True):
  325. return cmd.UpdateAddrSetExtIdsCommand(self, name, external_ids,
  326. if_exists)
  327. def _get_logical_router_port_gateway_chassis(self, lrp):
  328. """Get the list of chassis hosting this gateway port.
  329. @param lrp: logical router port
  330. @type lrp: Logical_Router_Port row
  331. @return: List of tuples (chassis_name, priority)
  332. """
  333. # Try retrieving gateway_chassis with new schema. If new schema is not
  334. # supported or user is using old schema, then use old schema for
  335. # getting gateway_chassis
  336. chassis = []
  337. if self._tables.get('Gateway_Chassis'):
  338. for gwc in lrp.gateway_chassis:
  339. chassis.append((gwc.chassis_name, gwc.priority))
  340. else:
  341. rc = lrp.options.get(ovn_const.OVN_GATEWAY_CHASSIS_KEY)
  342. if rc:
  343. chassis.append((rc, 0))
  344. return chassis
  345. def get_all_chassis_gateway_bindings(self,
  346. chassis_candidate_list=None):
  347. chassis_bindings = {}
  348. for chassis_name in chassis_candidate_list or []:
  349. chassis_bindings.setdefault(chassis_name, [])
  350. for lrp in self._tables['Logical_Router_Port'].rows.values():
  351. if not lrp.name.startswith('lrp-'):
  352. continue
  353. chassis = self._get_logical_router_port_gateway_chassis(lrp)
  354. for chassis_name, prio in chassis:
  355. if (not chassis_candidate_list or
  356. chassis_name in chassis_candidate_list):
  357. routers_hosted = chassis_bindings.setdefault(chassis_name,
  358. [])
  359. routers_hosted.append((lrp.name, prio))
  360. return chassis_bindings
  361. def get_gateway_chassis_binding(self, gateway_name):
  362. try:
  363. lrp = idlutils.row_by_value(
  364. self.idl, 'Logical_Router_Port', 'name', gateway_name)
  365. chassis_list = self._get_logical_router_port_gateway_chassis(lrp)
  366. return [chassis for chassis, prio in chassis_list]
  367. except idlutils.RowNotFound:
  368. return []
  369. def get_unhosted_gateways(self, port_physnet_dict, chassis_physnets,
  370. gw_chassis):
  371. unhosted_gateways = []
  372. valid_chassis_list = list(chassis_physnets)
  373. for lrp in self._tables['Logical_Router_Port'].rows.values():
  374. if not lrp.name.startswith('lrp-'):
  375. continue
  376. physnet = port_physnet_dict.get(lrp.name[len('lrp-'):])
  377. chassis_list = self._get_logical_router_port_gateway_chassis(lrp)
  378. for chassis_name, prio in chassis_list:
  379. # TODO(azbiswas): Handle the case when a chassis is no
  380. # longer valid. This may involve moving conntrack states,
  381. # so it needs to discussed in the OVN community first.
  382. if (chassis_name == ovn_const.OVN_GATEWAY_INVALID_CHASSIS or
  383. chassis_name not in valid_chassis_list or
  384. (physnet and
  385. physnet not in chassis_physnets.get(chassis_name)) or
  386. (gw_chassis and chassis_name not in gw_chassis)):
  387. unhosted_gateways.append(lrp.name)
  388. return unhosted_gateways
  389. def add_dhcp_options(self, subnet_id, port_id=None, may_exist=True,
  390. **columns):
  391. return cmd.AddDHCPOptionsCommand(self, subnet_id, port_id=port_id,
  392. may_exist=may_exist, **columns)
  393. def delete_dhcp_options(self, row_uuid, if_exists=True):
  394. return cmd.DelDHCPOptionsCommand(self, row_uuid, if_exists=if_exists)
  395. def _format_dhcp_row(self, row):
  396. ext_ids = dict(getattr(row, 'external_ids', {}))
  397. return {'cidr': row.cidr, 'options': dict(row.options),
  398. 'external_ids': ext_ids, 'uuid': row.uuid}
  399. def get_subnet_dhcp_options(self, subnet_id, with_ports=False):
  400. subnet = None
  401. ports = []
  402. for row in self._tables['DHCP_Options'].rows.values():
  403. external_ids = getattr(row, 'external_ids', {})
  404. if subnet_id == external_ids.get('subnet_id'):
  405. port_id = external_ids.get('port_id')
  406. if with_ports and port_id:
  407. ports.append(self._format_dhcp_row(row))
  408. elif not port_id:
  409. subnet = self._format_dhcp_row(row)
  410. if not with_ports:
  411. break
  412. return {'subnet': subnet, 'ports': ports}
  413. def get_subnets_dhcp_options(self, subnet_ids):
  414. ret_opts = []
  415. for row in self._tables['DHCP_Options'].rows.values():
  416. external_ids = getattr(row, 'external_ids', {})
  417. if (external_ids.get('subnet_id') in subnet_ids and not
  418. external_ids.get('port_id')):
  419. ret_opts.append(self._format_dhcp_row(row))
  420. if len(ret_opts) == len(subnet_ids):
  421. break
  422. return ret_opts
  423. def get_all_dhcp_options(self):
  424. dhcp_options = {'subnets': {}, 'ports_v4': {}, 'ports_v6': {}}
  425. for row in self._tables['DHCP_Options'].rows.values():
  426. external_ids = getattr(row, 'external_ids', {})
  427. if not external_ids.get('subnet_id'):
  428. # This row is not created by OVN ML2 driver. Ignore it.
  429. continue
  430. if not external_ids.get('port_id'):
  431. dhcp_options['subnets'][external_ids['subnet_id']] = (
  432. self._format_dhcp_row(row))
  433. else:
  434. port_dict = 'ports_v6' if ':' in row.cidr else 'ports_v4'
  435. dhcp_options[port_dict][external_ids['port_id']] = (
  436. self._format_dhcp_row(row))
  437. return dhcp_options
  438. def get_address_sets(self):
  439. address_sets = {}
  440. for row in self._tables['Address_Set'].rows.values():
  441. # TODO(lucasagomes): Remove OVN_SG_NAME_EXT_ID_KEY in the
  442. # Rocky release
  443. if not (ovn_const.OVN_SG_EXT_ID_KEY in row.external_ids or
  444. ovn_const.OVN_SG_NAME_EXT_ID_KEY in row.external_ids):
  445. continue
  446. name = getattr(row, 'name')
  447. data = {}
  448. for row_key in getattr(row, "_data", {}):
  449. data[row_key] = getattr(row, row_key)
  450. address_sets[name] = data
  451. return address_sets
  452. def get_router_port_options(self, lsp_name):
  453. try:
  454. lsp = idlutils.row_by_value(self.idl, 'Logical_Switch_Port',
  455. 'name', lsp_name)
  456. options = getattr(lsp, 'options')
  457. for key in list(options.keys()):
  458. if key not in ovn_const.OVN_ROUTER_PORT_OPTION_KEYS:
  459. del(options[key])
  460. return options
  461. except idlutils.RowNotFound:
  462. return {}
  463. def add_nat_rule_in_lrouter(self, lrouter, **columns):
  464. return cmd.AddNATRuleInLRouterCommand(self, lrouter, **columns)
  465. def delete_nat_rule_in_lrouter(self, lrouter, type, logical_ip,
  466. external_ip, if_exists=True):
  467. return cmd.DeleteNATRuleInLRouterCommand(self, lrouter, type,
  468. logical_ip, external_ip,
  469. if_exists)
  470. def get_lrouter_nat_rules(self, lrouter_name):
  471. try:
  472. lrouter = idlutils.row_by_value(self.idl, 'Logical_Router',
  473. 'name', lrouter_name)
  474. except idlutils.RowNotFound:
  475. msg = _("Logical Router %s does not exist") % lrouter_name
  476. raise RuntimeError(msg)
  477. nat_rules = []
  478. for nat_rule in getattr(lrouter, 'nat', []):
  479. ext_ids = {}
  480. # TODO(dalvarez): remove this check once the minimum OVS required
  481. # version contains the column (when OVS 2.8.2 is released).
  482. if self.is_col_present('NAT', 'external_ids'):
  483. ext_ids = dict(getattr(nat_rule, 'external_ids', {}))
  484. nat_rules.append({'external_ip': nat_rule.external_ip,
  485. 'logical_ip': nat_rule.logical_ip,
  486. 'type': nat_rule.type,
  487. 'uuid': nat_rule.uuid,
  488. 'external_ids': ext_ids})
  489. return nat_rules
  490. def set_nat_rule_in_lrouter(self, lrouter, nat_rule_uuid, **columns):
  491. return cmd.SetNATRuleInLRouterCommand(self, lrouter, nat_rule_uuid,
  492. **columns)
  493. def add_nat_ip_to_lrport_peer_options(self, lport, nat_ip):
  494. return cmd.AddNatIpToLRPortPeerOptionsCommand(self, lport, nat_ip)
  495. def delete_nat_ip_from_lrport_peer_options(self, lport, nat_ip):
  496. return cmd.DeleteNatIpFromLRPortPeerOptionsCommand(self, lport, nat_ip)
  497. def get_lswitch_port(self, lsp_name):
  498. try:
  499. return self.lookup('Logical_Switch_Port', lsp_name)
  500. except idlutils.RowNotFound:
  501. return None
  502. def get_parent_port(self, lsp_name):
  503. lsp = self.get_lswitch_port(lsp_name)
  504. if not lsp:
  505. return ''
  506. return lsp.parent_name
  507. def get_lswitch(self, lswitch_name):
  508. # FIXME(lucasagomes): We should refactor those get_*()
  509. # methods. Some of 'em require the name, others IDs etc... It can
  510. # be confusing.
  511. if uuidutils.is_uuid_like(lswitch_name):
  512. lswitch_name = utils.ovn_name(lswitch_name)
  513. try:
  514. return self.lookup('Logical_Switch', lswitch_name)
  515. except idlutils.RowNotFound:
  516. return None
  517. def get_ls_and_dns_record(self, lswitch_name):
  518. ls = self.get_lswitch(lswitch_name)
  519. if not ls:
  520. return (None, None)
  521. if not hasattr(ls, 'dns_records'):
  522. return (ls, None)
  523. for dns_row in ls.dns_records:
  524. if dns_row.external_ids.get('ls_name') == lswitch_name:
  525. return (ls, dns_row)
  526. return (ls, None)
  527. def get_floatingip(self, fip_id):
  528. # TODO(dalvarez): remove this check once the minimum OVS required
  529. # version contains the column (when OVS 2.8.2 is released).
  530. if not self.is_col_present('NAT', 'external_ids'):
  531. return
  532. fip = self.db_find('NAT', ('external_ids', '=',
  533. {ovn_const.OVN_FIP_EXT_ID_KEY: fip_id}))
  534. result = fip.execute(check_error=True)
  535. return result[0] if result else None
  536. def get_floatingip_by_ips(self, router_id, logical_ip, external_ip):
  537. if not all([router_id, logical_ip, external_ip]):
  538. return
  539. for nat in self.get_lrouter_nat_rules(utils.ovn_name(router_id)):
  540. if (nat['type'] == 'dnat_and_snat' and
  541. nat['logical_ip'] == logical_ip and
  542. nat['external_ip'] == external_ip):
  543. return nat
  544. def get_address_set(self, addrset_id, ip_version='ip4'):
  545. addr_name = utils.ovn_addrset_name(addrset_id, ip_version)
  546. try:
  547. return idlutils.row_by_value(self.idl, 'Address_Set',
  548. 'name', addr_name)
  549. except idlutils.RowNotFound:
  550. return None
  551. def check_revision_number(self, name, resource, resource_type,
  552. if_exists=True):
  553. return cmd.CheckRevisionNumberCommand(
  554. self, name, resource, resource_type, if_exists)
  555. def get_lrouter(self, lrouter_name):
  556. if uuidutils.is_uuid_like(lrouter_name):
  557. lrouter_name = utils.ovn_name(lrouter_name)
  558. # TODO(lucasagomes): Use lr_get() once we start refactoring this
  559. # API to use methods from ovsdbapp.
  560. lr = self.db_find_rows('Logical_Router', ('name', '=', lrouter_name))
  561. result = lr.execute(check_error=True)
  562. return result[0] if result else None
  563. def get_lrouter_port(self, lrp_name):
  564. # TODO(mangelajo): Implement lrp_get() ovsdbapp and use from here
  565. if uuidutils.is_uuid_like(lrp_name):
  566. lrp_name = utils.ovn_lrouter_port_name(lrp_name)
  567. lrp = self.db_find_rows('Logical_Router_Port', ('name', '=', lrp_name))
  568. result = lrp.execute(check_error=True)
  569. return result[0] if result else None
  570. def delete_lrouter_ext_gw(self, lrouter_name, if_exists=True):
  571. return cmd.DeleteLRouterExtGwCommand(self, lrouter_name, if_exists)
  572. def is_port_groups_supported(self):
  573. return self.is_table_present('Port_Group')
  574. def get_port_group(self, pg_name):
  575. if uuidutils.is_uuid_like(pg_name):
  576. pg_name = utils.ovn_port_group_name(pg_name)
  577. try:
  578. for pg in self._tables['Port_Group'].rows.values():
  579. if pg.name == pg_name:
  580. return pg
  581. except KeyError:
  582. # TODO(dalvarez): This except block is added for backwards compat
  583. # with old OVN schemas (<=2.9) where Port Groups are not present.
  584. # This (and other conditional code around this feature) shall be
  585. # removed at some point.
  586. return
  587. def get_port_groups(self):
  588. port_groups = {}
  589. try:
  590. for row in self._tables['Port_Group'].rows.values():
  591. name = getattr(row, 'name')
  592. if not (ovn_const.OVN_SG_EXT_ID_KEY in row.external_ids or
  593. name == ovn_const.OVN_DROP_PORT_GROUP_NAME):
  594. continue
  595. data = {}
  596. for row_key in getattr(row, "_data", {}):
  597. data[row_key] = getattr(row, row_key)
  598. port_groups[name] = data
  599. except KeyError:
  600. # TODO(dalvarez): This except block is added for backwards compat
  601. # with old OVN schemas (<=2.9) where Port Groups are not present.
  602. # This (and other conditional code around this feature) shall be
  603. # removed at some point.
  604. pass
  605. return port_groups
  606. def check_liveness(self):
  607. return cmd.CheckLivenessCommand(self)
  608. class OvsdbSbOvnIdl(sb_impl_idl.OvnSbApiIdlImpl, Backend):
  609. def __init__(self, connection):
  610. super(OvsdbSbOvnIdl, self).__init__(connection)
  611. # TODO(twilson) This direct access of the idl should be removed in
  612. # favor of a backend-agnostic method
  613. self.idl._session.reconnect.set_probe_interval(
  614. cfg.get_ovn_ovsdb_probe_interval())
  615. def _get_chassis_physnets(self, chassis):
  616. bridge_mappings = chassis.external_ids.get('ovn-bridge-mappings', '')
  617. mapping_dict = helpers.parse_mappings(bridge_mappings.split(','),
  618. unique_values=False)
  619. return list(mapping_dict.keys())
  620. def chassis_exists(self, hostname):
  621. cmd = self.db_find('Chassis', ('hostname', '=', hostname))
  622. return bool(cmd.execute(check_error=True))
  623. def get_chassis_hostname_and_physnets(self):
  624. chassis_info_dict = {}
  625. for ch in self.chassis_list().execute(check_error=True):
  626. chassis_info_dict[ch.hostname] = self._get_chassis_physnets(ch)
  627. return chassis_info_dict
  628. def get_gateway_chassis_from_cms_options(self):
  629. gw_chassis = []
  630. for ch in self.chassis_list().execute(check_error=True):
  631. cms_options = ch.external_ids.get('ovn-cms-options', '')
  632. if 'enable-chassis-as-gw' in cms_options.split(','):
  633. gw_chassis.append(ch.name)
  634. return gw_chassis
  635. def get_chassis_and_physnets(self):
  636. chassis_info_dict = {}
  637. for ch in self.chassis_list().execute(check_error=True):
  638. chassis_info_dict[ch.name] = self._get_chassis_physnets(ch)
  639. return chassis_info_dict
  640. def get_all_chassis(self, chassis_type=None):
  641. # TODO(azbiswas): Use chassis_type as input once the compute type
  642. # preference patch (as part of external ids) merges.
  643. return [c.name for c in self.chassis_list().execute(check_error=True)]
  644. def get_chassis_data_for_ml2_bind_port(self, hostname):
  645. try:
  646. cmd = self.db_find_rows('Chassis', ('hostname', '=', hostname))
  647. chassis = next(c for c in cmd.execute(check_error=True))
  648. except StopIteration:
  649. msg = _('Chassis with hostname %s does not exist') % hostname
  650. raise RuntimeError(msg)
  651. return (chassis.external_ids.get('datapath-type', ''),
  652. chassis.external_ids.get('iface-types', ''),
  653. self._get_chassis_physnets(chassis))
  654. def get_metadata_port_network(self, network):
  655. # TODO(twilson) This function should really just take a Row/RowView
  656. try:
  657. dp = self.lookup('Datapath_Binding', uuid.UUID(network))
  658. except idlutils.RowNotFound:
  659. return None
  660. cmd = self.db_find_rows('Port_Binding', ('datapath', '=', dp),
  661. ('type', '=', 'localport'))
  662. return next(iter(cmd.execute(check_error=True)), None)
  663. def get_chassis_metadata_networks(self, chassis_name):
  664. """Return a list with the metadata networks the chassis is hosting."""
  665. chassis = self.lookup('Chassis', chassis_name)
  666. proxy_networks = chassis.external_ids.get(
  667. 'neutron-metadata-proxy-networks', None)
  668. return proxy_networks.split(',') if proxy_networks else []
  669. def set_chassis_metadata_networks(self, chassis, networks):
  670. nets = ','.join(networks) if networks else ''
  671. # TODO(twilson) This could just use DbSetCommand
  672. return cmd.UpdateChassisExtIdsCommand(
  673. self, chassis, {'neutron-metadata-proxy-networks': nets},
  674. if_exists=True)
  675. def set_chassis_neutron_description(self, chassis, description,
  676. agent_type):
  677. desc_key = (ovn_const.OVN_AGENT_METADATA_DESC_KEY
  678. if agent_type == ovn_const.OVN_METADATA_AGENT else
  679. ovn_const.OVN_AGENT_DESC_KEY)
  680. return cmd.UpdateChassisExtIdsCommand(
  681. self, chassis, {desc_key: description}, if_exists=False)
  682. def get_network_port_bindings_by_ip(self, network, ip_address):
  683. rows = self.db_list_rows('Port_Binding').execute(check_error=True)
  684. # TODO(twilson) It would be useful to have a db_find that takes a
  685. # comparison function
  686. return [r for r in rows
  687. if (r.mac and str(r.datapath.uuid) == network) and
  688. ip_address in r.mac[0].split(' ')]
  689. def update_metadata_health_status(self, chassis, nb_cfg):
  690. return cmd.UpdateChassisExtIdsCommand(
  691. self, chassis,
  692. {ovn_const.OVN_AGENT_METADATA_SB_CFG_KEY: str(nb_cfg)},
  693. if_exists=True)
  694. def set_port_cidrs(self, name, cidrs):
  695. # TODO(twilson) add if_exists to db commands
  696. return self.db_set('Port_Binding', name, 'external_ids',
  697. {'neutron-port-cidrs': cidrs}, if_exists=True)
  698. def get_ports_on_chassis(self, chassis):
  699. # TODO(twilson) Some day it would be nice to stop passing names around
  700. # and just start using chassis objects so db_find_rows could be used
  701. rows = self.db_list_rows('Port_Binding').execute(check_error=True)
  702. return [r for r in rows if r.chassis and r.chassis[0].name == chassis]
  703. def get_logical_port_chassis_and_datapath(self, name):
  704. for port in self._tables['Port_Binding'].rows.values():
  705. if port.logical_port == name:
  706. datapath = str(port.datapath.uuid)
  707. chassis = port.chassis[0].name if port.chassis else None
  708. return chassis, datapath