A set of Neutron drivers for the VMware NSX.
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.
 
 
 

2873 lines
136 KiB

  1. # Copyright 2018 VMware, 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 decorator
  16. import mock
  17. import netaddr
  18. from oslo_config import cfg
  19. from oslo_db import exception as db_exc
  20. from oslo_log import log as logging
  21. import oslo_messaging
  22. from oslo_utils import excutils
  23. from sqlalchemy import exc as sql_exc
  24. import webob.exc
  25. from six import moves
  26. from six import string_types
  27. from neutron.db import agents_db
  28. from neutron.db import agentschedulers_db
  29. from neutron.db import allowedaddresspairs_db as addr_pair_db
  30. from neutron.db.availability_zone import router as router_az_db
  31. from neutron.db import db_base_plugin_v2
  32. from neutron.db import dns_db
  33. from neutron.db import external_net_db
  34. from neutron.db import extradhcpopt_db
  35. from neutron.db import extraroute_db
  36. from neutron.db import l3_attrs_db
  37. from neutron.db import l3_db
  38. from neutron.db import l3_gwmode_db
  39. from neutron.db.models import securitygroup as securitygroup_model
  40. from neutron.db import models_v2
  41. from neutron.db import portbindings_db
  42. from neutron.db import portsecurity_db
  43. from neutron.db import securitygroups_db
  44. from neutron.db import vlantransparent_db
  45. from neutron.extensions import securitygroup as ext_sg
  46. from neutron.extensions import tagging
  47. from neutron_lib.agent import topics
  48. from neutron_lib.api.definitions import allowedaddresspairs as addr_apidef
  49. from neutron_lib.api.definitions import availability_zone as az_def
  50. from neutron_lib.api.definitions import external_net as extnet_apidef
  51. from neutron_lib.api.definitions import extra_dhcp_opt as ext_edo
  52. from neutron_lib.api.definitions import l3 as l3_apidef
  53. from neutron_lib.api.definitions import port_security as psec
  54. from neutron_lib.api.definitions import portbindings as pbin
  55. from neutron_lib.api.definitions import provider_net as pnet
  56. from neutron_lib.api import faults
  57. from neutron_lib.api import validators
  58. from neutron_lib.api.validators import availability_zone as az_validator
  59. from neutron_lib import constants
  60. from neutron_lib.db import api as db_api
  61. from neutron_lib.db import utils as db_utils
  62. from neutron_lib import exceptions as n_exc
  63. from neutron_lib.exceptions import allowedaddresspairs as addr_exc
  64. from neutron_lib.exceptions import l3 as l3_exc
  65. from neutron_lib.exceptions import port_security as psec_exc
  66. from neutron_lib.plugins import directory
  67. from neutron_lib.plugins import utils as plugin_utils
  68. from neutron_lib import rpc as n_rpc
  69. from neutron_lib.services.qos import constants as qos_consts
  70. from neutron_lib.utils import helpers
  71. from neutron_lib.utils import net as nl_net_utils
  72. from vmware_nsx.api_replay import utils as api_replay_utils
  73. from vmware_nsx.common import availability_zones as nsx_com_az
  74. from vmware_nsx.common import exceptions as nsx_exc
  75. from vmware_nsx.common import locking
  76. from vmware_nsx.common import nsx_constants
  77. from vmware_nsx.common import utils
  78. from vmware_nsx.db import db as nsx_db
  79. from vmware_nsx.db import extended_security_group as extended_sec
  80. from vmware_nsx.db import extended_security_group_rule as extend_sg_rule
  81. from vmware_nsx.db import maclearning as mac_db
  82. from vmware_nsx.db import nsx_portbindings_db as pbin_db
  83. from vmware_nsx.extensions import advancedserviceproviders as as_providers
  84. from vmware_nsx.extensions import maclearning as mac_ext
  85. from vmware_nsx.extensions import providersecuritygroup as provider_sg
  86. from vmware_nsx.extensions import secgroup_rule_local_ip_prefix as sg_prefix
  87. from vmware_nsx.plugins.common import plugin
  88. from vmware_nsx.services.qos.common import utils as qos_com_utils
  89. from vmware_nsx.services.vpnaas.common_v3 import ipsec_utils
  90. from vmware_nsxlib.v3 import exceptions as nsx_lib_exc
  91. from vmware_nsxlib.v3 import nsx_constants as nsxlib_consts
  92. from vmware_nsxlib.v3 import utils as nsxlib_utils
  93. LOG = logging.getLogger(__name__)
  94. @decorator.decorator
  95. def api_replay_mode_wrapper(f, *args, **kwargs):
  96. if cfg.CONF.api_replay_mode:
  97. # NOTE(arosen): the mock.patch here is needed for api_replay_mode
  98. with mock.patch("neutron_lib.plugins.utils._fixup_res_dict",
  99. side_effect=api_replay_utils._fixup_res_dict):
  100. return f(*args, **kwargs)
  101. else:
  102. return f(*args, **kwargs)
  103. # NOTE(asarfaty): the order of inheritance here is important. in order for the
  104. # QoS notification to work, the AgentScheduler init must be called first
  105. # NOTE(arosen): same is true with the ExtendedSecurityGroupPropertiesMixin
  106. # this needs to be above securitygroups_db.SecurityGroupDbMixin.
  107. # FIXME(arosen): we can solve this inheritance order issue by just mixining in
  108. # the classes into a new class to handle the order correctly.
  109. class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
  110. addr_pair_db.AllowedAddressPairsMixin,
  111. plugin.NsxPluginBase,
  112. extended_sec.ExtendedSecurityGroupPropertiesMixin,
  113. pbin_db.NsxPortBindingMixin,
  114. extend_sg_rule.ExtendedSecurityGroupRuleMixin,
  115. securitygroups_db.SecurityGroupDbMixin,
  116. external_net_db.External_net_db_mixin,
  117. extraroute_db.ExtraRoute_db_mixin,
  118. router_az_db.RouterAvailabilityZoneMixin,
  119. l3_gwmode_db.L3_NAT_db_mixin,
  120. portbindings_db.PortBindingMixin,
  121. portsecurity_db.PortSecurityDbMixin,
  122. extradhcpopt_db.ExtraDhcpOptMixin,
  123. dns_db.DNSDbMixin,
  124. vlantransparent_db.Vlantransparent_db_mixin,
  125. mac_db.MacLearningDbMixin,
  126. l3_attrs_db.ExtraAttributesMixin,
  127. nsx_com_az.NSXAvailabilityZonesPluginCommon):
  128. """Common methods for NSX-V3 plugins (NSX-V3 & Policy)"""
  129. def __init__(self):
  130. super(NsxPluginV3Base, self).__init__()
  131. self._network_vlans = plugin_utils.parse_network_vlan_ranges(
  132. self._get_conf_attr('network_vlan_ranges'))
  133. self._native_dhcp_enabled = False
  134. self.start_rpc_listeners_called = False
  135. def _init_native_dhcp(self):
  136. if not self.nsxlib:
  137. self._native_dhcp_enabled = False
  138. return
  139. self._native_dhcp_enabled = True
  140. for az in self.get_azs_list():
  141. if not az._native_dhcp_profile_uuid:
  142. LOG.error("Unable to retrieve DHCP Profile %s for "
  143. "availability zone %s, "
  144. "native DHCP service is not supported",
  145. az.name, az.dhcp_profile)
  146. self._native_dhcp_enabled = False
  147. def _init_native_metadata(self):
  148. if not self.nsxlib:
  149. return
  150. for az in self.get_azs_list():
  151. if not az._native_md_proxy_uuid:
  152. LOG.error("Unable to retrieve Metadata Proxy %s for "
  153. "availability zone %s, "
  154. "native metadata service is not supported",
  155. az.name, az.metadata_proxy)
  156. def _extend_fault_map(self):
  157. """Extends the Neutron Fault Map.
  158. Exceptions specific to the NSX Plugin are mapped to standard
  159. HTTP Exceptions.
  160. """
  161. faults.FAULT_MAP.update({nsx_lib_exc.InvalidInput:
  162. webob.exc.HTTPBadRequest,
  163. nsx_lib_exc.ServiceClusterUnavailable:
  164. webob.exc.HTTPServiceUnavailable,
  165. nsx_lib_exc.ClientCertificateNotTrusted:
  166. webob.exc.HTTPBadRequest,
  167. nsx_exc.SecurityGroupMaximumCapacityReached:
  168. webob.exc.HTTPBadRequest,
  169. nsx_lib_exc.NsxLibInvalidInput:
  170. webob.exc.HTTPBadRequest,
  171. nsx_exc.NsxENSPortSecurity:
  172. webob.exc.HTTPBadRequest,
  173. nsx_exc.NsxPluginTemporaryError:
  174. webob.exc.HTTPServiceUnavailable,
  175. nsx_lib_exc.TooManyRequests:
  176. webob.exc.HTTPServiceUnavailable
  177. })
  178. def _get_conf_attr(self, attr):
  179. plugin_cfg = getattr(cfg.CONF, self.cfg_group)
  180. return getattr(plugin_cfg, attr)
  181. def _setup_rpc(self):
  182. """Should be implemented by each plugin"""
  183. pass
  184. @property
  185. def support_external_port_tagging(self):
  186. # oslo_messaging_notifications must be defined for this to work
  187. if (cfg.CONF.oslo_messaging_notifications.driver and
  188. self._get_conf_attr('support_nsx_port_tagging')):
  189. return True
  190. return False
  191. def update_port_nsx_tags(self, context, port_id, tags, is_delete=False):
  192. """Can be implemented by each plugin to update the backend port tags"""
  193. pass
  194. def start_rpc_listeners(self):
  195. if self.start_rpc_listeners_called:
  196. # If called more than once - we should not create it again
  197. return self.conn.consume_in_threads()
  198. self._setup_rpc()
  199. self.topic = topics.PLUGIN
  200. self.conn = n_rpc.Connection()
  201. self.conn.create_consumer(self.topic, self.endpoints, fanout=False)
  202. self.conn.create_consumer(topics.REPORTS,
  203. [agents_db.AgentExtRpcCallback()],
  204. fanout=False)
  205. self.start_rpc_listeners_called = True
  206. if self.support_external_port_tagging:
  207. self.start_tagging_rpc_listener()
  208. return self.conn.consume_in_threads()
  209. def start_tagging_rpc_listener(self):
  210. # Add listener for tags plugin notifications
  211. transport = oslo_messaging.get_notification_transport(cfg.CONF)
  212. targets = [oslo_messaging.Target(
  213. topic=cfg.CONF.oslo_messaging_notifications.topics[0])]
  214. endpoints = [TagsCallbacks()]
  215. pool = "tags-listeners"
  216. server = oslo_messaging.get_notification_listener(transport, targets,
  217. endpoints, pool=pool)
  218. server.start()
  219. server.wait()
  220. def _translate_external_tag(self, external_tag, port_id):
  221. tag_parts = external_tag.split(':')
  222. if len(tag_parts) != 2:
  223. LOG.warning("Skipping tag %s for port %s: wrong format",
  224. external_tag, port_id)
  225. else:
  226. return {'scope': tag_parts[0][:nsxlib_utils.MAX_RESOURCE_TYPE_LEN],
  227. 'tag': tag_parts[1][:nsxlib_utils.MAX_TAG_LEN]}
  228. def _translate_external_tags(self, external_tags, port_id):
  229. new_tags = []
  230. for tag in external_tags:
  231. new_tag = self._translate_external_tag(tag, port_id)
  232. if new_tag:
  233. new_tags.append(new_tag)
  234. return new_tags
  235. def get_external_tags_for_port(self, context, port_id):
  236. tags_plugin = directory.get_plugin(tagging.TAG_PLUGIN_TYPE)
  237. if tags_plugin:
  238. extra_tags = tags_plugin.get_tags(context, 'ports', port_id)
  239. return self._translate_external_tags(extra_tags['tags'], port_id)
  240. def _get_interface_subnet(self, context, interface_info):
  241. is_port, is_sub = self._validate_interface_info(interface_info)
  242. subnet_id = None
  243. if is_sub:
  244. subnet_id = interface_info.get('subnet_id')
  245. if not subnet_id:
  246. port_id = interface_info['port_id']
  247. port = self.get_port(context, port_id)
  248. if 'fixed_ips' in port and port['fixed_ips']:
  249. if len(port['fixed_ips']) > 1:
  250. # This should never happen since router interface is per
  251. # IP version, and we allow single fixed ip per ip version
  252. return
  253. subnet_id = port['fixed_ips'][0]['subnet_id']
  254. if subnet_id:
  255. return self.get_subnet(context, subnet_id)
  256. def _get_interface_network_id(self, context, interface_info, subnet=None):
  257. if subnet:
  258. return subnet['network_id']
  259. is_port, is_sub = self._validate_interface_info(interface_info)
  260. if is_port:
  261. net_id = self.get_port(context,
  262. interface_info['port_id'])['network_id']
  263. elif is_sub:
  264. net_id = self.get_subnet(context,
  265. interface_info['subnet_id'])['network_id']
  266. return net_id
  267. def _fix_sg_rule_dict_ips(self, sg_rule):
  268. # 0.0.0.0/# and ::/ are not valid entries for local and remote so we
  269. # need to change this to None
  270. if (sg_rule.get('remote_ip_prefix') and
  271. (sg_rule['remote_ip_prefix'].startswith('0.0.0.0/') or
  272. sg_rule['remote_ip_prefix'].startswith('::/'))):
  273. sg_rule['remote_ip_prefix'] = None
  274. if (sg_rule.get(sg_prefix.LOCAL_IP_PREFIX) and
  275. validators.is_attr_set(sg_rule[sg_prefix.LOCAL_IP_PREFIX]) and
  276. (sg_rule[sg_prefix.LOCAL_IP_PREFIX].startswith('0.0.0.0/') or
  277. sg_rule[sg_prefix.LOCAL_IP_PREFIX].startswith('::/'))):
  278. sg_rule[sg_prefix.LOCAL_IP_PREFIX] = None
  279. def _validate_interface_address_scope(self, context, router_db,
  280. interface_subnet):
  281. gw_network_id = (router_db.gw_port.network_id if router_db.gw_port
  282. else None)
  283. if not router_db.enable_snat and gw_network_id:
  284. self._validate_address_scope_for_router_interface(
  285. context.elevated(), router_db.id, gw_network_id,
  286. interface_subnet['id'], subnet=interface_subnet)
  287. def _validate_address_pairs(self, address_pairs):
  288. for pair in address_pairs:
  289. ip = pair.get('ip_address')
  290. # Validate ipv4 cidrs (No limitation on ipv6):
  291. if ':' not in ip:
  292. if len(ip.split('/')) > 1 and ip.split('/')[1] != '32':
  293. LOG.error("cidr %s is not supported in allowed address "
  294. "pairs", ip)
  295. raise nsx_exc.InvalidIPAddress(ip_address=ip)
  296. def _validate_number_of_address_pairs(self, port):
  297. address_pairs = port.get(addr_apidef.ADDRESS_PAIRS)
  298. num_allowed_on_backend = nsxlib_consts.NUM_ALLOWED_IP_ADDRESSES
  299. # Counting existing ports to take into account. If no fixed ips
  300. # are defined - we set it to 3 in order to reserve 2 fixed and another
  301. # for DHCP.
  302. existing_fixed_ips = len(port.get('fixed_ips', []))
  303. if existing_fixed_ips == 0:
  304. existing_fixed_ips = 3
  305. else:
  306. existing_fixed_ips += 1
  307. if address_pairs:
  308. max_addr_pairs = num_allowed_on_backend - existing_fixed_ips
  309. if len(address_pairs) > max_addr_pairs:
  310. err_msg = (_("Maximum of %(max)s address pairs can be defined "
  311. "for this port on the NSX backend") %
  312. {'max': max_addr_pairs})
  313. raise n_exc.InvalidInput(error_message=err_msg)
  314. def _create_port_address_pairs(self, context, port_data):
  315. (port_security, has_ip) = self._determine_port_security_and_has_ip(
  316. context, port_data)
  317. address_pairs = port_data.get(addr_apidef.ADDRESS_PAIRS)
  318. if validators.is_attr_set(address_pairs):
  319. if not port_security:
  320. raise addr_exc.AddressPairAndPortSecurityRequired()
  321. else:
  322. self._validate_address_pairs(address_pairs)
  323. self._validate_number_of_address_pairs(port_data)
  324. self._process_create_allowed_address_pairs(context, port_data,
  325. address_pairs)
  326. else:
  327. port_data[addr_apidef.ADDRESS_PAIRS] = []
  328. def _provider_sgs_specified(self, port_data):
  329. # checks if security groups were updated adding/modifying
  330. # security groups, port security is set and port has ip
  331. provider_sgs_specified = (validators.is_attr_set(
  332. port_data.get(provider_sg.PROVIDER_SECURITYGROUPS)) and
  333. port_data.get(provider_sg.PROVIDER_SECURITYGROUPS) != [])
  334. return provider_sgs_specified
  335. def _create_port_preprocess_security(
  336. self, context, port, port_data, neutron_db, is_ens_tz_port):
  337. (port_security, has_ip) = self._determine_port_security_and_has_ip(
  338. context, port_data)
  339. port_data[psec.PORTSECURITY] = port_security
  340. # No port security is allowed if the port belongs to an ENS TZ
  341. if (port_security and is_ens_tz_port and
  342. not self._ens_psec_supported()):
  343. raise nsx_exc.NsxENSPortSecurity()
  344. self._process_port_port_security_create(
  345. context, port_data, neutron_db)
  346. # allowed address pair checks
  347. self._create_port_address_pairs(context, port_data)
  348. if port_security and has_ip:
  349. self._ensure_default_security_group_on_port(context, port)
  350. (sgids, psgids) = self._get_port_security_groups_lists(
  351. context, port)
  352. elif (self._check_update_has_security_groups({'port': port_data}) or
  353. self._provider_sgs_specified(port_data) or
  354. self._get_provider_security_groups_on_port(context, port)):
  355. LOG.error("Port has conflicting port security status and "
  356. "security groups")
  357. raise psec_exc.PortSecurityAndIPRequiredForSecurityGroups()
  358. else:
  359. sgids = psgids = []
  360. port_data[ext_sg.SECURITYGROUPS] = (
  361. self._get_security_groups_on_port(context, port))
  362. return port_security, has_ip, sgids, psgids
  363. def _should_validate_port_sec_on_update_port(self, port_data):
  364. # Need to determine if we skip validations for port security.
  365. # This is the edge case when the subnet is deleted.
  366. # This should be called prior to deleting the fixed ip from the
  367. # port data
  368. for fixed_ip in port_data.get('fixed_ips', []):
  369. if 'delete_subnet' in fixed_ip:
  370. return False
  371. return True
  372. def _update_port_preprocess_security(
  373. self, context, port, id, updated_port, is_ens_tz_port,
  374. validate_port_sec=True, direct_vnic_type=False):
  375. delete_addr_pairs = self._check_update_deletes_allowed_address_pairs(
  376. port)
  377. has_addr_pairs = self._check_update_has_allowed_address_pairs(port)
  378. has_security_groups = self._check_update_has_security_groups(port)
  379. delete_security_groups = self._check_update_deletes_security_groups(
  380. port)
  381. # populate port_security setting
  382. port_data = port['port']
  383. if psec.PORTSECURITY not in port_data:
  384. updated_port[psec.PORTSECURITY] = \
  385. self._get_port_security_binding(context, id)
  386. has_ip = self._ip_on_port(updated_port)
  387. # validate port security and allowed address pairs
  388. if not updated_port[psec.PORTSECURITY]:
  389. # has address pairs in request
  390. if has_addr_pairs:
  391. raise addr_exc.AddressPairAndPortSecurityRequired()
  392. elif not delete_addr_pairs:
  393. # check if address pairs are in db
  394. updated_port[addr_apidef.ADDRESS_PAIRS] = (
  395. self.get_allowed_address_pairs(context, id))
  396. if updated_port[addr_apidef.ADDRESS_PAIRS]:
  397. raise addr_exc.AddressPairAndPortSecurityRequired()
  398. if delete_addr_pairs or has_addr_pairs:
  399. self._validate_address_pairs(
  400. updated_port[addr_apidef.ADDRESS_PAIRS])
  401. # delete address pairs and read them in
  402. self._delete_allowed_address_pairs(context, id)
  403. self._process_create_allowed_address_pairs(
  404. context, updated_port,
  405. updated_port[addr_apidef.ADDRESS_PAIRS])
  406. if updated_port[psec.PORTSECURITY] and psec.PORTSECURITY in port_data:
  407. # No port security is allowed if the port belongs to an ENS TZ
  408. if is_ens_tz_port and not self._ens_psec_supported():
  409. raise nsx_exc.NsxENSPortSecurity()
  410. # No port security is allowed if the port has a direct vnic type
  411. if direct_vnic_type:
  412. err_msg = _("Security features are not supported for "
  413. "ports with direct/direct-physical VNIC type")
  414. raise n_exc.InvalidInput(error_message=err_msg)
  415. # checks if security groups were updated adding/modifying
  416. # security groups, port security is set and port has ip
  417. provider_sgs_specified = self._provider_sgs_specified(updated_port)
  418. if (validate_port_sec and
  419. not (has_ip and updated_port[psec.PORTSECURITY])):
  420. if has_security_groups or provider_sgs_specified:
  421. LOG.error("Port has conflicting port security status and "
  422. "security groups")
  423. raise psec_exc.PortSecurityAndIPRequiredForSecurityGroups()
  424. # Update did not have security groups passed in. Check
  425. # that port does not have any security groups already on it.
  426. filters = {'port_id': [id]}
  427. security_groups = (
  428. super(NsxPluginV3Base, self)._get_port_security_group_bindings(
  429. context, filters)
  430. )
  431. if security_groups and not delete_security_groups:
  432. raise psec_exc.PortSecurityPortHasSecurityGroup()
  433. if delete_security_groups or has_security_groups:
  434. # delete the port binding and read it with the new rules.
  435. self._delete_port_security_group_bindings(context, id)
  436. sgids = self._get_security_groups_on_port(context, port)
  437. self._process_port_create_security_group(context, updated_port,
  438. sgids)
  439. if psec.PORTSECURITY in port['port']:
  440. self._process_port_port_security_update(
  441. context, port['port'], updated_port)
  442. return updated_port
  443. def _validate_create_network(self, context, net_data):
  444. """Validate the parameters of the new network to be created
  445. This method includes general validations that does not depend on
  446. provider attributes, or plugin specific configurations
  447. """
  448. external = net_data.get(extnet_apidef.EXTERNAL)
  449. is_external_net = validators.is_attr_set(external) and external
  450. with_qos = validators.is_attr_set(
  451. net_data.get(qos_consts.QOS_POLICY_ID))
  452. if with_qos:
  453. self._validate_qos_policy_id(
  454. context, net_data.get(qos_consts.QOS_POLICY_ID))
  455. if is_external_net:
  456. raise nsx_exc.QoSOnExternalNet()
  457. def _validate_update_network(self, context, net_id, original_net,
  458. net_data):
  459. """Validate the updated parameters of a network
  460. This method includes general validations that does not depend on
  461. provider attributes, or plugin specific configurations
  462. """
  463. extern_net = self._network_is_external(context, net_id)
  464. with_qos = validators.is_attr_set(
  465. net_data.get(qos_consts.QOS_POLICY_ID))
  466. # Do not allow QoS on external networks
  467. if with_qos:
  468. if extern_net:
  469. raise nsx_exc.QoSOnExternalNet()
  470. self._validate_qos_policy_id(
  471. context, net_data.get(qos_consts.QOS_POLICY_ID))
  472. # Do not support changing external/non-external networks
  473. if (extnet_apidef.EXTERNAL in net_data and
  474. net_data[extnet_apidef.EXTERNAL] != extern_net):
  475. err_msg = _("Cannot change the router:external flag of a network")
  476. raise n_exc.InvalidInput(error_message=err_msg)
  477. is_ens_net = self._is_ens_tz_net(context, net_id)
  478. if is_ens_net:
  479. self._assert_on_ens_with_qos(net_data)
  480. def _assert_on_illegal_port_with_qos(self, device_owner):
  481. # Prevent creating/update port with QoS policy
  482. # on router-interface/network-dhcp ports.
  483. if ((device_owner == l3_db.DEVICE_OWNER_ROUTER_INTF or
  484. device_owner == constants.DEVICE_OWNER_DHCP)):
  485. err_msg = _("Unable to create or update %s port with a QoS "
  486. "policy") % device_owner
  487. LOG.warning(err_msg)
  488. raise n_exc.InvalidInput(error_message=err_msg)
  489. def _assert_on_external_net_with_compute(self, port_data):
  490. # Prevent creating port with device owner prefix 'compute'
  491. # on external networks.
  492. device_owner = port_data.get('device_owner')
  493. if (device_owner is not None and
  494. device_owner.startswith(constants.DEVICE_OWNER_COMPUTE_PREFIX)):
  495. err_msg = _("Unable to update/create a port with an external "
  496. "network")
  497. LOG.warning(err_msg)
  498. raise n_exc.InvalidInput(error_message=err_msg)
  499. def _validate_ens_create_port(self, context, port_data):
  500. if self._ens_qos_supported():
  501. return
  502. qos_selected = validators.is_attr_set(port_data.get(
  503. qos_consts.QOS_POLICY_ID))
  504. if qos_selected:
  505. err_msg = _("Cannot configure QOS on ENS networks")
  506. raise n_exc.InvalidInput(error_message=err_msg)
  507. def _assert_on_port_admin_state(self, port_data, device_owner):
  508. """Do not allow changing the admin state of some ports"""
  509. if (device_owner == l3_db.DEVICE_OWNER_ROUTER_INTF or
  510. device_owner == l3_db.DEVICE_OWNER_ROUTER_GW):
  511. if port_data.get("admin_state_up") is False:
  512. err_msg = _("admin_state_up=False router ports are not "
  513. "supported")
  514. LOG.warning(err_msg)
  515. raise n_exc.InvalidInput(error_message=err_msg)
  516. def _validate_max_ips_per_port(self, context, fixed_ip_list, device_owner):
  517. """Validate the number of fixed ips on a port
  518. Do not allow multiple ip addresses on a port since the nsx backend
  519. cannot add multiple static dhcp bindings with the same port
  520. """
  521. if (device_owner and
  522. nl_net_utils.is_port_trusted({'device_owner': device_owner})):
  523. return
  524. if not validators.is_attr_set(fixed_ip_list):
  525. return
  526. msg = _('Exceeded maximum amount of fixed ips per port and ip version')
  527. if len(fixed_ip_list) > 2:
  528. raise n_exc.InvalidInput(error_message=msg)
  529. if len(fixed_ip_list) < 2:
  530. return
  531. def get_fixed_ip_version(i):
  532. if 'ip_address' in fixed_ip_list[i]:
  533. return netaddr.IPAddress(
  534. fixed_ip_list[i]['ip_address']).version
  535. if 'subnet_id' in fixed_ip_list[i]:
  536. subnet = self.get_subnet(context.elevated(),
  537. fixed_ip_list[i]['subnet_id'])
  538. return subnet['ip_version']
  539. ipver1 = get_fixed_ip_version(0)
  540. ipver2 = get_fixed_ip_version(1)
  541. if ipver1 and ipver2 and ipver1 != ipver2:
  542. # One fixed IP is allowed for each IP version
  543. return
  544. raise n_exc.InvalidInput(error_message=msg)
  545. def _get_subnets_for_fixed_ips_on_port(self, context, port_data):
  546. # get the subnet id from the fixed ips of the port
  547. if 'fixed_ips' in port_data and port_data['fixed_ips']:
  548. subnet_ids = (fixed_ip['subnet_id']
  549. for fixed_ip in port_data['fixed_ips'])
  550. return (self._get_subnet(context.elevated(), subnet_id)
  551. for subnet_id in subnet_ids)
  552. return []
  553. def _validate_create_port(self, context, port_data):
  554. self._validate_max_ips_per_port(context,
  555. port_data.get('fixed_ips', []),
  556. port_data.get('device_owner'))
  557. is_external_net = self._network_is_external(
  558. context, port_data['network_id'])
  559. qos_selected = validators.is_attr_set(port_data.get(
  560. qos_consts.QOS_POLICY_ID))
  561. device_owner = port_data.get('device_owner')
  562. # QoS validations
  563. if qos_selected:
  564. self._validate_qos_policy_id(
  565. context, port_data.get(qos_consts.QOS_POLICY_ID))
  566. self._assert_on_illegal_port_with_qos(device_owner)
  567. if is_external_net:
  568. raise nsx_exc.QoSOnExternalNet()
  569. is_ens_tz_port = self._is_ens_tz_port(context, port_data)
  570. if is_ens_tz_port:
  571. self._validate_ens_create_port(context, port_data)
  572. # External network validations:
  573. if is_external_net:
  574. self._assert_on_external_net_with_compute(port_data)
  575. self._assert_on_port_admin_state(port_data, device_owner)
  576. self._validate_extra_dhcp_options(port_data.get(ext_edo.EXTRADHCPOPTS))
  577. def _assert_on_vpn_port_change(self, port_data):
  578. if port_data['device_owner'] == ipsec_utils.VPN_PORT_OWNER:
  579. msg = _('Can not update/delete VPNaaS port %s') % port_data['id']
  580. raise n_exc.InvalidInput(error_message=msg)
  581. def _assert_on_lb_port_fixed_ip_change(self, port_data, orig_dev_own):
  582. if orig_dev_own == constants.DEVICE_OWNER_LOADBALANCERV2:
  583. if "fixed_ips" in port_data and port_data["fixed_ips"]:
  584. msg = _('Can not update Loadbalancer port with fixed IP')
  585. raise n_exc.InvalidInput(error_message=msg)
  586. def _assert_on_device_owner_change(self, port_data, orig_dev_own):
  587. """Prevent illegal device owner modifications
  588. """
  589. if orig_dev_own == constants.DEVICE_OWNER_LOADBALANCERV2:
  590. if ("allowed_address_pairs" in port_data and
  591. port_data["allowed_address_pairs"]):
  592. msg = _('Loadbalancer port can not be updated '
  593. 'with address pairs')
  594. raise n_exc.InvalidInput(error_message=msg)
  595. if 'device_owner' not in port_data:
  596. return
  597. new_dev_own = port_data['device_owner']
  598. if new_dev_own == orig_dev_own:
  599. return
  600. err_msg = (_("Changing port device owner '%(orig)s' to '%(new)s' is "
  601. "not allowed") % {'orig': orig_dev_own,
  602. 'new': new_dev_own})
  603. # Do not allow changing nova <-> neutron device owners
  604. if ((orig_dev_own.startswith(constants.DEVICE_OWNER_COMPUTE_PREFIX) and
  605. new_dev_own.startswith(constants.DEVICE_OWNER_NETWORK_PREFIX)) or
  606. (orig_dev_own.startswith(constants.DEVICE_OWNER_NETWORK_PREFIX) and
  607. new_dev_own.startswith(constants.DEVICE_OWNER_COMPUTE_PREFIX))):
  608. raise n_exc.InvalidInput(error_message=err_msg)
  609. # Do not allow removing the device owner in some cases
  610. if orig_dev_own == constants.DEVICE_OWNER_DHCP:
  611. raise n_exc.InvalidInput(error_message=err_msg)
  612. def _assert_on_port_sec_change(self, port_data, device_owner):
  613. """Do not allow enabling port security/mac learning of some ports
  614. Trusted ports are created with port security and mac learning disabled
  615. in neutron, and it should not change.
  616. """
  617. if nl_net_utils.is_port_trusted({'device_owner': device_owner}):
  618. if port_data.get(psec.PORTSECURITY) is True:
  619. err_msg = _("port_security_enabled=True is not supported for "
  620. "trusted ports")
  621. LOG.warning(err_msg)
  622. raise n_exc.InvalidInput(error_message=err_msg)
  623. mac_learning = port_data.get(mac_ext.MAC_LEARNING)
  624. if (validators.is_attr_set(mac_learning) and mac_learning is True):
  625. err_msg = _("mac_learning_enabled=True is not supported for "
  626. "trusted ports")
  627. LOG.warning(err_msg)
  628. raise n_exc.InvalidInput(error_message=err_msg)
  629. def _validate_update_port(self, context, id, original_port, port_data):
  630. qos_selected = validators.is_attr_set(port_data.get
  631. (qos_consts.QOS_POLICY_ID))
  632. is_external_net = self._network_is_external(
  633. context, original_port['network_id'])
  634. device_owner = (port_data['device_owner']
  635. if 'device_owner' in port_data
  636. else original_port.get('device_owner'))
  637. # QoS validations
  638. if qos_selected:
  639. self._validate_qos_policy_id(
  640. context, port_data.get(qos_consts.QOS_POLICY_ID))
  641. if is_external_net:
  642. raise nsx_exc.QoSOnExternalNet()
  643. self._assert_on_illegal_port_with_qos(device_owner)
  644. is_ens_tz_port = self._is_ens_tz_port(context, original_port)
  645. if is_ens_tz_port and not self._ens_qos_supported():
  646. err_msg = _("Cannot configure QOS on ENS networks")
  647. raise n_exc.InvalidInput(error_message=err_msg)
  648. # External networks validations:
  649. if is_external_net:
  650. self._assert_on_external_net_with_compute(port_data)
  651. # Device owner validations:
  652. orig_dev_owner = original_port.get('device_owner')
  653. self._assert_on_device_owner_change(port_data, orig_dev_owner)
  654. self._assert_on_port_admin_state(port_data, device_owner)
  655. self._assert_on_port_sec_change(port_data, device_owner)
  656. self._validate_max_ips_per_port(context,
  657. port_data.get('fixed_ips', []),
  658. device_owner)
  659. self._validate_number_of_address_pairs(port_data)
  660. self._assert_on_vpn_port_change(original_port)
  661. self._assert_on_lb_port_fixed_ip_change(port_data, orig_dev_owner)
  662. self._validate_extra_dhcp_options(port_data.get(ext_edo.EXTRADHCPOPTS))
  663. def _get_dhcp_port_name(self, net_name, net_id):
  664. return utils.get_name_and_uuid('%s-%s' % ('dhcp',
  665. net_name or 'network'),
  666. net_id)
  667. def _build_port_name(self, context, port_data):
  668. device_owner = port_data.get('device_owner')
  669. device_id = port_data.get('device_id')
  670. if device_owner == l3_db.DEVICE_OWNER_ROUTER_INTF and device_id:
  671. router = self._get_router(context, device_id)
  672. name = utils.get_name_and_uuid(
  673. router['name'] or 'router', port_data['id'], tag='port')
  674. elif device_owner == constants.DEVICE_OWNER_DHCP:
  675. network = self.get_network(context, port_data['network_id'])
  676. name = self._get_dhcp_port_name(network['name'],
  677. network['id'])
  678. elif device_owner.startswith(constants.DEVICE_OWNER_COMPUTE_PREFIX):
  679. name = utils.get_name_and_uuid(
  680. port_data['name'] or 'instance-port', port_data['id'])
  681. else:
  682. name = port_data['name']
  683. return name
  684. def _validate_external_net_create(self, net_data, default_tier0_router,
  685. tier0_validator=None):
  686. """Validate external network configuration
  687. Returns a tuple of:
  688. - Boolean is provider network (always True)
  689. - Network type (always L3_EXT)
  690. - tier 0 router id
  691. - vlan id
  692. """
  693. if not validators.is_attr_set(net_data.get(pnet.PHYSICAL_NETWORK)):
  694. tier0_uuid = default_tier0_router
  695. else:
  696. tier0_uuid = net_data[pnet.PHYSICAL_NETWORK]
  697. if ((validators.is_attr_set(net_data.get(pnet.NETWORK_TYPE)) and
  698. net_data.get(pnet.NETWORK_TYPE) != utils.NetworkTypes.L3_EXT and
  699. net_data.get(pnet.NETWORK_TYPE) != utils.NetworkTypes.LOCAL) or
  700. validators.is_attr_set(net_data.get(pnet.SEGMENTATION_ID))):
  701. msg = (_("External network cannot be created with %s provider "
  702. "network or segmentation id") %
  703. net_data.get(pnet.NETWORK_TYPE))
  704. raise n_exc.InvalidInput(error_message=msg)
  705. if tier0_validator:
  706. tier0_validator(tier0_uuid)
  707. return (True, utils.NetworkTypes.L3_EXT, tier0_uuid, 0)
  708. def _extend_network_dict_provider(self, context, network, bindings=None):
  709. """Add network provider fields to the network dict from the DB"""
  710. if 'id' not in network:
  711. return
  712. if not bindings:
  713. bindings = nsx_db.get_network_bindings(context.session,
  714. network['id'])
  715. # With NSX plugin, "normal" overlay networks will have no binding
  716. if bindings:
  717. # Network came in through provider networks API
  718. network[pnet.NETWORK_TYPE] = bindings[0].binding_type
  719. network[pnet.PHYSICAL_NETWORK] = bindings[0].phy_uuid
  720. network[pnet.SEGMENTATION_ID] = bindings[0].vlan_id
  721. def _extend_get_network_dict_provider(self, context, network):
  722. self._extend_network_dict_provider(context, network)
  723. network[qos_consts.QOS_POLICY_ID] = (qos_com_utils.
  724. get_network_policy_id(context, network['id']))
  725. def _translate_net_db_2_dict(self, context, net_db):
  726. net_dict = self._make_network_dict(net_db, context=context)
  727. self._extend_get_network_dict_provider(context, net_dict)
  728. return net_dict
  729. def get_network(self, context, id, fields=None):
  730. with db_api.CONTEXT_READER.using(context):
  731. # Get network from Neutron database
  732. network = self._get_network(context, id)
  733. # Don't do field selection here otherwise we won't be able to add
  734. # provider networks fields
  735. net = self._translate_net_db_2_dict(context, network)
  736. return db_utils.resource_fields(net, fields)
  737. def get_networks(self, context, filters=None, fields=None,
  738. sorts=None, limit=None, marker=None,
  739. page_reverse=False):
  740. # Get networks from Neutron database
  741. filters = filters or {}
  742. with db_api.CONTEXT_READER.using(context):
  743. networks = super(NsxPluginV3Base, self).get_networks(
  744. context, filters, fields, sorts,
  745. limit, marker, page_reverse)
  746. # Add provider network fields
  747. for net in networks:
  748. self._extend_get_network_dict_provider(context, net)
  749. return (networks if not fields else
  750. [db_utils.resource_fields(network,
  751. fields) for network in networks])
  752. def _assert_on_ens_with_qos(self, net_data):
  753. if self._ens_qos_supported():
  754. return
  755. qos_id = net_data.get(qos_consts.QOS_POLICY_ID)
  756. if validators.is_attr_set(qos_id):
  757. err_msg = _("Cannot configure QOS on ENS networks")
  758. raise n_exc.InvalidInput(error_message=err_msg)
  759. def _get_port_qos_policy_id(self, context, original_port,
  760. updated_port):
  761. """Return the QoS policy Id of a port that is being created/updated
  762. Return the QoS policy assigned directly to the port (after update or
  763. originally), or the policy of the network, if it is a compute port that
  764. should inherit it.
  765. original_port: the neutron port data before this update
  766. (or None in a case of a new port creation)
  767. updated_ports: the modified fields of this port
  768. (or all th attributes of the new port)
  769. """
  770. orig_compute = False
  771. if original_port:
  772. orig_compute = original_port.get('device_owner', '').startswith(
  773. constants.DEVICE_OWNER_COMPUTE_PREFIX)
  774. updated_compute = updated_port.get('device_owner', '').startswith(
  775. constants.DEVICE_OWNER_COMPUTE_PREFIX)
  776. is_new_compute = updated_compute and not orig_compute
  777. qos_policy_id = None
  778. if qos_consts.QOS_POLICY_ID in updated_port:
  779. qos_policy_id = updated_port[qos_consts.QOS_POLICY_ID]
  780. elif original_port:
  781. # Look for the original QoS policy of this port
  782. qos_policy_id = qos_com_utils.get_port_policy_id(
  783. context, original_port['id'])
  784. # If the port is now a 'compute' port (attached to a vm) and
  785. # Qos policy was not configured on the port directly,
  786. # try to take it from the ports network
  787. if qos_policy_id is None and is_new_compute:
  788. # check if the network of this port has a policy
  789. net_id = (original_port.get('network_id') if original_port
  790. else updated_port.get('network_id'))
  791. qos_policy_id = qos_com_utils.get_network_policy_id(
  792. context, net_id)
  793. return qos_policy_id
  794. def _ens_psec_supported(self):
  795. """Should be implemented by each plugin"""
  796. pass
  797. def _ens_qos_supported(self):
  798. """Should be implemented by each plugin"""
  799. pass
  800. def _has_native_dhcp_metadata(self):
  801. """Should be implemented by each plugin"""
  802. pass
  803. def _get_nsx_net_tz_id(self, nsx_net):
  804. """Should be implemented by each plugin"""
  805. pass
  806. def _get_network_nsx_id(self, context, neutron_id):
  807. """Should be implemented by each plugin"""
  808. pass
  809. def _get_tier0_uplink_cidrs(self, tier0_id):
  810. """Should be implemented by each plugin"""
  811. pass
  812. def _validate_ens_net_portsecurity(self, net_data):
  813. """Validate/Update the port security of the new network for ENS TZ
  814. Should be implemented by the plugin if necessary
  815. """
  816. pass
  817. def _is_ens_tz_net(self, context, net_id):
  818. """Return True if the network is based on an END transport zone"""
  819. tz_id = self._get_net_tz(context, net_id)
  820. if tz_id:
  821. # Check the mode of this TZ
  822. return self._is_ens_tz(tz_id)
  823. return False
  824. def _is_ens_tz_port(self, context, port_data):
  825. # Check the host-switch-mode of the TZ connected to the ports network
  826. return self._is_ens_tz_net(context, port_data['network_id'])
  827. def _is_overlay_network(self, context, network_id):
  828. """Should be implemented by each plugin"""
  829. pass
  830. def _generate_segment_id(self, context, physical_network, net_data,
  831. restricted_vlans):
  832. bindings = nsx_db.get_network_bindings_by_phy_uuid(
  833. context.session, physical_network)
  834. vlan_ranges = self._network_vlans.get(physical_network, [])
  835. if vlan_ranges:
  836. vlan_ids = set()
  837. for vlan_min, vlan_max in vlan_ranges:
  838. vlan_ids |= set(moves.range(vlan_min, vlan_max + 1))
  839. else:
  840. vlan_min = constants.MIN_VLAN_TAG
  841. vlan_max = constants.MAX_VLAN_TAG
  842. vlan_ids = set(moves.range(vlan_min, vlan_max + 1))
  843. used_ids_in_range = [binding.vlan_id for binding in bindings
  844. if binding.vlan_id in vlan_ids]
  845. not_allowed_in_range = set(used_ids_in_range + restricted_vlans)
  846. free_ids = list(vlan_ids ^ not_allowed_in_range)
  847. if len(free_ids) == 0:
  848. raise n_exc.NoNetworkAvailable()
  849. net_data[pnet.SEGMENTATION_ID] = free_ids[0]
  850. return net_data[pnet.SEGMENTATION_ID]
  851. def _default_physical_net(self, physical_net):
  852. return physical_net is None or physical_net == 'default'
  853. def _validate_provider_create(self, context, network_data,
  854. default_vlan_tz_uuid,
  855. default_overlay_tz_uuid,
  856. nsxlib_tz, nsxlib_network,
  857. transparent_vlan=False):
  858. """Validate the parameters of a new provider network
  859. raises an error if illegal
  860. returns a dictionary with the relevant processed data:
  861. - is_provider_net: boolean
  862. - net_type: provider network type or None
  863. - physical_net: the uuid of the relevant transport zone or None
  864. - vlan_id: vlan tag, 0 or None
  865. - switch_mode: standard or ENS
  866. """
  867. is_provider_net = any(
  868. validators.is_attr_set(network_data.get(f))
  869. for f in (pnet.NETWORK_TYPE,
  870. pnet.PHYSICAL_NETWORK,
  871. pnet.SEGMENTATION_ID))
  872. physical_net = network_data.get(pnet.PHYSICAL_NETWORK)
  873. if not validators.is_attr_set(physical_net):
  874. physical_net = None
  875. vlan_id = network_data.get(pnet.SEGMENTATION_ID)
  876. if not validators.is_attr_set(vlan_id):
  877. vlan_id = None
  878. if vlan_id and transparent_vlan:
  879. err_msg = (_("Segmentation ID cannot be set with transparent "
  880. "vlan!"))
  881. raise n_exc.InvalidInput(error_message=err_msg)
  882. err_msg = None
  883. net_type = network_data.get(pnet.NETWORK_TYPE)
  884. tz_type = nsxlib_consts.TRANSPORT_TYPE_VLAN
  885. switch_mode = nsxlib_consts.HOST_SWITCH_MODE_STANDARD
  886. if validators.is_attr_set(net_type):
  887. if net_type == utils.NsxV3NetworkTypes.FLAT:
  888. if vlan_id is not None:
  889. err_msg = (_("Segmentation ID cannot be specified with "
  890. "%s network type") %
  891. utils.NsxV3NetworkTypes.FLAT)
  892. else:
  893. if not transparent_vlan:
  894. # Set VLAN id to 0 for flat networks
  895. vlan_id = '0'
  896. if self._default_physical_net(physical_net):
  897. physical_net = default_vlan_tz_uuid
  898. elif net_type == utils.NsxV3NetworkTypes.VLAN:
  899. # Use default VLAN transport zone if physical network not given
  900. if self._default_physical_net(physical_net):
  901. physical_net = default_vlan_tz_uuid
  902. restricted_vlans = self._get_tz_restricted_vlans(physical_net)
  903. if not transparent_vlan:
  904. # Validate VLAN id
  905. if not vlan_id:
  906. vlan_id = self._generate_segment_id(context,
  907. physical_net,
  908. network_data,
  909. restricted_vlans)
  910. elif not plugin_utils.is_valid_vlan_tag(vlan_id):
  911. err_msg = (_('Segmentation ID %(seg_id)s out of '
  912. 'range (%(min_id)s through %(max_id)s)') %
  913. {'seg_id': vlan_id,
  914. 'min_id': constants.MIN_VLAN_TAG,
  915. 'max_id': constants.MAX_VLAN_TAG})
  916. elif vlan_id in restricted_vlans:
  917. err_msg = (_('Segmentation ID %(seg_id)s cannot be '
  918. 'used as it is used by the transport '
  919. 'node') %
  920. {'seg_id': vlan_id})
  921. else:
  922. # Verify VLAN id is not already allocated
  923. bindings = nsx_db.\
  924. get_network_bindings_by_vlanid_and_physical_net(
  925. context.session, vlan_id, physical_net)
  926. if bindings:
  927. raise n_exc.VlanIdInUse(
  928. vlan_id=vlan_id, physical_network=physical_net)
  929. elif net_type == utils.NsxV3NetworkTypes.GENEVE:
  930. if vlan_id:
  931. err_msg = (_("Segmentation ID cannot be specified with "
  932. "%s network type") %
  933. utils.NsxV3NetworkTypes.GENEVE)
  934. tz_type = nsxlib_consts.TRANSPORT_TYPE_OVERLAY
  935. elif net_type == utils.NsxV3NetworkTypes.NSX_NETWORK:
  936. # Linking neutron networks to an existing NSX logical switch
  937. if not physical_net:
  938. err_msg = (_("Physical network must be specified with "
  939. "%s network type") % net_type)
  940. # Validate the logical switch existence
  941. else:
  942. try:
  943. nsx_net = nsxlib_network.get(physical_net)
  944. tz_id = self._get_nsx_net_tz_id(nsx_net)
  945. switch_mode = nsxlib_tz.get_host_switch_mode(tz_id)
  946. except nsx_lib_exc.ResourceNotFound:
  947. err_msg = (_('Logical switch %s does not exist') %
  948. physical_net)
  949. # make sure no other neutron network is using it
  950. bindings = (
  951. nsx_db.get_network_bindings_by_vlanid_and_physical_net(
  952. context.elevated().session, 0, physical_net))
  953. if bindings:
  954. err_msg = (_('Logical switch %s is already used by '
  955. 'another network') % physical_net)
  956. else:
  957. err_msg = (_('%(net_type_param)s %(net_type_value)s not '
  958. 'supported') %
  959. {'net_type_param': pnet.NETWORK_TYPE,
  960. 'net_type_value': net_type})
  961. elif is_provider_net:
  962. # FIXME: Ideally provider-network attributes should be checked
  963. # at the NSX backend. For now, the network_type is required,
  964. # so the plugin can do a quick check locally.
  965. err_msg = (_('%s is required for creating a provider network') %
  966. pnet.NETWORK_TYPE)
  967. else:
  968. net_type = None
  969. if physical_net is None:
  970. # Default to transport type overlay
  971. physical_net = default_overlay_tz_uuid
  972. # validate the transport zone existence and type
  973. if (not err_msg and physical_net and
  974. net_type != utils.NsxV3NetworkTypes.NSX_NETWORK):
  975. if is_provider_net:
  976. try:
  977. backend_type = nsxlib_tz.get_transport_type(
  978. physical_net)
  979. except nsx_lib_exc.ResourceNotFound:
  980. err_msg = (_('Transport zone %s does not exist') %
  981. physical_net)
  982. else:
  983. if backend_type != tz_type:
  984. err_msg = (_('%(tz)s transport zone is required for '
  985. 'creating a %(net)s provider network') %
  986. {'tz': tz_type, 'net': net_type})
  987. if not err_msg:
  988. switch_mode = nsxlib_tz.get_host_switch_mode(physical_net)
  989. if err_msg:
  990. raise n_exc.InvalidInput(error_message=err_msg)
  991. if (switch_mode == nsxlib_consts.HOST_SWITCH_MODE_ENS):
  992. if not self._allow_ens_networks():
  993. raise NotImplementedError(_("ENS support is disabled"))
  994. self._assert_on_ens_with_qos(network_data)
  995. self._validate_ens_net_portsecurity(network_data)
  996. return {'is_provider_net': is_provider_net,
  997. 'net_type': net_type,
  998. 'physical_net': physical_net,
  999. 'vlan_id': vlan_id,
  1000. 'switch_mode': switch_mode}
  1001. def _network_is_nsx_net(self, context, network_id):
  1002. bindings = nsx_db.get_network_bindings(context.session, network_id)
  1003. if not bindings:
  1004. return False
  1005. return (bindings[0].binding_type ==
  1006. utils.NsxV3NetworkTypes.NSX_NETWORK)
  1007. def _vif_type_by_vnic_type(self, direct_vnic_type):
  1008. return (nsx_constants.VIF_TYPE_DVS if direct_vnic_type
  1009. else pbin.VIF_TYPE_OVS)
  1010. def _get_network_segmentation_id(self, context, neutron_id):
  1011. bindings = nsx_db.get_network_bindings(context.session, neutron_id)
  1012. if bindings:
  1013. return bindings[0].vlan_id
  1014. def _get_network_vlan_transparent(self, context, network_id):
  1015. if not cfg.CONF.vlan_transparent:
  1016. return False
  1017. # Get this flag directly from DB to improve performance
  1018. db_entry = context.session.query(models_v2.Network).filter_by(
  1019. id=network_id).first()
  1020. if db_entry:
  1021. return True if db_entry.vlan_transparent else False
  1022. def _extend_nsx_port_dict_binding(self, context, port_data):
  1023. # Not using the register api for this because we need the context
  1024. # Some attributes were already initialized by _extend_port_portbinding
  1025. if pbin.VIF_TYPE not in port_data:
  1026. port_data[pbin.VIF_TYPE] = pbin.VIF_TYPE_OVS
  1027. if pbin.VNIC_TYPE not in port_data:
  1028. port_data[pbin.VNIC_TYPE] = pbin.VNIC_NORMAL
  1029. if 'network_id' in port_data:
  1030. net_id = port_data['network_id']
  1031. if pbin.VIF_DETAILS not in port_data:
  1032. port_data[pbin.VIF_DETAILS] = {}
  1033. port_data[pbin.VIF_DETAILS][pbin.OVS_HYBRID_PLUG] = False
  1034. if (port_data.get('device_owner') ==
  1035. constants.DEVICE_OWNER_FLOATINGIP):
  1036. # floatingip belongs to an external net without nsx-id
  1037. port_data[pbin.VIF_DETAILS]['nsx-logical-switch-id'] = None
  1038. else:
  1039. port_data[pbin.VIF_DETAILS]['nsx-logical-switch-id'] = (
  1040. self._get_network_nsx_id(context, net_id))
  1041. if port_data[pbin.VNIC_TYPE] != pbin.VNIC_NORMAL:
  1042. port_data[pbin.VIF_DETAILS]['segmentation-id'] = (
  1043. self._get_network_segmentation_id(context, net_id))
  1044. port_data[pbin.VIF_DETAILS]['vlan-transparent'] = (
  1045. self._get_network_vlan_transparent(context, net_id))
  1046. def _extend_qos_port_dict_binding(self, context, port):
  1047. # add the qos policy id from the DB
  1048. if 'id' in port:
  1049. port[qos_consts.QOS_POLICY_ID] = qos_com_utils.get_port_policy_id(
  1050. context, port['id'])
  1051. def fix_direct_vnic_port_sec(self, direct_vnic_type, port_data):
  1052. if direct_vnic_type:
  1053. if validators.is_attr_set(port_data.get(psec.PORTSECURITY)):
  1054. # 'direct' and 'direct-physical' vnic types ports requires
  1055. # port-security to be disabled.
  1056. if port_data[psec.PORTSECURITY]:
  1057. err_msg = _("Security features are not supported for "
  1058. "ports with direct/direct-physical VNIC "
  1059. "type")
  1060. raise n_exc.InvalidInput(error_message=err_msg)
  1061. else:
  1062. # Implicitly disable port-security for direct vnic types.
  1063. port_data[psec.PORTSECURITY] = False
  1064. def _validate_network_type(self, context, network_id, net_types):
  1065. net = self.get_network(context, network_id)
  1066. if net.get(pnet.NETWORK_TYPE) in net_types:
  1067. return True
  1068. return False
  1069. def _revert_neutron_port_update(self, context, port_id,
  1070. original_port, updated_port,
  1071. port_security, sec_grp_updated):
  1072. # revert the neutron port update
  1073. super(NsxPluginV3Base, self).update_port(context, port_id,
  1074. {'port': original_port})
  1075. # revert allowed address pairs
  1076. if port_security:
  1077. orig_pair = original_port.get(addr_apidef.ADDRESS_PAIRS)
  1078. updated_pair = updated_port.get(addr_apidef.ADDRESS_PAIRS)
  1079. if orig_pair != updated_pair:
  1080. self._delete_allowed_address_pairs(context, port_id)
  1081. if orig_pair:
  1082. self._process_create_allowed_address_pairs(
  1083. context, original_port, orig_pair)
  1084. # revert the security groups modifications
  1085. if sec_grp_updated:
  1086. self.update_security_group_on_port(
  1087. context, port_id, {'port': original_port},
  1088. updated_port, original_port)
  1089. def _get_external_attachment_info(self, context, router):
  1090. gw_port = router.gw_port
  1091. ipaddress = None
  1092. netmask = None
  1093. nexthop = None
  1094. if gw_port:
  1095. # gw_port may have multiple IPs, only configure the first one
  1096. if gw_port.get('fixed_ips'):
  1097. ipaddress = gw_port['fixed_ips'][0]['ip_address']
  1098. network_id = gw_port.get('network_id')
  1099. if network_id:
  1100. ext_net = self._get_network(context, network_id)
  1101. if not ext_net.external:
  1102. msg = (_("Network '%s' is not a valid external "
  1103. "network") % network_id)
  1104. raise n_exc.BadRequest(resource='router', msg=msg)
  1105. if ext_net.subnets:
  1106. ext_subnet = ext_net.subnets[0]
  1107. netmask = str(netaddr.IPNetwork(ext_subnet.cidr).netmask)
  1108. nexthop = ext_subnet.gateway_ip
  1109. return (ipaddress, netmask, nexthop)
  1110. def _get_tier0_uuid_by_net_id(self, context, network_id):
  1111. if not network_id:
  1112. return
  1113. network = self.get_network(context, network_id)
  1114. if not network.get(pnet.PHYSICAL_NETWORK):
  1115. az = self.get_network_az(network)
  1116. return az._default_tier0_router
  1117. else:
  1118. return network.get(pnet.PHYSICAL_NETWORK)
  1119. def _validate_router_tz(self, context, tier0_uuid, subnets):
  1120. """Ensure the related GW (Tier0 router) belongs to the same TZ
  1121. as the subnets attached to the Tier1 router
  1122. Should be implemented by each plugin.
  1123. """
  1124. pass
  1125. def _get_router_gw_info(self, context, router_id):
  1126. router = self.get_router(context, router_id)
  1127. return router.get(l3_apidef.EXTERNAL_GW_INFO, {})
  1128. def _validate_router_gw_and_tz(self, context, router_id, info,
  1129. org_enable_snat, router_subnets):
  1130. # Ensure that a router cannot have SNAT disabled if there are
  1131. # floating IP's assigned
  1132. if (info and 'enable_snat' in info and
  1133. org_enable_snat != info.get('enable_snat') and
  1134. info.get('enable_snat') is False and
  1135. self.router_gw_port_has_floating_ips(context, router_id)):
  1136. msg = _("Unable to set SNAT disabled. Floating IPs assigned")
  1137. raise n_exc.InvalidInput(error_message=msg)
  1138. # Ensure that the router GW tier0 belongs to the same TZ as the
  1139. # subnets of its interfaces
  1140. if info and info.get('network_id'):
  1141. new_tier0_uuid = self._get_tier0_uuid_by_net_id(context.elevated(),
  1142. info['network_id'])
  1143. if new_tier0_uuid:
  1144. self._validate_router_tz(context, new_tier0_uuid,
  1145. router_subnets)
  1146. def _get_tier0_uuid_by_router(self, context, router):
  1147. network_id = router.gw_port_id and router.gw_port.network_id
  1148. return self._get_tier0_uuid_by_net_id(context, network_id)
  1149. def _validate_gw_overlap_interfaces(self, context, gateway_net,
  1150. interfaces_networks):
  1151. # Ensure that interface subnets cannot overlap with the GW subnet
  1152. gw_subnets = self._get_subnets_by_network(
  1153. context.elevated(), gateway_net)
  1154. gw_cidrs = [subnet['cidr'] for subnet in gw_subnets]
  1155. gw_ip_set = netaddr.IPSet(gw_cidrs)
  1156. if_subnets = []
  1157. for net in interfaces_networks:
  1158. if_subnets.extend(self._get_subnets_by_network(
  1159. context.elevated(), net))
  1160. if_cidrs = [subnet['cidr'] for subnet in if_subnets]
  1161. if_ip_set = netaddr.IPSet(if_cidrs)
  1162. if gw_ip_set & if_ip_set:
  1163. msg = _("Interface network cannot overlap with router GW network")
  1164. LOG.error(msg)
  1165. raise n_exc.InvalidInput(error_message=msg)
  1166. def _get_update_router_gw_actions(
  1167. self,
  1168. org_tier0_uuid, orgaddr, org_enable_snat,
  1169. new_tier0_uuid, newaddr, new_enable_snat,
  1170. tier1_services_exist, sr_currently_exists):
  1171. """Return a dictionary of flags indicating which actions should be
  1172. performed on this router GW update.
  1173. """
  1174. actions = {}
  1175. # Remove router link port between tier1 and tier0 if tier0 router link
  1176. # is removed or changed
  1177. actions['remove_router_link_port'] = (
  1178. org_tier0_uuid and
  1179. (not new_tier0_uuid or org_tier0_uuid != new_tier0_uuid))
  1180. # Remove SNAT rules for gw ip if gw ip is deleted/changed or
  1181. # enable_snat is updated from True to False
  1182. actions['remove_snat_rules'] = (
  1183. org_enable_snat and orgaddr and
  1184. (newaddr != orgaddr or not new_enable_snat))
  1185. # Remove No-DNAT rules if GW was removed or snat was disabled
  1186. actions['remove_no_dnat_rules'] = (
  1187. orgaddr and org_enable_snat and
  1188. (not newaddr or not new_enable_snat))
  1189. # Revocate bgp announce for nonat subnets if tier0 router link is
  1190. # changed or enable_snat is updated from False to True
  1191. actions['revocate_bgp_announce'] = (
  1192. not org_enable_snat and org_tier0_uuid and
  1193. (new_tier0_uuid != org_tier0_uuid or new_enable_snat))
  1194. # Add router link port between tier1 and tier0 if tier0 router link is
  1195. # added or changed to a new one
  1196. actions['add_router_link_port'] = (
  1197. new_tier0_uuid and
  1198. (not org_tier0_uuid or org_tier0_uuid != new_tier0_uuid))
  1199. # Add SNAT rules for gw ip if gw ip is add/changed or
  1200. # enable_snat is updated from False to True
  1201. actions['add_snat_rules'] = (
  1202. new_enable_snat and newaddr and
  1203. (newaddr != orgaddr or not org_enable_snat))
  1204. # Add No-DNAT rules if GW was added, and the router has SNAT enabled,
  1205. # or if SNAT was enabled
  1206. actions['add_no_dnat_rules'] = (
  1207. new_enable_snat and newaddr and
  1208. (not orgaddr or not org_enable_snat))
  1209. # Bgp announce for nonat subnets if tier0 router link is changed or
  1210. # enable_snat is updated from True to False
  1211. actions['bgp_announce'] = (
  1212. not new_enable_snat and new_tier0_uuid and
  1213. (new_tier0_uuid != org_tier0_uuid or not org_enable_snat))
  1214. # Advertise NAT routes if enable SNAT to support FIP. In the NoNAT
  1215. # use case, only NSX connected routes need to be advertised.
  1216. actions['advertise_route_nat_flag'] = (
  1217. True if new_enable_snat else False)
  1218. actions['advertise_route_connected_flag'] = (
  1219. True if not new_enable_snat else False)
  1220. # the purpose of this var is to be able to differ between
  1221. # adding a gateway w/o snat and adding snat (when adding/removing gw
  1222. # the snat option is on by default).
  1223. new_with_snat = True if (new_enable_snat and newaddr) else False
  1224. has_gw = True if newaddr else False
  1225. if sr_currently_exists:
  1226. # currently there is a service router on the backend
  1227. actions['add_service_router'] = False
  1228. # Should remove the service router if the GW was removed,
  1229. # or no service needs it: SNAT, LBaaS or FWaaS
  1230. actions['remove_service_router'] = (
  1231. not has_gw or not (tier1_services_exist or new_with_snat))
  1232. if actions['remove_service_router']:
  1233. LOG.info("Removing service router [has GW: %s, services %s, "
  1234. "SNAT %s]",
  1235. has_gw, tier1_services_exist, new_with_snat)
  1236. else:
  1237. # currently there is no service router on the backend
  1238. actions['remove_service_router'] = False
  1239. # Should add service router if there is a GW
  1240. # and there is a service that needs it: SNAT, LB or FWaaS
  1241. actions['add_service_router'] = (
  1242. has_gw is not None and (new_with_snat or tier1_services_exist))
  1243. if actions['add_service_router']:
  1244. LOG.info("Adding service router [has GW: %s, services %s, "
  1245. "SNAT %s]",
  1246. has_gw, tier1_services_exist, new_with_snat)
  1247. return actions
  1248. def _validate_update_router_gw(self, context, router_id, gw_info):
  1249. router_ports = self._get_router_interfaces(context, router_id)
  1250. for port in router_ports:
  1251. # if setting this router as no-snat, make sure gw address scope
  1252. # match those of the subnets
  1253. if not gw_info.get('enable_snat',
  1254. cfg.CONF.enable_snat_by_default):
  1255. for fip in port['fixed_ips']:
  1256. self._validate_address_scope_for_router_interface(
  1257. context.elevated(), router_id,
  1258. gw_info['network_id'], fip['subnet_id'])
  1259. # If the network attached to a router is a VLAN backed network
  1260. # then it must be attached to an edge cluster
  1261. if (not gw_info and
  1262. not self._is_overlay_network(context, port['network_id'])):
  1263. msg = _("A router attached to a VLAN backed network "
  1264. "must have an external network assigned")
  1265. raise n_exc.InvalidInput(error_message=msg)
  1266. def _validate_ext_routes(self, context, router_id, gw_info, new_routes):
  1267. ext_net_id = (gw_info['network_id']
  1268. if validators.is_attr_set(gw_info) and gw_info else None)
  1269. if not ext_net_id:
  1270. port_filters = {'device_id': [router_id],
  1271. 'device_owner': [l3_db.DEVICE_OWNER_ROUTER_GW]}
  1272. gw_ports = self.get_ports(context, filters=port_filters)
  1273. if gw_ports:
  1274. ext_net_id = gw_ports[0]['network_id']
  1275. if ext_net_id:
  1276. subnets = self._get_subnets_by_network(context, ext_net_id)
  1277. ext_cidrs = [subnet['cidr'] for subnet in subnets]
  1278. for route in new_routes:
  1279. if netaddr.all_matching_cidrs(
  1280. route['nexthop'], ext_cidrs):
  1281. error_message = (_("route with destination %(dest)s have "
  1282. "an external nexthop %(nexthop)s which "
  1283. "can't be supported") %
  1284. {'dest': route['destination'],
  1285. 'nexthop': route['nexthop']})
  1286. LOG.error(error_message)
  1287. raise n_exc.InvalidInput(error_message=error_message)
  1288. def _get_static_routes_diff(self, context, router_id, gw_info,
  1289. router_data):
  1290. new_routes = router_data['routes']
  1291. self._validate_ext_routes(context, router_id, gw_info,
  1292. new_routes)
  1293. self._validate_routes(context, router_id, new_routes)
  1294. old_routes = self._get_extra_routes_by_router_id(
  1295. context, router_id)
  1296. routes_added, routes_removed = helpers.diff_list_of_dict(
  1297. old_routes, new_routes)
  1298. return routes_added, routes_removed
  1299. def _assert_on_router_admin_state(self, router_data):
  1300. if router_data.get("admin_state_up") is False:
  1301. err_msg = _("admin_state_up=False routers are not supported")
  1302. LOG.warning(err_msg)
  1303. raise n_exc.InvalidInput(error_message=err_msg)
  1304. def _build_dhcp_server_config(self, context, network, subnet, port, az):
  1305. name = self.nsxlib.native_dhcp.build_server_name(
  1306. network['name'], network['id'])
  1307. net_tags = self.nsxlib.build_v3_tags_payload(
  1308. network, resource_type='os-neutron-net-id',
  1309. project_name=context.tenant_name)
  1310. dns_domain = None
  1311. if network.get('dns_domain'):
  1312. net_dns = network['dns_domain']
  1313. if isinstance(net_dns, string_types):
  1314. dns_domain = net_dns
  1315. elif hasattr(net_dns, "dns_domain"):
  1316. dns_domain = net_dns.dns_domain
  1317. if not dns_domain or not validators.is_attr_set(dns_domain):
  1318. dns_domain = az.dns_domain
  1319. dns_nameservers = subnet['dns_nameservers']
  1320. if not dns_nameservers or not validators.is_attr_set(dns_nameservers):
  1321. dns_nameservers = az.nameservers
  1322. # There must be exactly one fixed ip matching given subnet
  1323. fixed_ip_addr = [fip['ip_address'] for fip in port['fixed_ips']
  1324. if fip['subnet_id'] == subnet['id']]
  1325. return self.nsxlib.native_dhcp.build_server(
  1326. name,
  1327. ip_address=fixed_ip_addr[0],
  1328. cidr=subnet['cidr'],
  1329. gateway_ip=subnet['gateway_ip'],
  1330. host_routes=subnet['host_routes'],
  1331. dns_domain=dns_domain,
  1332. dns_nameservers=dns_nameservers,
  1333. dhcp_profile_id=az._native_dhcp_profile_uuid,
  1334. tags=net_tags)
  1335. def _enable_native_dhcp(self, context, network, subnet):
  1336. # Enable native DHCP service on the backend for this network.
  1337. # First create a Neutron DHCP port and use its assigned IP
  1338. # address as the DHCP server address in an API call to create a
  1339. # LogicalDhcpServer on the backend. Then create the corresponding
  1340. # logical port for the Neutron port with DHCP attachment as the
  1341. # LogicalDhcpServer UUID.
  1342. # TODO(annak):
  1343. # This function temporarily serves both nsx_v3 and nsx_p plugins.
  1344. # In future, when platform supports native dhcp in policy for infra
  1345. # segments, this function should move back to nsx_v3 plugin
  1346. # Delete obsolete settings if exist. This could happen when a
  1347. # previous failed transaction was rolled back. But the backend
  1348. # entries are still there.
  1349. self._disable_native_dhcp(context, network['id'])
  1350. # Get existing ports on subnet.
  1351. existing_ports = super(NsxPluginV3Base, self).get_ports(
  1352. context, filters={'network_id': [network['id']],
  1353. 'fixed_ips': {'subnet_id': [subnet['id']]}})
  1354. nsx_net_id = self._get_network_nsx_id(context, network['id'])
  1355. if not nsx_net_id:
  1356. msg = ("Unable to obtain backend network id for logical DHCP "
  1357. "server for network %s" % network['id'])
  1358. LOG.error(msg)
  1359. raise nsx_exc.NsxPluginException(err_msg=msg)
  1360. az = self.get_network_az_by_net_id(context, network['id'])
  1361. port_data = {
  1362. "name": "",
  1363. "admin_state_up": True,
  1364. "device_id": az._native_dhcp_profile_uuid,
  1365. "device_owner": constants.DEVICE_OWNER_DHCP,
  1366. "network_id": network['id'],
  1367. "tenant_id": network["tenant_id"],
  1368. "mac_address": constants.ATTR_NOT_SPECIFIED,
  1369. "fixed_ips": [{"subnet_id": subnet['id']}],
  1370. psec.PORTSECURITY: False
  1371. }
  1372. # Create the DHCP port (on neutron only) and update its port security
  1373. port = {'port': port_data}
  1374. neutron_port = super(NsxPluginV3Base, self).create_port(context, port)
  1375. is_ens_tz_port = self._is_ens_tz_port(context, port_data)
  1376. self._create_port_preprocess_security(context, port, port_data,
  1377. neutron_port, is_ens_tz_port)
  1378. self._process_portbindings_create_and_update(
  1379. context, port_data, neutron_port)
  1380. server_data = self._build_dhcp_server_config(
  1381. context, network, subnet, neutron_port, az)
  1382. port_tags = self.nsxlib.build_v3_tags_payload(
  1383. neutron_port, resource_type='os-neutron-dport-id',
  1384. project_name=context.tenant_name)
  1385. dhcp_server = None
  1386. dhcp_port_profiles = []
  1387. if (not self._has_native_dhcp_metadata() and
  1388. not self._is_ens_tz_net(context, network['id'])):
  1389. dhcp_port_profiles.append(self._dhcp_profile)
  1390. try:
  1391. dhcp_server = self.nsxlib.dhcp_server.create(**server_data)
  1392. LOG.debug("Created logical DHCP server %(server)s for network "
  1393. "%(network)s",
  1394. {'server': dhcp_server['id'], 'network': network['id']})
  1395. name = self._build_port_name(context, port_data)
  1396. nsx_port = self.nsxlib.logical_port.create(
  1397. nsx_net_id, dhcp_server['id'], tags=port_tags, name=name,
  1398. attachment_type=nsxlib_consts.ATTACHMENT_DHCP,
  1399. switch_profile_ids=dhcp_port_profiles)
  1400. LOG.debug("Created DHCP logical port %(port)s for "
  1401. "network %(network)s",
  1402. {'port': nsx_port['id'], 'network': network['id']})
  1403. except nsx_lib_exc.ServiceClusterUnavailable:
  1404. raise webob.exc.HTTPServiceUnavailable()
  1405. except nsx_lib_exc.ManagerError:
  1406. err_msg = ("Unable to create logical DHCP server for "
  1407. "network %s" % network['id'])
  1408. LOG.error(err_msg)
  1409. if dhcp_server:
  1410. self.nsxlib.dhcp_server.delete(dhcp_server['id'])
  1411. super(NsxPluginV3Base, self).delete_port(
  1412. context, neutron_port['id'])
  1413. raise nsx_exc.NsxPluginException(err_msg=err_msg)
  1414. try:
  1415. # Add neutron_port_id -> nsx_port_id mapping to the DB.
  1416. nsx_db.add_neutron_nsx_port_mapping(
  1417. context.session, neutron_port['id'], nsx_net_id,
  1418. nsx_port['id'])
  1419. # Add neutron_net_id -> dhcp_service_id mapping to the DB.
  1420. nsx_db.add_neutron_nsx_service_binding(
  1421. context.session, network['id'], neutron_port['id'],
  1422. nsxlib_consts.SERVICE_DHCP, dhcp_server['id'])
  1423. except (db_exc.DBError, sql_exc.TimeoutError):
  1424. with excutils.save_and_reraise_exception():
  1425. LOG.error("Failed to create mapping for DHCP port %s,"
  1426. "deleting port and logical DHCP server",
  1427. neutron_port['id'])
  1428. self.nsxlib.dhcp_server.delete(dhcp_server['id'])
  1429. self._cleanup_port(context, neutron_port['id'], nsx_port['id'])
  1430. # Configure existing ports to work with the new DHCP server
  1431. try:
  1432. for port_data in existing_ports:
  1433. self._add_dhcp_binding(context, port_data)
  1434. except Exception:
  1435. LOG.error('Unable to create DHCP bindings for existing ports '
  1436. 'on subnet %s', subnet['id'])
  1437. def _disable_native_dhcp(self, context, network_id):
  1438. # Disable native DHCP service on the backend for this network.
  1439. # First delete the DHCP port in this network. Then delete the
  1440. # corresponding LogicalDhcpServer for this network.
  1441. self._ensure_native_dhcp()
  1442. dhcp_service = nsx_db.get_nsx_service_binding(
  1443. context.session, network_id, nsxlib_consts.SERVICE_DHCP)
  1444. if not dhcp_service:
  1445. return
  1446. if dhcp_service['port_id']:
  1447. try:
  1448. _net_id, nsx_port_id = nsx_db.get_nsx_switch_and_port_id(
  1449. context.session, dhcp_service['port_id'])
  1450. self._cleanup_port(context, dhcp_service['port_id'],
  1451. nsx_port_id)
  1452. except nsx_lib_exc.ResourceNotFound:
  1453. # This could happen when the port has been manually deleted.
  1454. LOG.error("Failed to delete DHCP port %(port)s for "
  1455. "network %(network)s",
  1456. {'port': dhcp_service['port_id'],
  1457. 'network': network_id})
  1458. else:
  1459. LOG.error("DHCP port is not configured for network %s",
  1460. network_id)
  1461. try:
  1462. self.nsxlib.dhcp_server.delete(dhcp_service['nsx_service_id'])
  1463. LOG.debug("Deleted logical DHCP server %(server)s for network "
  1464. "%(network)s",
  1465. {'server': dhcp_service['nsx_service_id'],
  1466. 'network': network_id})
  1467. except nsx_lib_exc.ManagerError:
  1468. with excutils.save_and_reraise_exception():
  1469. LOG.error("Unable to delete logical DHCP server %(server)s "
  1470. "for network %(network)s",
  1471. {'server': dhcp_service['nsx_service_id'],
  1472. 'network': network_id})
  1473. try:
  1474. # Delete neutron_id -> dhcp_service_id mapping from the DB.
  1475. nsx_db.delete_neutron_nsx_service_binding(
  1476. context.session, network_id, nsxlib_consts.SERVICE_DHCP)
  1477. # Delete all DHCP bindings under this DHCP server from the DB.
  1478. nsx_db.delete_neutron_nsx_dhcp_bindings_by_service_id(
  1479. context.session, dhcp_service['nsx_service_id'])
  1480. except db_exc.DBError:
  1481. with excutils.save_and_reraise_exception():
  1482. LOG.error("Unable to delete DHCP server mapping for "
  1483. "network %s", network_id)
  1484. def _filter_ipv4_dhcp_fixed_ips(self, context, fixed_ips):
  1485. ips = []
  1486. for fixed_ip in fixed_ips:
  1487. if netaddr.IPNetwork(fixed_ip['ip_address']).version != 4:
  1488. continue
  1489. with db_api.CONTEXT_READER.using(context):
  1490. subnet = self.get_subnet(context, fixed_ip['subnet_id'])
  1491. if subnet['enable_dhcp']:
  1492. ips.append(fixed_ip)
  1493. return ips
  1494. def _add_dhcp_binding(self, context, port):
  1495. if not utils.is_port_dhcp_configurable(port):
  1496. return
  1497. dhcp_service = nsx_db.get_nsx_service_binding(
  1498. context.session, port['network_id'], nsxlib_consts.SERVICE_DHCP)
  1499. if not dhcp_service:
  1500. return
  1501. for fixed_ip in self._filter_ipv4_dhcp_fixed_ips(
  1502. context, port['fixed_ips']):
  1503. binding = self._add_dhcp_binding_on_server(
  1504. context, dhcp_service['nsx_service_id'], fixed_ip['subnet_id'],
  1505. fixed_ip['ip_address'], port)
  1506. try:
  1507. nsx_db.add_neutron_nsx_dhcp_binding(
  1508. context.session, port['id'], fixed_ip['subnet_id'],
  1509. fixed_ip['ip_address'], dhcp_service['nsx_service_id'],
  1510. binding['id'])
  1511. except (db_exc.DBError, sql_exc.TimeoutError):
  1512. LOG.error("Failed to add mapping of DHCP binding "
  1513. "%(binding)s for port %(port)s, deleting "
  1514. "DHCP binding on server",
  1515. {'binding': binding['id'], 'port': port['id']})
  1516. fake_db_binding = {
  1517. 'port_id': port['id'],
  1518. 'nsx_service_id': dhcp_service['nsx_service_id'],
  1519. 'nsx_binding_id': binding['id']}
  1520. self._delete_dhcp_binding_on_server(context, fake_db_binding)
  1521. def _add_dhcp_binding_on_server(self, context, dhcp_service_id, subnet_id,
  1522. ip, port):
  1523. try:
  1524. hostname = 'host-%s' % ip.replace('.', '-')
  1525. subnet = self.get_subnet(context, subnet_id)
  1526. gateway_ip = subnet.get('gateway_ip')
  1527. options = self._get_dhcp_options(
  1528. context, ip, port.get(ext_edo.EXTRADHCPOPTS),
  1529. port['network_id'], subnet)
  1530. binding = self.nsxlib.dhcp_server.create_binding(
  1531. dhcp_service_id, port['mac_address'], ip, hostname,
  1532. self._get_conf_attr('dhcp_lease_time'), options, gateway_ip)
  1533. LOG.debug("Created static binding (mac: %(mac)s, ip: %(ip)s, "
  1534. "gateway: %(gateway)s, options: %(options)s) for port "
  1535. "%(port)s on logical DHCP server %(server)s",
  1536. {'mac': port['mac_address'], 'ip': ip,
  1537. 'gateway': gateway_ip, 'options': options,
  1538. 'port': port['id'],
  1539. 'server': dhcp_service_id})
  1540. return binding
  1541. except nsx_lib_exc.ManagerError:
  1542. with excutils.save_and_reraise_exception():
  1543. LOG.error("Unable to create static binding (mac: %(mac)s, "
  1544. "ip: %(ip)s, gateway: %(gateway)s, options: "
  1545. "%(options)s) for port %(port)s on logical DHCP "
  1546. "server %(server)s",
  1547. {'mac': port['mac_address'], 'ip': ip,
  1548. 'gateway': gateway_ip, 'options': options,
  1549. 'port': port['id'],
  1550. 'server': dhcp_service_id})
  1551. def _delete_dhcp_binding(self, context, port):
  1552. # Do not check device_owner here because Nova may have already
  1553. # deleted that before Neutron's port deletion.
  1554. bindings = nsx_db.get_nsx_dhcp_bindings(context.session, port['id'])
  1555. for binding in bindings:
  1556. self._delete_dhcp_binding_on_server(context, binding)
  1557. try:
  1558. nsx_db.delete_neutron_nsx_dhcp_binding(
  1559. context.session, binding['port_id'],
  1560. binding['nsx_binding_id'])
  1561. except db_exc.DBError:
  1562. LOG.error("Unable to delete mapping of DHCP binding "
  1563. "%(binding)s for port %(port)s",
  1564. {'binding': binding['nsx_binding_id'],
  1565. 'port': binding['port_id']})
  1566. def _delete_dhcp_binding_on_server(self, context, binding):
  1567. try:
  1568. self.nsxlib.dhcp_server.delete_binding(
  1569. binding['nsx_service_id'], binding['nsx_binding_id'])
  1570. LOG.debug("Deleted static binding for port %(port)s) on "
  1571. "logical DHCP server %(server)s",
  1572. {'port': binding['port_id'],
  1573. 'server': binding['nsx_service_id']})
  1574. except nsx_lib_exc.ManagerError:
  1575. with excutils.save_and_reraise_exception():
  1576. LOG.error("Unable to delete static binding for port "
  1577. "%(port)s) on logical DHCP server %(server)s",
  1578. {'port': binding['port_id'],
  1579. 'server': binding['nsx_service_id']})
  1580. def _find_dhcp_binding(self, subnet_id, ip_address, bindings):
  1581. for binding in bindings:
  1582. if (subnet_id == binding['subnet_id'] and
  1583. ip_address == binding['ip_address']):
  1584. return binding
  1585. def _update_dhcp_binding(self, context, old_port, new_port):
  1586. # First check if any IPv4 address in fixed_ips is changed.
  1587. # Then update DHCP server setting or DHCP static binding
  1588. # depending on the port type.
  1589. # Note that Neutron allows a port with multiple IPs in the
  1590. # same subnet. But backend DHCP server may not support that.
  1591. if (utils.is_port_dhcp_configurable(old_port) !=
  1592. utils.is_port_dhcp_configurable(new_port)):
  1593. # Note that the device_owner could be changed,
  1594. # but still needs DHCP binding.
  1595. if utils.is_port_dhcp_configurable(old_port):
  1596. self._delete_dhcp_binding(context, old_port)
  1597. else:
  1598. self._add_dhcp_binding(context, new_port)
  1599. return
  1600. # Collect IPv4 DHCP addresses from original and updated fixed_ips
  1601. # in the form of [(subnet_id, ip_address)].
  1602. old_fixed_ips = set([(fixed_ip['subnet_id'], fixed_ip['ip_address'])
  1603. for fixed_ip in self._filter_ipv4_dhcp_fixed_ips(
  1604. context, old_port['fixed_ips'])])
  1605. new_fixed_ips = set([(fixed_ip['subnet_id'], fixed_ip['ip_address'])
  1606. for fixed_ip in self._filter_ipv4_dhcp_fixed_ips(
  1607. context, new_port['fixed_ips'])])
  1608. # Find out the subnet/IP differences before and after the update.
  1609. ips_to_add = list(new_fixed_ips - old_fixed_ips)
  1610. ips_to_delete = list(old_fixed_ips - new_fixed_ips)
  1611. ip_change = (ips_to_add or ips_to_delete)
  1612. if (old_port["device_owner"] == constants.DEVICE_OWNER_DHCP and
  1613. ip_change):
  1614. # Update backend DHCP server address if the IP address of a DHCP
  1615. # port is changed.
  1616. if len(new_fixed_ips) != 1:
  1617. msg = _("Can only configure one IP address on a DHCP server")
  1618. LOG.error(msg)
  1619. raise n_exc.InvalidInput(error_message=msg)
  1620. # Locate the backend DHCP server for this DHCP port.
  1621. dhcp_service = nsx_db.get_nsx_service_binding(
  1622. context.session, old_port['network_id'],
  1623. nsxlib_consts.SERVICE_DHCP)
  1624. if dhcp_service:
  1625. new_ip = ips_to_add[0][1]
  1626. try:
  1627. self.nsxlib.dhcp_server.update(
  1628. dhcp_service['nsx_service_id'],
  1629. server_ip=new_ip)
  1630. LOG.debug("Updated IP %(ip)s for logical DHCP server "
  1631. "%(server)s",
  1632. {'ip': new_ip,
  1633. 'server': dhcp_service['nsx_service_id']})
  1634. except nsx_lib_exc.ManagerError:
  1635. with excutils.save_and_reraise_exception():
  1636. LOG.error("Unable to update IP %(ip)s for logical "
  1637. "DHCP server %(server)s",
  1638. {'ip': new_ip,
  1639. 'server': dhcp_service['nsx_service_id']})
  1640. elif utils.is_port_dhcp_configurable(old_port):
  1641. # Update static DHCP bindings for a compute port.
  1642. bindings = nsx_db.get_nsx_dhcp_bindings(context.session,
  1643. old_port['id'])
  1644. dhcp_opts = new_port.get(ext_edo.EXTRADHCPOPTS)
  1645. dhcp_opts_changed = (old_port[ext_edo.EXTRADHCPOPTS] !=
  1646. new_port[ext_edo.EXTRADHCPOPTS])
  1647. if ip_change:
  1648. # If IP address is changed, update associated DHCP bindings,
  1649. # metadata route, and default hostname.
  1650. # Mac address (if changed) will be updated at the same time.
  1651. if ([subnet_id for (subnet_id, ip) in ips_to_add] ==
  1652. [subnet_id for (subnet_id, ip) in ips_to_delete]):
  1653. # No change on subnet_id, just update corresponding IPs.
  1654. for i, (subnet_id, ip) in enumerate(ips_to_delete):
  1655. binding = self._find_dhcp_binding(subnet_id, ip,
  1656. bindings)
  1657. if binding:
  1658. subnet = self.get_subnet(context,
  1659. binding['subnet_id'])
  1660. self._update_dhcp_binding_on_server(
  1661. context, binding, new_port['mac_address'],
  1662. ips_to_add[i][1], old_port['network_id'],
  1663. dhcp_opts=dhcp_opts, subnet=subnet)
  1664. # Update DB IP
  1665. nsx_db.update_nsx_dhcp_bindings(context.session,
  1666. old_port['id'],
  1667. ip,
  1668. ips_to_add[i][1])
  1669. else:
  1670. for (subnet_id, ip) in ips_to_delete:
  1671. binding = self._find_dhcp_binding(subnet_id, ip,
  1672. bindings)
  1673. if binding:
  1674. self._delete_dhcp_binding_on_server(context,
  1675. binding)
  1676. if ips_to_add:
  1677. dhcp_service = nsx_db.get_nsx_service_binding(
  1678. context.session, new_port['network_id'],
  1679. nsxlib_consts.SERVICE_DHCP)
  1680. if dhcp_service:
  1681. for (subnet_id, ip) in ips_to_add:
  1682. self._add_dhcp_binding_on_server(
  1683. context, dhcp_service['nsx_service_id'],
  1684. subnet_id, ip, new_port)
  1685. elif (old_port['mac_address'] != new_port['mac_address'] or
  1686. dhcp_opts_changed):
  1687. # If only Mac address/dhcp opts is changed,
  1688. # update it in all associated DHCP bindings.
  1689. for binding in bindings:
  1690. subnet = self.get_subnet(context, binding['subnet_id'])
  1691. self._update_dhcp_binding_on_server(
  1692. context, binding, new_port['mac_address'],
  1693. binding['ip_address'], old_port['network_id'],
  1694. dhcp_opts=dhcp_opts, subnet=subnet)
  1695. def _cleanup_port(self, context, port_id, nsx_port_id=None):
  1696. # Clean up neutron port and nsx manager port if provided
  1697. # Does not handle cleanup of policy port
  1698. super(NsxPluginV3Base, self).delete_port(context, port_id)
  1699. if nsx_port_id and self.nsxlib:
  1700. self.nsxlib.logical_port.delete(nsx_port_id)
  1701. def _is_excluded_port(self, device_owner, port_security):
  1702. if device_owner == l3_db.DEVICE_OWNER_ROUTER_INTF:
  1703. return False
  1704. if device_owner == constants.DEVICE_OWNER_DHCP:
  1705. if not self._has_native_dhcp_metadata():
  1706. return True
  1707. elif not port_security:
  1708. return True
  1709. return False
  1710. def _validate_obj_az_on_creation(self, context, obj_data, obj_type):
  1711. # validate the availability zone, and get the AZ object
  1712. if az_def.AZ_HINTS in obj_data:
  1713. self._validate_availability_zones_forced(
  1714. context, obj_type, obj_data[az_def.AZ_HINTS])
  1715. return self.get_obj_az_by_hints(obj_data)
  1716. def _add_az_to_net(self, context, net_id, net_data):
  1717. if az_def.AZ_HINTS in net_data:
  1718. # Update the AZ hints in the neutron object
  1719. az_hints = az_validator.convert_az_list_to_string(
  1720. net_data[az_def.AZ_HINTS])
  1721. super(NsxPluginV3Base, self).update_network(
  1722. context, net_id,
  1723. {'network': {az_def.AZ_HINTS: az_hints}})
  1724. def _add_az_to_router(self, context, router_id, router_data):
  1725. if az_def.AZ_HINTS in router_data:
  1726. # Update the AZ hints in the neutron object
  1727. az_hints = az_validator.convert_az_list_to_string(
  1728. router_data[az_def.AZ_HINTS])
  1729. super(NsxPluginV3Base, self).update_router(
  1730. context, router_id,
  1731. {'router': {az_def.AZ_HINTS: az_hints}})
  1732. def get_network_availability_zones(self, net_db):
  1733. if self._has_native_dhcp_metadata():
  1734. hints = az_validator.convert_az_string_to_list(
  1735. net_db[az_def.AZ_HINTS])
  1736. # When using the configured AZs, the az will always be the same
  1737. # as the hint (or default if none)
  1738. if hints:
  1739. az_name = hints[0]
  1740. else:
  1741. az_name = self.get_default_az().name
  1742. return [az_name]
  1743. else:
  1744. return []
  1745. def _get_router_az_obj(self, router):
  1746. l3_attrs_db.ExtraAttributesMixin._extend_extra_router_dict(
  1747. router, router)
  1748. return self.get_router_az(router)
  1749. def get_router_availability_zones(self, router):
  1750. """Return availability zones which a router belongs to."""
  1751. return [self._get_router_az_obj(router).name]
  1752. def _validate_availability_zones_forced(self, context, resource_type,
  1753. availability_zones):
  1754. return self.validate_availability_zones(context, resource_type,
  1755. availability_zones,
  1756. force=True)
  1757. def _list_availability_zones(self, context, filters=None):
  1758. # If no native_dhcp_metadata - use neutron AZs
  1759. if not self._has_native_dhcp_metadata():
  1760. return super(NsxPluginV3Base, self)._list_availability_zones(
  1761. context, filters=filters)
  1762. result = {}
  1763. for az in self._availability_zones_data.list_availability_zones():
  1764. # Add this availability zone as a network & router resource
  1765. if filters:
  1766. if 'name' in filters and az not in filters['name']:
  1767. continue
  1768. for res in ['network', 'router']:
  1769. if 'resource' not in filters or res in filters['resource']:
  1770. result[(az, res)] = True
  1771. return result
  1772. def validate_availability_zones(self, context, resource_type,
  1773. availability_zones, force=False):
  1774. # This method is called directly from this plugin but also from
  1775. # registered callbacks
  1776. if self._is_sub_plugin and not force:
  1777. # validation should be done together for both plugins
  1778. return
  1779. # If no native_dhcp_metadata - use neutron AZs
  1780. if not self._has_native_dhcp_metadata():
  1781. return super(NsxPluginV3Base, self).validate_availability_zones(
  1782. context, resource_type, availability_zones)
  1783. # Validate against the configured AZs
  1784. return self.validate_obj_azs(availability_zones)
  1785. def _ensure_nsxlib(self, feature):
  1786. if not self.nsxlib:
  1787. msg = (_("%s is not supported since passthough API is disabled") %
  1788. feature)
  1789. LOG.error(msg)
  1790. raise n_exc.InvalidInput(error_message=msg)
  1791. def _ensure_native_dhcp(self):
  1792. self._ensure_nsxlib("Native DHCP")
  1793. if not self._native_dhcp_enabled:
  1794. msg = (_("Native DHCP is not supported since dhcp_profile is not"
  1795. " provided in plugin configuration"))
  1796. LOG.error(msg)
  1797. raise n_exc.InvalidInput(error_message=msg)
  1798. def _get_net_dhcp_relay(self, context, net_id):
  1799. """Should be implemented by each plugin"""
  1800. pass
  1801. def _get_ipv6_subnet(self, context, network):
  1802. for subnet in network.subnets:
  1803. if subnet.ip_version == 6:
  1804. return subnet
  1805. def _validate_single_ipv6_subnet(self, context, network, subnet):
  1806. if subnet.get('ip_version') == 6:
  1807. if self._get_ipv6_subnet(context, network):
  1808. msg = (_("Only one ipv6 subnet per network is supported"))
  1809. LOG.error(msg)
  1810. raise n_exc.InvalidInput(error_message=msg)
  1811. def _subnet_with_native_dhcp(self, subnet, orig_subnet=None):
  1812. native_metadata = self._has_native_dhcp_metadata()
  1813. default_enable_dhcp = (orig_subnet.get('enable_dhcp', False)
  1814. if orig_subnet else False)
  1815. # DHCPv6 is not yet supported, but slaac is
  1816. # When configuring slaac, neutron requires the user
  1817. # to enable dhcp, however plugin code does not consider
  1818. # slaac as dhcp.
  1819. return (native_metadata and
  1820. subnet.get('enable_dhcp', default_enable_dhcp) and
  1821. subnet.get('ipv6_address_mode') != 'slaac')
  1822. def _validate_subnet_ip_version(self, subnet):
  1823. # This validation only needs to be called at create,
  1824. # since ip version and ipv6 mode attributes are read only
  1825. if subnet.get('ip_version') == 4:
  1826. # No dhcp restrictions for V4
  1827. return
  1828. enable_dhcp = subnet.get('enable_dhcp', False)
  1829. is_slaac = (subnet.get('ipv6_address_mode') == 'slaac')
  1830. if enable_dhcp and not is_slaac:
  1831. # No DHCPv6 support yet
  1832. msg = _("DHCPv6 is not supported")
  1833. LOG.error(msg)
  1834. raise n_exc.InvalidInput(error_message=msg)
  1835. def _create_subnet(self, context, subnet):
  1836. self._validate_number_of_subnet_static_routes(subnet)
  1837. self._validate_host_routes_input(subnet)
  1838. self._validate_subnet_ip_version(subnet['subnet'])
  1839. net_id = subnet['subnet']['network_id']
  1840. network = self._get_network(context, net_id)
  1841. self._validate_single_ipv6_subnet(context, network, subnet['subnet'])
  1842. # TODO(berlin): public external subnet announcement
  1843. if self._subnet_with_native_dhcp(subnet['subnet']):
  1844. self._validate_external_subnet(context, net_id)
  1845. self._ensure_native_dhcp()
  1846. lock = 'nsxv3_network_' + net_id
  1847. ddi_support, ddi_type = self._is_ddi_supported_on_net_with_type(
  1848. context, net_id, network=network)
  1849. with locking.LockManager.get_lock(lock):
  1850. # Check if it is on an overlay network and is the first
  1851. # DHCP-enabled subnet to create.
  1852. if ddi_support:
  1853. if self._has_no_dhcp_enabled_subnet(context, network):
  1854. created_subnet = super(
  1855. NsxPluginV3Base, self).create_subnet(context,
  1856. subnet)
  1857. try:
  1858. # This can be called only after the super create
  1859. # since we need the subnet pool to be translated
  1860. # to allocation pools
  1861. self._validate_address_space(
  1862. context, created_subnet)
  1863. except n_exc.InvalidInput:
  1864. # revert the subnet creation
  1865. with excutils.save_and_reraise_exception():
  1866. super(NsxPluginV3Base, self).delete_subnet(
  1867. context, created_subnet['id'])
  1868. self._extension_manager.process_create_subnet(context,
  1869. subnet['subnet'], created_subnet)
  1870. dhcp_relay = self._get_net_dhcp_relay(context, net_id)
  1871. if not dhcp_relay:
  1872. if self.nsxlib:
  1873. try:
  1874. self._enable_native_dhcp(context, network,
  1875. created_subnet)
  1876. except nsx_lib_exc.ManagerError:
  1877. with excutils.save_and_reraise_exception():
  1878. super(NsxPluginV3Base,
  1879. self).delete_subnet(
  1880. context, created_subnet['id'])
  1881. else:
  1882. msg = (_("Native DHCP is not supported since "
  1883. "passthough API is disabled"))
  1884. self._enable_native_dhcp(context, network,
  1885. created_subnet)
  1886. msg = None
  1887. else:
  1888. msg = (_("Can not create more than one DHCP-enabled "
  1889. "subnet in network %s") % net_id)
  1890. else:
  1891. msg = _("Native DHCP is not supported for %(type)s "
  1892. "network %(id)s") % {'id': net_id,
  1893. 'type': ddi_type}
  1894. if msg:
  1895. LOG.error(msg)
  1896. raise n_exc.InvalidInput(error_message=msg)
  1897. else:
  1898. created_subnet = super(NsxPluginV3Base, self).create_subnet(
  1899. context, subnet)
  1900. try:
  1901. # This can be called only after the super create
  1902. # since we need the subnet pool to be translated
  1903. # to allocation pools
  1904. self._validate_address_space(context, created_subnet)
  1905. except n_exc.InvalidInput:
  1906. # revert the subnet creation
  1907. with excutils.save_and_reraise_exception():
  1908. super(NsxPluginV3Base, self).delete_subnet(
  1909. context, created_subnet['id'])
  1910. return created_subnet
  1911. def _create_bulk_with_callback(self, resource, context, request_items,
  1912. post_create_func=None, rollback_func=None):
  1913. # This is a copy of the _create_bulk() in db_base_plugin_v2.py,
  1914. # but extended with user-provided callback functions.
  1915. objects = []
  1916. collection = "%ss" % resource
  1917. items = request_items[collection]
  1918. try:
  1919. with db_api.CONTEXT_WRITER.using(context):
  1920. for item in items:
  1921. obj_creator = getattr(self, 'create_%s' % resource)
  1922. obj = obj_creator(context, item)
  1923. objects.append(obj)
  1924. if post_create_func:
  1925. # The user-provided post_create function is called
  1926. # after a new object is created.
  1927. post_create_func(obj)
  1928. except Exception:
  1929. if rollback_func:
  1930. # The user-provided rollback function is called when an
  1931. # exception occurred.
  1932. for obj in objects:
  1933. rollback_func(obj)
  1934. # Note that the session.rollback() function is called here.
  1935. # session.rollback() will invoke transaction.rollback() on
  1936. # the transaction this session maintains. The latter will
  1937. # deactive the transaction and clear the session's cache.
  1938. #
  1939. # But depending on where the exception occurred,
  1940. # transaction.rollback() may have already been called
  1941. # internally before reaching here.
  1942. #
  1943. # For example, if the exception happened under a
  1944. # "with session.begin(subtransactions=True):" statement
  1945. # anywhere in the middle of processing obj_creator(),
  1946. # transaction.__exit__() will invoke transaction.rollback().
  1947. # Thus when the exception reaches here, the session's cache
  1948. # is already empty.
  1949. context.session.rollback()
  1950. with excutils.save_and_reraise_exception():
  1951. LOG.error("An exception occurred while creating "
  1952. "the %(resource)s:%(item)s",
  1953. {'resource': resource, 'item': item})
  1954. return objects
  1955. def _post_create_subnet(self, context, subnet):
  1956. LOG.debug("Collect native DHCP entries for network %s",
  1957. subnet['network_id'])
  1958. dhcp_service = nsx_db.get_nsx_service_binding(
  1959. context.session, subnet['network_id'], nsxlib_consts.SERVICE_DHCP)
  1960. if dhcp_service:
  1961. _net_id, nsx_port_id = nsx_db.get_nsx_switch_and_port_id(
  1962. context.session, dhcp_service['port_id'])
  1963. return {'nsx_port_id': nsx_port_id,
  1964. 'nsx_service_id': dhcp_service['nsx_service_id']}
  1965. def _rollback_subnet(self, subnet, dhcp_info):
  1966. LOG.debug("Rollback native DHCP entries for network %s",
  1967. subnet['network_id'])
  1968. if dhcp_info and self.nsxlib:
  1969. try:
  1970. self.nsxlib.logical_port.delete(dhcp_info['nsx_port_id'])
  1971. except Exception as e:
  1972. LOG.error("Failed to delete logical port %(id)s "
  1973. "during rollback. Exception: %(e)s",
  1974. {'id': dhcp_info['nsx_port_id'], 'e': e})
  1975. try:
  1976. self.nsxlib.dhcp_server.delete(dhcp_info['nsx_service_id'])
  1977. except Exception as e:
  1978. LOG.error("Failed to delete logical DHCP server %(id)s "
  1979. "during rollback. Exception: %(e)s",
  1980. {'id': dhcp_info['nsx_service_id'], 'e': e})
  1981. def create_subnet_bulk(self, context, subnets):
  1982. # Maintain a local cache here because when the rollback function
  1983. # is called, the cache in the session may have already been cleared.
  1984. _subnet_dhcp_info = {}
  1985. def _post_create(subnet):
  1986. if subnet['enable_dhcp']:
  1987. _subnet_dhcp_info[subnet['id']] = self._post_create_subnet(
  1988. context, subnet)
  1989. def _rollback(subnet):
  1990. if (subnet and subnet['enable_dhcp'] and
  1991. subnet['id'] in _subnet_dhcp_info):
  1992. self._rollback_subnet(subnet, _subnet_dhcp_info[subnet['id']])
  1993. del _subnet_dhcp_info[subnet['id']]
  1994. if self._has_native_dhcp_metadata():
  1995. return self._create_bulk_with_callback('subnet', context, subnets,
  1996. _post_create, _rollback)
  1997. else:
  1998. return self._create_bulk('subnet', context, subnets)
  1999. def _get_neutron_net_ids_by_nsx_id(self, context, nsx_id):
  2000. """Should be implemented by each plugin"""
  2001. pass
  2002. def _validate_number_of_subnet_static_routes(self, subnet_input):
  2003. s = subnet_input['subnet']
  2004. request_host_routes = (validators.is_attr_set(s.get('host_routes')) and
  2005. s['host_routes'])
  2006. num_allowed_on_backend = nsxlib_consts.MAX_STATIC_ROUTES
  2007. if request_host_routes:
  2008. if len(request_host_routes) > num_allowed_on_backend:
  2009. err_msg = (_(
  2010. "Number of static routes is limited at the backend to %("
  2011. "backend)s. Requested %(requested)s") %
  2012. {'backend': nsxlib_consts.MAX_STATIC_ROUTES,
  2013. 'requested': len(request_host_routes)})
  2014. raise n_exc.InvalidInput(error_message=err_msg)
  2015. def get_subnets(self, context, filters=None, fields=None, sorts=None,
  2016. limit=None, marker=None, page_reverse=False):
  2017. filters = filters or {}
  2018. lswitch_ids = filters.pop(as_providers.ADV_SERVICE_PROVIDERS, [])
  2019. if lswitch_ids:
  2020. # This is a request from Nova for metadata processing.
  2021. # Find the corresponding neutron network for each logical switch.
  2022. network_ids = filters.pop('network_id', [])
  2023. context = context.elevated()
  2024. for lswitch_id in lswitch_ids:
  2025. network_ids += self._get_neutron_net_ids_by_nsx_id(
  2026. context, lswitch_id)
  2027. filters['network_id'] = network_ids
  2028. return super(NsxPluginV3Base, self).get_subnets(
  2029. context, filters, fields, sorts, limit, marker, page_reverse)
  2030. def delete_subnet(self, context, subnet_id):
  2031. # TODO(berlin): cancel public external subnet announcement
  2032. if self._has_native_dhcp_metadata():
  2033. # Ensure that subnet is not deleted if attached to router.
  2034. self._subnet_check_ip_allocations_internal_router_ports(
  2035. context, subnet_id)
  2036. subnet = self.get_subnet(context, subnet_id)
  2037. if self._subnet_with_native_dhcp(subnet):
  2038. lock = 'nsxv3_network_' + subnet['network_id']
  2039. with locking.LockManager.get_lock(lock):
  2040. # Check if it is the last DHCP-enabled subnet to delete.
  2041. network = self._get_network(context, subnet['network_id'])
  2042. if self._has_single_dhcp_enabled_subnet(context, network):
  2043. try:
  2044. self._disable_native_dhcp(context, network['id'])
  2045. except Exception as e:
  2046. LOG.error("Failed to disable native DHCP for "
  2047. "network %(id)s. Exception: %(e)s",
  2048. {'id': network['id'], 'e': e})
  2049. super(NsxPluginV3Base, self).delete_subnet(
  2050. context, subnet_id)
  2051. return
  2052. super(NsxPluginV3Base, self).delete_subnet(context, subnet_id)
  2053. def _update_subnet(self, context, subnet_id, subnet):
  2054. updated_subnet = None
  2055. orig_subnet = self.get_subnet(context, subnet_id)
  2056. self._validate_number_of_subnet_static_routes(subnet)
  2057. self._validate_external_subnet(context, orig_subnet['network_id'])
  2058. self._validate_host_routes_input(
  2059. subnet,
  2060. orig_enable_dhcp=orig_subnet['enable_dhcp'],
  2061. orig_host_routes=orig_subnet['host_routes'])
  2062. network = self._get_network(context, orig_subnet['network_id'])
  2063. if (subnet['subnet'].get('ip_version') !=
  2064. orig_subnet.get('ip_version')):
  2065. self._validate_single_ipv6_subnet(
  2066. context, network, subnet['subnet'])
  2067. if self._has_native_dhcp_metadata():
  2068. enable_dhcp = self._subnet_with_native_dhcp(
  2069. subnet['subnet'], orig_subnet=orig_subnet)
  2070. orig_enable_dhcp = self._subnet_with_native_dhcp(orig_subnet)
  2071. if enable_dhcp != orig_enable_dhcp:
  2072. self._ensure_native_dhcp()
  2073. lock = 'nsxv3_network_' + orig_subnet['network_id']
  2074. with locking.LockManager.get_lock(lock):
  2075. if enable_dhcp:
  2076. (ddi_support,
  2077. ddi_type) = self._is_ddi_supported_on_net_with_type(
  2078. context, orig_subnet['network_id'],
  2079. network=network)
  2080. if ddi_support:
  2081. if self._has_no_dhcp_enabled_subnet(
  2082. context, network):
  2083. updated_subnet = super(
  2084. NsxPluginV3Base, self).update_subnet(
  2085. context, subnet_id, subnet)
  2086. self._extension_manager.process_update_subnet(
  2087. context, subnet['subnet'], updated_subnet)
  2088. self._enable_native_dhcp(context, network,
  2089. updated_subnet)
  2090. msg = None
  2091. else:
  2092. msg = (_("Multiple DHCP-enabled subnets is "
  2093. "not allowed in network %s") %
  2094. orig_subnet['network_id'])
  2095. else:
  2096. msg = (_("Native DHCP is not supported for "
  2097. "%(type)s network %(id)s") %
  2098. {'id': orig_subnet['network_id'],
  2099. 'type': ddi_type})
  2100. if msg:
  2101. LOG.error(msg)
  2102. raise n_exc.InvalidInput(error_message=msg)
  2103. elif self._has_single_dhcp_enabled_subnet(context,
  2104. network):
  2105. self._disable_native_dhcp(context, network['id'])
  2106. updated_subnet = super(
  2107. NsxPluginV3Base, self).update_subnet(
  2108. context, subnet_id, subnet)
  2109. self._extension_manager.process_update_subnet(
  2110. context, subnet['subnet'], updated_subnet)
  2111. if not updated_subnet:
  2112. updated_subnet = super(NsxPluginV3Base, self).update_subnet(
  2113. context, subnet_id, subnet)
  2114. self._extension_manager.process_update_subnet(
  2115. context, subnet['subnet'], updated_subnet)
  2116. # Check if needs to update logical DHCP server for native DHCP.
  2117. if self._subnet_with_native_dhcp(updated_subnet):
  2118. self._ensure_native_dhcp()
  2119. kwargs = {}
  2120. for key in ('dns_nameservers', 'gateway_ip', 'host_routes'):
  2121. if key in subnet['subnet']:
  2122. value = subnet['subnet'][key]
  2123. if value != orig_subnet[key]:
  2124. kwargs[key] = value
  2125. if key != 'dns_nameservers':
  2126. kwargs['options'] = None
  2127. if 'options' in kwargs:
  2128. sr, gw_ip = self.nsxlib.native_dhcp.build_static_routes(
  2129. updated_subnet.get('gateway_ip'),
  2130. updated_subnet.get('cidr'),
  2131. updated_subnet.get('host_routes', []))
  2132. kwargs['options'] = {'option121': {'static_routes': sr}}
  2133. kwargs.pop('host_routes', None)
  2134. if (gw_ip is not None and 'gateway_ip' not in kwargs and
  2135. gw_ip != updated_subnet['gateway_ip']):
  2136. kwargs['gateway_ip'] = gw_ip
  2137. if kwargs:
  2138. dhcp_service = nsx_db.get_nsx_service_binding(
  2139. context.session, orig_subnet['network_id'],
  2140. nsxlib_consts.SERVICE_DHCP)
  2141. if dhcp_service:
  2142. try:
  2143. self.nsxlib.dhcp_server.update(
  2144. dhcp_service['nsx_service_id'], **kwargs)
  2145. except nsx_lib_exc.ManagerError:
  2146. with excutils.save_and_reraise_exception():
  2147. LOG.error(
  2148. "Unable to update logical DHCP server "
  2149. "%(server)s for network %(network)s",
  2150. {'server': dhcp_service['nsx_service_id'],
  2151. 'network': orig_subnet['network_id']})
  2152. if 'options' in kwargs:
  2153. # Need to update the static binding of every VM in
  2154. # this logical DHCP server.
  2155. bindings = nsx_db.get_nsx_dhcp_bindings_by_service(
  2156. context.session, dhcp_service['nsx_service_id'])
  2157. for binding in bindings:
  2158. port = self._get_port(context, binding['port_id'])
  2159. dhcp_opts = port.get(ext_edo.EXTRADHCPOPTS)
  2160. self._update_dhcp_binding_on_server(
  2161. context, binding, port['mac_address'],
  2162. binding['ip_address'],
  2163. port['network_id'],
  2164. gateway_ip=kwargs.get('gateway_ip', False),
  2165. dhcp_opts=dhcp_opts,
  2166. options=kwargs.get('options'),
  2167. subnet=updated_subnet)
  2168. return updated_subnet
  2169. def _has_active_port(self, context, network_id):
  2170. ports_in_use = context.session.query(models_v2.Port).filter_by(
  2171. network_id=network_id).all()
  2172. return not all([p.device_owner in
  2173. db_base_plugin_v2.AUTO_DELETE_PORT_OWNERS
  2174. for p in ports_in_use]) if ports_in_use else False
  2175. def _delete_network_disable_dhcp(self, context, network_id):
  2176. # Disable native DHCP and delete DHCP ports before network deletion
  2177. lock = 'nsxv3_network_' + network_id
  2178. with locking.LockManager.get_lock(lock):
  2179. # Disable native DHCP if there is no other existing port
  2180. # besides DHCP port.
  2181. if not self._has_active_port(context, network_id):
  2182. self._disable_native_dhcp(context, network_id)
  2183. def _retry_delete_network(self, context, network_id):
  2184. """This method attempts to retry the delete on a network if there are
  2185. AUTO_DELETE_PORT_OWNERS left. This is to avoid a race condition
  2186. between delete_network and the dhcp creating a port on the network.
  2187. """
  2188. first_try = True
  2189. while True:
  2190. try:
  2191. with db_api.CONTEXT_WRITER.using(context):
  2192. self._process_l3_delete(context, network_id)
  2193. return super(NsxPluginV3Base, self).delete_network(
  2194. context, network_id)
  2195. except n_exc.NetworkInUse:
  2196. # There is a race condition in delete_network() that we need
  2197. # to work around here. delete_network() issues a query to
  2198. # automatically delete DHCP ports and then checks to see if any
  2199. # ports exist on the network. If a network is created and
  2200. # deleted quickly, such as when running tempest, the DHCP agent
  2201. # may be creating its port for the network around the same time
  2202. # that the network is deleted. This can result in the DHCP
  2203. # port getting created in between these two queries in
  2204. # delete_network(). To work around that, we'll call
  2205. # delete_network() a second time if we get a NetworkInUse
  2206. # exception but the only port(s) that exist are ones that
  2207. # delete_network() is supposed to automatically delete.
  2208. if not first_try:
  2209. # We tried once to work around the known race condition,
  2210. # but we still got the exception, so something else is
  2211. # wrong that we can't recover from.
  2212. raise
  2213. first_try = False
  2214. if self._has_active_port(context, network_id):
  2215. # There is a port on the network that is not going to be
  2216. # automatically deleted (such as a tenant created port), so
  2217. # we have nothing else to do but raise the exception.
  2218. raise
  2219. def _get_dhcp_options(self, context, ip, extra_dhcp_opts, net_id,
  2220. subnet):
  2221. # Always add option121.
  2222. net_az = self.get_network_az_by_net_id(context, net_id)
  2223. options = {'option121': {'static_routes': [
  2224. {'network': '%s' % net_az.native_metadata_route,
  2225. 'next_hop': '0.0.0.0'},
  2226. {'network': '%s' % net_az.native_metadata_route,
  2227. 'next_hop': ip}]}}
  2228. if subnet:
  2229. sr, gateway_ip = self.nsxlib.native_dhcp.build_static_routes(
  2230. subnet.get('gateway_ip'), subnet.get('cidr'),
  2231. subnet.get('host_routes', []))
  2232. options['option121']['static_routes'].extend(sr)
  2233. # Adding extra options only if configured on port
  2234. if extra_dhcp_opts:
  2235. other_opts = []
  2236. for opt in extra_dhcp_opts:
  2237. opt_name = opt['opt_name']
  2238. if opt['opt_value'] is not None:
  2239. # None value means - delete this option. Since we rebuild
  2240. # the options from scratch, it can be ignored.
  2241. opt_val = opt['opt_value']
  2242. if opt_name == 'classless-static-route':
  2243. # Add to the option121 static routes
  2244. net, ip = opt_val.split(',')
  2245. options['option121']['static_routes'].append({
  2246. 'network': net, 'next_hop': ip})
  2247. else:
  2248. other_opts.append({
  2249. 'code': nsxlib_utils.get_dhcp_opt_code(opt_name),
  2250. 'values': [opt_val]})
  2251. if other_opts:
  2252. options['others'] = other_opts
  2253. return options
  2254. def _update_dhcp_binding_on_server(self, context, binding, mac, ip,
  2255. net_id, gateway_ip=False,
  2256. dhcp_opts=None, options=None,
  2257. subnet=None):
  2258. try:
  2259. data = {'mac_address': mac, 'ip_address': ip}
  2260. if ip != binding['ip_address']:
  2261. data['host_name'] = 'host-%s' % ip.replace('.', '-')
  2262. data['options'] = self._get_dhcp_options(
  2263. context, ip, dhcp_opts, net_id,
  2264. subnet)
  2265. elif (dhcp_opts is not None or
  2266. options is not None):
  2267. data['options'] = self._get_dhcp_options(
  2268. context, ip, dhcp_opts, net_id,
  2269. subnet)
  2270. if gateway_ip is not False:
  2271. # Note that None is valid for gateway_ip, means deleting it.
  2272. data['gateway_ip'] = gateway_ip
  2273. self.nsxlib.dhcp_server.update_binding(
  2274. binding['nsx_service_id'], binding['nsx_binding_id'], **data)
  2275. LOG.debug("Updated static binding (mac: %(mac)s, ip: %(ip)s, "
  2276. "gateway: %(gateway)s) for port %(port)s on "
  2277. "logical DHCP server %(server)s",
  2278. {'mac': mac, 'ip': ip, 'gateway': gateway_ip,
  2279. 'port': binding['port_id'],
  2280. 'server': binding['nsx_service_id']})
  2281. except nsx_lib_exc.ManagerError:
  2282. with excutils.save_and_reraise_exception():
  2283. LOG.error("Unable to update static binding (mac: %(mac)s, "
  2284. "ip: %(ip)s, gateway: %(gateway)s) for port "
  2285. "%(port)s on logical DHCP server %(server)s",
  2286. {'mac': mac, 'ip': ip, 'gateway': gateway_ip,
  2287. 'port': binding['port_id'],
  2288. 'server': binding['nsx_service_id']})
  2289. def _validate_extra_dhcp_options(self, opts):
  2290. if not opts or not self._has_native_dhcp_metadata():
  2291. return
  2292. for opt in opts:
  2293. opt_name = opt['opt_name']
  2294. opt_val = opt['opt_value']
  2295. if opt_name == 'classless-static-route':
  2296. # separate validation for option121
  2297. if opt_val is not None:
  2298. try:
  2299. net, ip = opt_val.split(',')
  2300. except Exception:
  2301. msg = (_("Bad value %(val)s for DHCP option "
  2302. "%(name)s") % {'name': opt_name,
  2303. 'val': opt_val})
  2304. raise n_exc.InvalidInput(error_message=msg)
  2305. elif not nsxlib_utils.get_dhcp_opt_code(opt_name):
  2306. msg = (_("DHCP option %s is not supported") % opt_name)
  2307. raise n_exc.InvalidInput(error_message=msg)
  2308. def _is_vlan_router_interface_supported(self):
  2309. """Should be implemented by each plugin"""
  2310. def _is_ddi_supported_on_network(self, context, network_id, network=None):
  2311. result, _ = self._is_ddi_supported_on_net_with_type(
  2312. context, network_id, network=network)
  2313. return result
  2314. def _is_ddi_supported_on_net_with_type(self, context, network_id,
  2315. network=None):
  2316. # Get the network dictionary from the inputs
  2317. if network:
  2318. net = (network if isinstance(network, dict)
  2319. else self._translate_net_db_2_dict(context, network))
  2320. else:
  2321. net = self.get_network(context, network_id)
  2322. # NSX current does not support transparent VLAN ports for
  2323. # DHCP and metadata
  2324. if cfg.CONF.vlan_transparent:
  2325. if net.get('vlan_transparent') is True:
  2326. return False, "VLAN transparent"
  2327. # NSX current does not support flat network ports for
  2328. # DHCP and metadata
  2329. if net.get(pnet.NETWORK_TYPE) == utils.NsxV3NetworkTypes.FLAT:
  2330. return False, "flat"
  2331. # supported for overlay networks, and for vlan networks depending on
  2332. # NSX version
  2333. is_overlay = self._is_overlay_network(context, network_id)
  2334. net_type = "overlay" if is_overlay else "non-overlay"
  2335. return (is_overlay or
  2336. self._is_vlan_router_interface_supported()), net_type
  2337. def _has_no_dhcp_enabled_subnet(self, context, network):
  2338. # Check if there is no DHCP-enabled subnet in the network.
  2339. for subnet in network.subnets:
  2340. if subnet.enable_dhcp and subnet.ipv6_address_mode != 'slaac':
  2341. return False
  2342. return True
  2343. def _has_single_dhcp_enabled_subnet(self, context, network):
  2344. # Check if there is only one DHCP-enabled subnet in the network.
  2345. count = 0
  2346. for subnet in network.subnets:
  2347. if subnet.enable_dhcp and subnet.ip_version == 4:
  2348. count += 1
  2349. if count > 1:
  2350. return False
  2351. return True if count == 1 else False
  2352. def _cidrs_overlap(self, cidr0, cidr1):
  2353. return cidr0.first <= cidr1.last and cidr1.first <= cidr0.last
  2354. def _validate_address_space(self, context, subnet):
  2355. # get the subnet IPs
  2356. if ('allocation_pools' in subnet and
  2357. validators.is_attr_set(subnet['allocation_pools'])):
  2358. # use the pools instead of the cidr
  2359. subnet_networks = [
  2360. netaddr.IPRange(pool.get('start'), pool.get('end'))
  2361. for pool in subnet.get('allocation_pools')]
  2362. else:
  2363. cidr = subnet.get('cidr')
  2364. if not validators.is_attr_set(cidr):
  2365. return
  2366. subnet_networks = [netaddr.IPNetwork(subnet['cidr'])]
  2367. # Check if subnet overlaps with shared address space.
  2368. # This is checked on the backend when attaching subnet to a router.
  2369. shared_ips_cidrs = self._get_conf_attr('transit_networks')
  2370. for subnet_net in subnet_networks:
  2371. for shared_ips in shared_ips_cidrs:
  2372. if netaddr.IPSet(subnet_net) & netaddr.IPSet([shared_ips]):
  2373. msg = _("Subnet overlaps with shared address space "
  2374. "%s") % shared_ips
  2375. LOG.error(msg)
  2376. raise n_exc.InvalidInput(error_message=msg)
  2377. # Ensure that the NSX uplink cidr does not lie on the same subnet as
  2378. # the external subnet
  2379. filters = {'id': [subnet['network_id']],
  2380. 'router:external': [True]}
  2381. external_nets = self.get_networks(context, filters=filters)
  2382. tier0_routers = [ext_net[pnet.PHYSICAL_NETWORK]
  2383. for ext_net in external_nets
  2384. if ext_net.get(pnet.PHYSICAL_NETWORK)]
  2385. for tier0_rtr in set(tier0_routers):
  2386. tier0_cidrs = self._get_tier0_uplink_cidrs(tier0_rtr)
  2387. for cidr in tier0_cidrs:
  2388. tier0_subnet = netaddr.IPNetwork(cidr).cidr
  2389. for subnet_network in subnet_networks:
  2390. if self._cidrs_overlap(tier0_subnet, subnet_network):
  2391. msg = _("External subnet cannot overlap with T0 "
  2392. "router cidr %s") % cidr
  2393. LOG.error(msg)
  2394. raise n_exc.InvalidInput(error_message=msg)
  2395. def _need_router_no_dnat_rules(self, subnet):
  2396. # NAT is not supported for IPv6
  2397. return (subnet['ip_version'] == 4)
  2398. def _need_router_snat_rules(self, context, router_id, subnet,
  2399. gw_address_scope):
  2400. # NAT is not supported for IPv6
  2401. if subnet['ip_version'] != 4:
  2402. return False
  2403. # if the subnets address scope is the same as the gateways:
  2404. # no need for SNAT
  2405. if gw_address_scope:
  2406. subnet_address_scope = self._get_subnetpool_address_scope(
  2407. context, subnet['subnetpool_id'])
  2408. if (gw_address_scope == subnet_address_scope):
  2409. LOG.info("No need for SNAT rule for router %(router)s "
  2410. "and subnet %(subnet)s because they use the "
  2411. "same address scope %(addr_scope)s.",
  2412. {'router': router_id,
  2413. 'subnet': subnet['id'],
  2414. 'addr_scope': gw_address_scope})
  2415. return False
  2416. return True
  2417. def _get_mdproxy_port_name(self, net_name, net_id):
  2418. return utils.get_name_and_uuid('%s-%s' % ('mdproxy',
  2419. net_name or 'network'),
  2420. net_id)
  2421. def _create_net_mdproxy_port(self, context, network, az, nsx_net_id):
  2422. if (not self.nsxlib or
  2423. not self._has_native_dhcp_metadata()):
  2424. return
  2425. is_ddi_network = self._is_ddi_supported_on_network(
  2426. context, network['id'], network=network)
  2427. if is_ddi_network:
  2428. # Enable native metadata proxy for this network.
  2429. tags = self.nsxlib.build_v3_tags_payload(
  2430. network, resource_type='os-neutron-net-id',
  2431. project_name=context.tenant_name)
  2432. name = self._get_mdproxy_port_name(network['name'],
  2433. network['id'])
  2434. try:
  2435. md_port = self.nsxlib.logical_port.create(
  2436. nsx_net_id, az._native_md_proxy_uuid,
  2437. tags=tags, name=name,
  2438. attachment_type=nsxlib_consts.ATTACHMENT_MDPROXY)
  2439. except nsx_lib_exc.ResourceNotFound:
  2440. err_msg = (_('Logical switch %s or MD proxy %s do '
  2441. 'not exist') % (nsx_net_id,
  2442. az._native_md_proxy_uuid))
  2443. LOG.error(err_msg)
  2444. raise nsx_exc.NsxPluginException(err_msg=err_msg)
  2445. LOG.debug("Created MD-Proxy logical port %(port)s "
  2446. "for network %(network)s",
  2447. {'port': md_port['id'],
  2448. 'network': network['id']})
  2449. def _delete_nsx_port_by_network(self, network_id):
  2450. if not self.nsxlib:
  2451. return
  2452. port_id = self.nsxlib.get_id_by_resource_and_tag(
  2453. self.nsxlib.logical_port.resource_type,
  2454. 'os-neutron-net-id', network_id)
  2455. if port_id:
  2456. self.nsxlib.logical_port.delete(port_id)
  2457. def _support_vlan_router_interfaces(self):
  2458. """Should be implemented by each plugin"""
  2459. pass
  2460. def _validate_multiple_subnets_routers(self, context, router_id,
  2461. net_id, subnet):
  2462. network = self.get_network(context, net_id)
  2463. net_type = network.get(pnet.NETWORK_TYPE)
  2464. if (net_type and
  2465. not self._support_vlan_router_interfaces() and
  2466. not self._is_overlay_network(context, net_id)):
  2467. err_msg = (_("Only overlay networks can be attached to a logical "
  2468. "router. Network %(net_id)s is a %(net_type)s based "
  2469. "network") % {'net_id': net_id, 'net_type': net_type})
  2470. LOG.error(err_msg)
  2471. raise n_exc.InvalidInput(error_message=err_msg)
  2472. # Unable to attach a trunked network to a router interface
  2473. if cfg.CONF.vlan_transparent:
  2474. if network.get('vlan_transparent') is True:
  2475. err_msg = (_("Transparent VLAN networks cannot be attached to "
  2476. "a logical router."))
  2477. LOG.error(err_msg)
  2478. raise n_exc.InvalidInput(error_message=err_msg)
  2479. port_filters = {'device_owner': [l3_db.DEVICE_OWNER_ROUTER_INTF],
  2480. 'network_id': [net_id]}
  2481. intf_ports = self.get_ports(context.elevated(), filters=port_filters)
  2482. router_ids = [port['device_id']
  2483. for port in intf_ports if port['device_id']]
  2484. if len(router_ids) > 0:
  2485. err_msg = _("Only one subnet of each IP version in a network "
  2486. "%(net_id)s can be attached to router, one subnet "
  2487. "is already attached to router %(router_id)s") % {
  2488. 'net_id': net_id,
  2489. 'router_id': router_ids[0]}
  2490. if router_id in router_ids:
  2491. # We support 2 subnets from same net only for dual stack case
  2492. if not subnet:
  2493. # No IP provided on connected port
  2494. LOG.error(err_msg)
  2495. raise n_exc.InvalidInput(error_message=err_msg)
  2496. for port in intf_ports:
  2497. if port['device_id'] != router_id:
  2498. continue
  2499. if 'fixed_ips' in port and port['fixed_ips']:
  2500. ex_subnet = self.get_subnet(
  2501. context.elevated(),
  2502. port['fixed_ips'][0]['subnet_id'])
  2503. if ex_subnet['ip_version'] == subnet['ip_version']:
  2504. # attach to the same router with same IP version
  2505. LOG.error(err_msg)
  2506. raise n_exc.InvalidInput(error_message=err_msg)
  2507. else:
  2508. # attach to multiple routers
  2509. LOG.error(err_msg)
  2510. raise l3_exc.RouterInterfaceAttachmentConflict(reason=err_msg)
  2511. def _router_has_edge_fw_rules(self, context, router):
  2512. if not router.gw_port_id:
  2513. # No GW -> No rule on the edge firewall
  2514. return False
  2515. if self.fwaas_callbacks and self.fwaas_callbacks.fwaas_enabled:
  2516. ports = self._get_router_interfaces(context, router.id)
  2517. return self.fwaas_callbacks.router_with_fwg(context, ports)
  2518. def _get_tz_restricted_vlans(self, tz_uuid):
  2519. if not self.nsxlib:
  2520. return []
  2521. restricted_vlans = []
  2522. # Get all transport nodes of this transport zone
  2523. tns = self.nsxlib.transport_node.list()['results']
  2524. for tn in tns:
  2525. # Check if it belongs to the current TZ
  2526. tzs = [ep.get('transport_zone_id') for ep in
  2527. tn.get('transport_zone_endpoints', [])]
  2528. if tz_uuid not in tzs:
  2529. continue
  2530. if ('host_switch_spec' in tn and
  2531. 'host_switches' in tn['host_switch_spec']):
  2532. for hs in tn['host_switch_spec']['host_switches']:
  2533. profile_attrs = hs.get('host_switch_profile_ids', [])
  2534. for profile_attr in profile_attrs:
  2535. if profile_attr['key'] == 'UplinkHostSwitchProfile':
  2536. profile = self.nsxlib.host_switch_profiles.get(
  2537. profile_attr['value'])
  2538. vlan_id = profile.get('transport_vlan')
  2539. if vlan_id:
  2540. restricted_vlans.append(vlan_id)
  2541. return restricted_vlans
  2542. @api_replay_mode_wrapper
  2543. def _create_floating_ip_wrapper(self, context, floatingip):
  2544. initial_status = (constants.FLOATINGIP_STATUS_ACTIVE
  2545. if floatingip['floatingip']['port_id']
  2546. else constants.FLOATINGIP_STATUS_DOWN)
  2547. return super(NsxPluginV3Base, self).create_floatingip(
  2548. context, floatingip, initial_status=initial_status)
  2549. def _ensure_default_security_group(self, context, tenant_id):
  2550. # NOTE(arosen): if in replay mode we'll create all the default
  2551. # security groups for the user with their data so we don't
  2552. # want this to be called.
  2553. if not cfg.CONF.api_replay_mode:
  2554. return super(NsxPluginV3Base, self)._ensure_default_security_group(
  2555. context, tenant_id)
  2556. def _handle_api_replay_default_sg(self, context, secgroup_db):
  2557. """Set default api-replay migrated SG as default manually"""
  2558. if (secgroup_db['name'] == 'default'):
  2559. # this is a default security group copied from another cloud
  2560. # Ugly patch! mark it as default manually
  2561. with context.session.begin(subtransactions=True):
  2562. try:
  2563. default_entry = securitygroup_model.DefaultSecurityGroup(
  2564. security_group_id=secgroup_db['id'],
  2565. project_id=secgroup_db['project_id'])
  2566. context.session.add(default_entry)
  2567. except Exception as e:
  2568. LOG.error("Failed to mark migrated security group %(id)s "
  2569. "as default %(e)s",
  2570. {'id': secgroup_db['id'], 'e': e})
  2571. class TagsCallbacks(object):
  2572. target = oslo_messaging.Target(
  2573. namespace=None,
  2574. version='1.0')
  2575. def __init__(self, **kwargs):
  2576. super(TagsCallbacks, self).__init__()
  2577. def info(self, ctxt, publisher_id, event_type, payload, metadata):
  2578. # Make sure we catch only tags operations, and each one only once
  2579. # tagging events look like 'tag.create/delete.start/end'
  2580. if (event_type.startswith('tag.') and
  2581. event_type.endswith('.end')):
  2582. action = event_type.split('.')[1]
  2583. is_delete = (action == 'delete')
  2584. # Currently support only ports tags
  2585. if payload.get('parent_resource') == 'ports':
  2586. core_plugin = directory.get_plugin()
  2587. port_id = payload.get('parent_resource_id')
  2588. core_plugin.update_port_nsx_tags(ctxt, port_id,
  2589. payload.get('tags'),
  2590. is_delete=is_delete)