OpenStack Networking (Neutron)
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.
 
 
 
 

677 lines
30 KiB

  1. # Copyright 2016 Hewlett Packard Enterprise Development, LP
  2. #
  3. # All Rights Reserved.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  6. # not use this file except in compliance with the License. You may obtain
  7. # a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  13. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  14. # License for the specific language governing permissions and limitations
  15. # under the License.
  16. import copy
  17. from keystoneauth1 import loading as ks_loading
  18. import netaddr
  19. from neutron_lib.api.definitions import ip_allocation as ipalloc_apidef
  20. from neutron_lib.api.definitions import l2_adjacency as l2adj_apidef
  21. from neutron_lib.api.definitions import network as net_def
  22. from neutron_lib.api.definitions import port as port_def
  23. from neutron_lib.api.definitions import segment as seg_apidef
  24. from neutron_lib.api.definitions import segments_peer_subnet_host_routes
  25. from neutron_lib.api.definitions import standard_attr_segment
  26. from neutron_lib.api.definitions import subnet as subnet_def
  27. from neutron_lib.api.definitions import subnet_segmentid_writable
  28. from neutron_lib.api import validators
  29. from neutron_lib.callbacks import events
  30. from neutron_lib.callbacks import registry
  31. from neutron_lib.callbacks import resources
  32. from neutron_lib import constants
  33. from neutron_lib.db import resource_extend
  34. from neutron_lib import exceptions as n_exc
  35. from neutron_lib.exceptions import placement as placement_exc
  36. from neutron_lib.placement import client as placement_client
  37. from neutron_lib.plugins import directory
  38. from novaclient import client as nova_client
  39. from novaclient import exceptions as nova_exc
  40. from oslo_config import cfg
  41. from oslo_log import log
  42. from oslo_utils import excutils
  43. from neutron._i18n import _
  44. from neutron.common import ipv6_utils
  45. from neutron.extensions import segment
  46. from neutron.notifiers import batch_notifier
  47. from neutron.objects import network as net_obj
  48. from neutron.objects import ports as ports_obj
  49. from neutron.objects import subnet as subnet_obj
  50. from neutron.services.segments import db
  51. from neutron.services.segments import exceptions
  52. LOG = log.getLogger(__name__)
  53. NOVA_API_VERSION = '2.41'
  54. IPV4_RESOURCE_CLASS = 'IPV4_ADDRESS'
  55. SEGMENT_NAME_STUB = 'Neutron segment id %s'
  56. MAX_INVENTORY_UPDATE_RETRIES = 10
  57. @resource_extend.has_resource_extenders
  58. @registry.has_registry_receivers
  59. class Plugin(db.SegmentDbMixin, segment.SegmentPluginBase):
  60. _instance = None
  61. supported_extension_aliases = [seg_apidef.ALIAS,
  62. ipalloc_apidef.ALIAS,
  63. l2adj_apidef.ALIAS,
  64. standard_attr_segment.ALIAS,
  65. subnet_segmentid_writable.ALIAS,
  66. segments_peer_subnet_host_routes.ALIAS]
  67. __native_pagination_support = True
  68. __native_sorting_support = True
  69. __filter_validation_support = True
  70. def __init__(self):
  71. self.nova_updater = NovaSegmentNotifier()
  72. self.segment_host_routes = SegmentHostRoutes()
  73. @staticmethod
  74. @resource_extend.extends([net_def.COLLECTION_NAME])
  75. def _extend_network_dict_binding(network_res, network_db):
  76. if not directory.get_plugin('segments'):
  77. return
  78. # TODO(carl_baldwin) Make this work with service subnets when
  79. # it's a thing.
  80. is_adjacent = (not network_db.subnets or
  81. not network_db.subnets[0].segment_id)
  82. network_res[l2adj_apidef.L2_ADJACENCY] = is_adjacent
  83. @staticmethod
  84. @resource_extend.extends([subnet_def.COLLECTION_NAME])
  85. def _extend_subnet_dict_binding(subnet_res, subnet_db):
  86. subnet_res['segment_id'] = subnet_db.get('segment_id')
  87. @staticmethod
  88. @resource_extend.extends([port_def.COLLECTION_NAME])
  89. def _extend_port_dict_binding(port_res, port_db):
  90. if not directory.get_plugin('segments'):
  91. return
  92. value = ipalloc_apidef.IP_ALLOCATION_IMMEDIATE
  93. if port_db.get('ip_allocation'):
  94. value = port_db.get('ip_allocation')
  95. port_res[ipalloc_apidef.IP_ALLOCATION] = value
  96. @classmethod
  97. def get_instance(cls):
  98. if cls._instance is None:
  99. cls._instance = cls()
  100. return cls._instance
  101. @registry.receives(resources.SEGMENT, [events.BEFORE_DELETE])
  102. def _prevent_segment_delete_with_subnet_associated(
  103. self, resource, event, trigger, payload=None):
  104. """Raise exception if there are any subnets associated with segment."""
  105. if payload.metadata.get(db.FOR_NET_DELETE):
  106. # don't check if this is a part of a network delete operation
  107. return
  108. segment_id = payload.resource_id
  109. subnets = subnet_obj.Subnet.get_objects(payload.context,
  110. segment_id=segment_id)
  111. subnet_ids = [s.id for s in subnets]
  112. if subnet_ids:
  113. reason = _("The segment is still associated with subnet(s) "
  114. "%s") % ", ".join(subnet_ids)
  115. raise exceptions.SegmentInUse(segment_id=segment_id,
  116. reason=reason)
  117. @registry.receives(
  118. resources.SUBNET, [events.PRECOMMIT_DELETE_ASSOCIATIONS])
  119. def _validate_auto_address_subnet_delete(self, resource, event, trigger,
  120. payload):
  121. context = payload.context
  122. subnet = subnet_obj.Subnet.get_object(context, id=payload.resource_id)
  123. is_auto_addr_subnet = ipv6_utils.is_auto_address_subnet(subnet)
  124. if not is_auto_addr_subnet or subnet.segment_id is None:
  125. return
  126. ports = ports_obj.Port.get_ports_allocated_by_subnet_id(context,
  127. subnet.id)
  128. for port in ports:
  129. fixed_ips = [f for f in port.fixed_ips if f.subnet_id != subnet.id]
  130. if len(fixed_ips) != 0:
  131. continue
  132. LOG.info("Found port %(port_id)s, with IP auto-allocation "
  133. "only on subnet %(subnet)s which is associated with "
  134. "segment %(segment_id)s, cannot delete",
  135. {'port_id': port.id,
  136. 'subnet': subnet.id,
  137. 'segment_id': subnet.segment_id})
  138. raise n_exc.SubnetInUse(subnet_id=subnet.id)
  139. class Event(object):
  140. def __init__(self, method, segment_ids, total=None, reserved=None,
  141. segment_host_mappings=None, host=None):
  142. self.method = method
  143. if isinstance(segment_ids, set):
  144. self.segment_ids = segment_ids
  145. else:
  146. self.segment_id = segment_ids
  147. self.total = total
  148. self.reserved = reserved
  149. self.segment_host_mappings = segment_host_mappings
  150. self.host = host
  151. @registry.has_registry_receivers
  152. class NovaSegmentNotifier(object):
  153. def __init__(self):
  154. self.p_client, self.n_client = self._get_clients()
  155. self.batch_notifier = batch_notifier.BatchNotifier(
  156. cfg.CONF.send_events_interval, self._send_notifications)
  157. def _get_clients(self):
  158. p_client = placement_client.PlacementAPIClient(
  159. cfg.CONF, openstack_api_version='placement 1.1')
  160. n_auth = ks_loading.load_auth_from_conf_options(cfg.CONF, 'nova')
  161. n_session = ks_loading.load_session_from_conf_options(
  162. cfg.CONF,
  163. 'nova',
  164. auth=n_auth)
  165. extensions = [
  166. ext for ext in nova_client.discover_extensions(NOVA_API_VERSION)
  167. if ext.name == "server_external_events"]
  168. n_client = nova_client.Client(
  169. NOVA_API_VERSION,
  170. session=n_session,
  171. region_name=cfg.CONF.nova.region_name,
  172. endpoint_type=cfg.CONF.nova.endpoint_type,
  173. extensions=extensions)
  174. return p_client, n_client
  175. def _send_notifications(self, batched_events):
  176. for event in batched_events:
  177. try:
  178. event.method(event)
  179. except placement_exc.PlacementEndpointNotFound:
  180. LOG.debug('Placement API was not found when trying to '
  181. 'update routed networks IPv4 inventories')
  182. return
  183. def _notify_subnet(self, context, subnet, segment_id):
  184. total, reserved = self._calculate_inventory_total_and_reserved(subnet)
  185. if total:
  186. segment_host_mappings = net_obj.SegmentHostMapping.get_objects(
  187. context, segment_id=segment_id)
  188. self.batch_notifier.queue_event(Event(
  189. self._create_or_update_nova_inventory, segment_id, total=total,
  190. reserved=reserved,
  191. segment_host_mappings=segment_host_mappings))
  192. @registry.receives(resources.SUBNET, [events.AFTER_CREATE])
  193. def _notify_subnet_created(self, resource, event, trigger, context,
  194. subnet, **kwargs):
  195. segment_id = subnet.get('segment_id')
  196. if not segment_id or subnet['ip_version'] != constants.IP_VERSION_4:
  197. return
  198. self._notify_subnet(context, subnet, segment_id)
  199. def _create_or_update_nova_inventory(self, event):
  200. try:
  201. self._update_nova_inventory(event)
  202. except placement_exc.PlacementResourceProviderNotFound:
  203. self._create_nova_inventory(event.segment_id, event.total,
  204. event.reserved,
  205. event.segment_host_mappings)
  206. def _update_nova_inventory(self, event):
  207. for count in range(MAX_INVENTORY_UPDATE_RETRIES):
  208. ipv4_inventory = self.p_client.get_inventory(event.segment_id,
  209. IPV4_RESOURCE_CLASS)
  210. if event.total:
  211. ipv4_inventory['total'] += event.total
  212. if event.reserved:
  213. ipv4_inventory['reserved'] += event.reserved
  214. try:
  215. self.p_client.update_resource_provider_inventory(
  216. event.segment_id, ipv4_inventory, IPV4_RESOURCE_CLASS)
  217. return
  218. except placement_exc.PlacementResourceProviderGenerationConflict:
  219. LOG.debug('Re-trying to update Nova IPv4 inventory for '
  220. 'routed network segment: %s', event.segment_id)
  221. LOG.error('Failed to update Nova IPv4 inventory for routed '
  222. 'network segment: %s', event.segment_id)
  223. def _get_nova_aggregate_uuid(self, aggregate):
  224. try:
  225. return aggregate.uuid
  226. except AttributeError:
  227. with excutils.save_and_reraise_exception():
  228. LOG.exception("uuid was not returned as part of the aggregate "
  229. "object which indicates that the Nova API "
  230. "backend does not support microversions. Ensure "
  231. "that the compute endpoint in the service "
  232. "catalog points to the v2.1 API.")
  233. def _create_nova_inventory(self, segment_id, total, reserved,
  234. segment_host_mappings):
  235. name = SEGMENT_NAME_STUB % segment_id
  236. resource_provider = {'name': name, 'uuid': segment_id}
  237. self.p_client.create_resource_provider(resource_provider)
  238. aggregate = self.n_client.aggregates.create(name, None)
  239. aggregate_uuid = self._get_nova_aggregate_uuid(aggregate)
  240. self.p_client.associate_aggregates(segment_id, [aggregate_uuid])
  241. for mapping in segment_host_mappings:
  242. self.n_client.aggregates.add_host(aggregate.id, mapping.host)
  243. ipv4_inventory = {
  244. IPV4_RESOURCE_CLASS: {
  245. 'total': total, 'reserved': reserved, 'min_unit': 1,
  246. 'max_unit': 1, 'step_size': 1, 'allocation_ratio': 1.0,
  247. }
  248. }
  249. self.p_client.update_resource_provider_inventories(
  250. segment_id, ipv4_inventory)
  251. def _calculate_inventory_total_and_reserved(self, subnet):
  252. total = 0
  253. reserved = 0
  254. allocation_pools = subnet.get('allocation_pools') or []
  255. for pool in allocation_pools:
  256. total += int(netaddr.IPAddress(pool['end']) -
  257. netaddr.IPAddress(pool['start'])) + 1
  258. if total:
  259. if subnet.get('gateway_ip'):
  260. total += 1
  261. reserved += 1
  262. if subnet.get('enable_dhcp'):
  263. reserved += 1
  264. return total, reserved
  265. @registry.receives(resources.SUBNET, [events.AFTER_UPDATE])
  266. def _notify_subnet_updated(self, resource, event, trigger, context,
  267. subnet, original_subnet, **kwargs):
  268. segment_id = subnet.get('segment_id')
  269. original_segment_id = original_subnet.get('segment_id')
  270. if not segment_id or subnet['ip_version'] != constants.IP_VERSION_4:
  271. return
  272. if original_segment_id != segment_id:
  273. # Migration to routed network, treat as create
  274. self._notify_subnet(context, subnet, segment_id)
  275. return
  276. filters = {'segment_id': [segment_id],
  277. 'ip_version': [constants.IP_VERSION_4]}
  278. if not subnet['allocation_pools']:
  279. plugin = directory.get_plugin()
  280. alloc_pools = [s['allocation_pools'] for s in
  281. plugin.get_subnets(context, filters=filters)]
  282. if not any(alloc_pools):
  283. self.batch_notifier.queue_event(Event(
  284. self._delete_nova_inventory, segment_id))
  285. return
  286. original_total, original_reserved = (
  287. self._calculate_inventory_total_and_reserved(original_subnet))
  288. updated_total, updated_reserved = (
  289. self._calculate_inventory_total_and_reserved(subnet))
  290. total = updated_total - original_total
  291. reserved = updated_reserved - original_reserved
  292. if total or reserved:
  293. segment_host_mappings = None
  294. if not original_subnet['allocation_pools']:
  295. segment_host_mappings = net_obj.SegmentHostMapping.get_objects(
  296. context, segment_id=segment_id)
  297. self.batch_notifier.queue_event(Event(
  298. self._create_or_update_nova_inventory, segment_id, total=total,
  299. reserved=reserved,
  300. segment_host_mappings=segment_host_mappings))
  301. @registry.receives(resources.SUBNET, [events.AFTER_DELETE])
  302. def _notify_subnet_deleted(self, resource, event, trigger, context,
  303. subnet, **kwargs):
  304. if kwargs.get(db.FOR_NET_DELETE):
  305. return # skip segment RP update if it is going to be deleted
  306. segment_id = subnet.get('segment_id')
  307. if not segment_id or subnet['ip_version'] != constants.IP_VERSION_4:
  308. return
  309. total, reserved = self._calculate_inventory_total_and_reserved(subnet)
  310. if total:
  311. filters = {'segment_id': [segment_id], 'ip_version': [4]}
  312. plugin = directory.get_plugin()
  313. if plugin.get_subnets_count(context, filters=filters) > 0:
  314. self.batch_notifier.queue_event(Event(
  315. self._update_nova_inventory, segment_id, total=-total,
  316. reserved=-reserved))
  317. else:
  318. self.batch_notifier.queue_event(Event(
  319. self._delete_nova_inventory, segment_id))
  320. def _get_aggregate_id(self, segment_id):
  321. try:
  322. aggregate_uuid = self.p_client.list_aggregates(
  323. segment_id)['aggregates'][0]
  324. except placement_exc.PlacementAggregateNotFound:
  325. LOG.info('Segment %s resource provider aggregate not found',
  326. segment_id)
  327. return
  328. for aggregate in self.n_client.aggregates.list():
  329. nc_aggregate_uuid = self._get_nova_aggregate_uuid(aggregate)
  330. if nc_aggregate_uuid == aggregate_uuid:
  331. return aggregate.id
  332. def _delete_nova_inventory(self, event):
  333. aggregate_id = self._get_aggregate_id(event.segment_id)
  334. if aggregate_id:
  335. aggregate = self.n_client.aggregates.get_details(aggregate_id)
  336. for host in aggregate.hosts:
  337. self.n_client.aggregates.remove_host(aggregate_id, host)
  338. self.n_client.aggregates.delete(aggregate_id)
  339. try:
  340. self.p_client.delete_resource_provider(event.segment_id)
  341. except placement_exc.PlacementClientError as exc:
  342. LOG.info('Segment %s resource provider not found; error: %s',
  343. event.segment_id, str(exc))
  344. @registry.receives(resources.SEGMENT_HOST_MAPPING, [events.AFTER_CREATE])
  345. def _notify_host_addition_to_aggregate(self, resource, event, trigger,
  346. payload=None):
  347. subnets = subnet_obj.Subnet.get_objects(
  348. payload.context,
  349. segment_id=payload.metadata.get('current_segment_ids'))
  350. segment_ids = {s.segment_id for s in subnets}
  351. self.batch_notifier.queue_event(
  352. Event(self._add_host_to_aggregate,
  353. segment_ids, host=payload.metadata.get('host')))
  354. def _add_host_to_aggregate(self, event):
  355. for segment_id in event.segment_ids:
  356. aggregate_id = self._get_aggregate_id(segment_id)
  357. if not aggregate_id:
  358. LOG.info('When adding host %(host)s, aggregate not found '
  359. 'for routed network segment %(segment_id)s',
  360. {'host': event.host, 'segment_id': segment_id})
  361. continue
  362. try:
  363. self.n_client.aggregates.add_host(aggregate_id, event.host)
  364. except nova_exc.Conflict:
  365. LOG.info('Host %(host)s already exists in aggregate for '
  366. 'routed network segment %(segment_id)s',
  367. {'host': event.host, 'segment_id': segment_id})
  368. @registry.receives(resources.PORT,
  369. [events.AFTER_CREATE, events.AFTER_DELETE])
  370. def _notify_port_created_or_deleted(self, resource, event, trigger,
  371. context, port, **kwargs):
  372. if not self._does_port_require_nova_inventory_update(port):
  373. return
  374. ipv4_subnets_number, segment_id = (
  375. self._get_ipv4_subnets_number_and_segment_id(port, context))
  376. if segment_id:
  377. if event == events.AFTER_DELETE:
  378. ipv4_subnets_number = -ipv4_subnets_number
  379. self.batch_notifier.queue_event(
  380. Event(self._update_nova_inventory,
  381. segment_id, reserved=ipv4_subnets_number))
  382. @registry.receives(resources.PORT, [events.AFTER_UPDATE])
  383. def _notify_port_updated(self, resource, event, trigger, context,
  384. **kwargs):
  385. port = kwargs.get('port')
  386. original_port = kwargs.get('original_port')
  387. does_original_port_require_nova_inventory_update = (
  388. self._does_port_require_nova_inventory_update(original_port))
  389. does_port_require_nova_inventory_update = (
  390. self._does_port_require_nova_inventory_update(port))
  391. if not (does_original_port_require_nova_inventory_update or
  392. does_port_require_nova_inventory_update):
  393. return
  394. original_port_ipv4_subnets_number, segment_id = (
  395. self._get_ipv4_subnets_number_and_segment_id(original_port,
  396. context))
  397. if not segment_id:
  398. return
  399. port_ipv4_subnets_number = len(self._get_ipv4_subnet_ids(port))
  400. if not does_original_port_require_nova_inventory_update:
  401. original_port_ipv4_subnets_number = 0
  402. if not does_port_require_nova_inventory_update:
  403. port_ipv4_subnets_number = 0
  404. update = port_ipv4_subnets_number - original_port_ipv4_subnets_number
  405. if update:
  406. self.batch_notifier.queue_event(Event(self._update_nova_inventory,
  407. segment_id, reserved=update))
  408. def _get_ipv4_subnets_number_and_segment_id(self, port, context):
  409. ipv4_subnet_ids = self._get_ipv4_subnet_ids(port)
  410. if not ipv4_subnet_ids:
  411. return 0, None
  412. subnet = subnet_obj.Subnet.get_object(context, id=ipv4_subnet_ids[0])
  413. if subnet and subnet.segment_id:
  414. return len(ipv4_subnet_ids), subnet.segment_id
  415. return 0, None
  416. def _does_port_require_nova_inventory_update(self, port):
  417. device_owner = port.get('device_owner')
  418. if (device_owner.startswith(constants.DEVICE_OWNER_COMPUTE_PREFIX) or
  419. device_owner == constants.DEVICE_OWNER_DHCP):
  420. return False
  421. return True
  422. def _get_ipv4_subnet_ids(self, port):
  423. ipv4_subnet_ids = []
  424. for ip in port.get('fixed_ips', []):
  425. if netaddr.IPAddress(
  426. ip['ip_address']).version == constants.IP_VERSION_4:
  427. ipv4_subnet_ids.append(ip['subnet_id'])
  428. return ipv4_subnet_ids
  429. @registry.receives(resources.SEGMENT, [events.AFTER_DELETE])
  430. def _notify_segment_deleted(
  431. self, resource, event, trigger, payload=None):
  432. if payload:
  433. self.batch_notifier.queue_event(Event(
  434. self._delete_nova_inventory, payload.resource_id))
  435. @registry.has_registry_receivers
  436. class SegmentHostRoutes(object):
  437. def _get_subnets(self, context, network_id):
  438. return subnet_obj.Subnet.get_objects(context, network_id=network_id)
  439. def _count_subnets(self, context, network_id):
  440. return subnet_obj.Subnet.count(context, network_id=network_id)
  441. def _calculate_routed_network_host_routes(self, context, ip_version,
  442. network_id=None, subnet_id=None,
  443. segment_id=None,
  444. host_routes=None,
  445. gateway_ip=None,
  446. old_gateway_ip=None,
  447. deleted_cidr=None):
  448. """Calculate host routes for routed network.
  449. This method is used to calculate the host routes for routed networks
  450. both when handling the user create or update request and when making
  451. updates to subnets on the network in response to events: AFTER_CREATE
  452. and AFTER_DELETE.
  453. :param ip_version: IP version (4/6).
  454. :param network_id: Network ID.
  455. :param subnet_id: UUID of the subnet.
  456. :param segment_id: Segement ID associated with the subnet.
  457. :param host_routes: Current host_routes of the subnet.
  458. :param gateway_ip: The subnets gateway IP address.
  459. :param old_gateway_ip: The old gateway IP address of the subnet when it
  460. is changed on update.
  461. :param deleted_cidr: The cidr of a deleted subnet.
  462. :returns Host routes with routes for the other subnet's on the routed
  463. network appended unless a route to the destination already
  464. exists.
  465. """
  466. if host_routes is None:
  467. host_routes = []
  468. dest_ip_nets = [netaddr.IPNetwork(route['destination']) for
  469. route in host_routes]
  470. # Drop routes to the deleted cidr, when the subnet was deleted.
  471. if deleted_cidr:
  472. delete_route = {'destination': deleted_cidr, 'nexthop': gateway_ip}
  473. if delete_route in host_routes:
  474. host_routes.remove(delete_route)
  475. for subnet in self._get_subnets(context, network_id):
  476. if (subnet.id == subnet_id or subnet.segment_id == segment_id or
  477. subnet.ip_version != ip_version):
  478. continue
  479. subnet_ip_net = netaddr.IPNetwork(subnet.cidr)
  480. if old_gateway_ip:
  481. old_route = {'destination': str(subnet.cidr),
  482. 'nexthop': old_gateway_ip}
  483. if old_route in host_routes:
  484. host_routes.remove(old_route)
  485. dest_ip_nets.remove(subnet_ip_net)
  486. if gateway_ip:
  487. # Use netaddr here in case the user provided a summary route
  488. # (supernet route). I.e subnet.cidr = 10.0.1.0/24 and
  489. # the user provided a host route for 10.0.0.0/16. We don't
  490. # need to append a route in this case.
  491. if not any(subnet_ip_net in ip_net for ip_net in dest_ip_nets):
  492. host_routes.append({'destination': subnet.cidr,
  493. 'nexthop': gateway_ip})
  494. return host_routes
  495. def _host_routes_need_update(self, host_routes, calc_host_routes):
  496. """Compare host routes and calculated host routes
  497. :param host_routes: Current host routes
  498. :param calc_host_routes: Host routes + calculated host routes for
  499. routed network
  500. :returns True if host_routes and calc_host_routes are not equal
  501. """
  502. return ((set((route['destination'],
  503. route['nexthop']) for route in host_routes) !=
  504. set((route['destination'],
  505. route['nexthop']) for route in calc_host_routes)))
  506. def _update_routed_network_host_routes(self, context, network_id,
  507. deleted_cidr=None):
  508. """Update host routes on subnets on a routed network after event
  509. Host routes on the subnets on a routed network may need updates after
  510. any CREATE or DELETE event.
  511. :param network_id: Network ID
  512. :param deleted_cidr: The cidr of a deleted subnet.
  513. """
  514. for subnet in self._get_subnets(context, network_id):
  515. host_routes = [{'destination': str(route.destination),
  516. 'nexthop': route.nexthop}
  517. for route in subnet.host_routes]
  518. calc_host_routes = self._calculate_routed_network_host_routes(
  519. context=context,
  520. ip_version=subnet.ip_version,
  521. network_id=subnet.network_id,
  522. subnet_id=subnet.id,
  523. segment_id=subnet.segment_id,
  524. host_routes=copy.deepcopy(host_routes),
  525. gateway_ip=subnet.gateway_ip,
  526. deleted_cidr=deleted_cidr)
  527. if self._host_routes_need_update(host_routes, calc_host_routes):
  528. LOG.debug(
  529. "Updating host routes for subnet %s on routed network %s",
  530. subnet.id, subnet.network_id)
  531. plugin = directory.get_plugin()
  532. plugin.update_subnet(context, subnet.id,
  533. {'subnet': {
  534. 'host_routes': calc_host_routes}})
  535. @registry.receives(resources.SUBNET, [events.BEFORE_CREATE])
  536. def host_routes_before_create(self, resource, event, trigger, context,
  537. subnet, **kwargs):
  538. segment_id = subnet.get('segment_id')
  539. gateway_ip = subnet.get('gateway_ip')
  540. if validators.is_attr_set(subnet.get('host_routes')):
  541. host_routes = subnet.get('host_routes')
  542. else:
  543. host_routes = []
  544. if segment_id is not None and validators.is_attr_set(gateway_ip):
  545. calc_host_routes = self._calculate_routed_network_host_routes(
  546. context=context,
  547. ip_version=netaddr.IPNetwork(subnet['cidr']).version,
  548. network_id=subnet['network_id'],
  549. segment_id=segment_id,
  550. host_routes=copy.deepcopy(host_routes),
  551. gateway_ip=gateway_ip)
  552. if (not host_routes or
  553. self._host_routes_need_update(host_routes,
  554. calc_host_routes)):
  555. subnet['host_routes'] = calc_host_routes
  556. @registry.receives(resources.SUBNET, [events.BEFORE_UPDATE])
  557. def host_routes_before_update(self, resource, event, trigger, **kwargs):
  558. context = kwargs['context']
  559. subnet, original_subnet = kwargs['request'], kwargs['original_subnet']
  560. orig_segment_id = original_subnet.get('segment_id')
  561. segment_id = subnet.get('segment_id', orig_segment_id)
  562. orig_gateway_ip = original_subnet.get('gateway_ip')
  563. gateway_ip = subnet.get('gateway_ip', orig_gateway_ip)
  564. orig_host_routes = original_subnet.get('host_routes')
  565. host_routes = subnet.get('host_routes', orig_host_routes)
  566. if (segment_id and (host_routes != orig_host_routes or
  567. gateway_ip != orig_gateway_ip)):
  568. calc_host_routes = self._calculate_routed_network_host_routes(
  569. context=context,
  570. ip_version=netaddr.IPNetwork(original_subnet['cidr']).version,
  571. network_id=original_subnet['network_id'],
  572. segment_id=segment_id,
  573. host_routes=copy.deepcopy(host_routes),
  574. gateway_ip=gateway_ip,
  575. old_gateway_ip=orig_gateway_ip if (
  576. gateway_ip != orig_gateway_ip) else None)
  577. if self._host_routes_need_update(host_routes, calc_host_routes):
  578. subnet['host_routes'] = calc_host_routes
  579. @registry.receives(resources.SUBNET, [events.AFTER_CREATE])
  580. def host_routes_after_create(self, resource, event, trigger, **kwargs):
  581. context = kwargs['context']
  582. subnet = kwargs['subnet']
  583. # If there are other subnets on the network and subnet has segment_id
  584. # ensure host routes for all subnets are updated.
  585. if (self._count_subnets(context, subnet['network_id']) > 1 and
  586. subnet.get('segment_id')):
  587. self._update_routed_network_host_routes(context,
  588. subnet['network_id'])
  589. @registry.receives(resources.SUBNET, [events.AFTER_DELETE])
  590. def host_routes_after_delete(self, resource, event, trigger, context,
  591. subnet, **kwargs):
  592. # If this is a routed network, remove any routes to this subnet on
  593. # this networks remaining subnets.
  594. if kwargs.get(db.FOR_NET_DELETE):
  595. return # skip subnet update if the network is going to be deleted
  596. if subnet.get('segment_id'):
  597. self._update_routed_network_host_routes(
  598. context, subnet['network_id'], deleted_cidr=subnet['cidr'])