Skip to content

Commit c784856

Browse files
authored
Merge pull request #44 from stackhpc/upstream/yoga-2023-05-08
Synchronise yoga with upstream
2 parents fb2310a + 3b65c1a commit c784856

File tree

16 files changed

+229
-47
lines changed

16 files changed

+229
-47
lines changed

doc/source/admin/config-dhcp-ha.rst

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,38 @@ To test the HA of DHCP agent:
442442

443443
#. Start DHCP agent on HostB. The VM gets the wanted IP again.
444444

445+
No HA for metadata service on isolated networks
446+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
447+
448+
All Neutron backends using the DHCP agent can also provide `metadata service
449+
<https://docs.openstack.org/nova/latest/user/metadata.html>`_ in isolated
450+
networks (i.e. networks without a router). In this case the DHCP agent manages
451+
the metadata service (see config option `enable_isolated_metadata
452+
<https://docs.openstack.org/neutron/latest/configuration/dhcp-agent.html#DEFAULT.enable_isolated_metadata>`_).
453+
454+
Note however that the metadata service is only redundant for IPv4, and not
455+
IPv6, even when the DHCP service is configured to be highly available
456+
(config option `dhcp_agents_per_network
457+
<https://docs.openstack.org/neutron/latest/configuration/neutron.html#DEFAULT.dhcp_agents_per_network>`_
458+
> 1). This is because the DHCP agent will insert a route to the well known
459+
metadata IPv4 address (`169.254.169.254`) via its own IP address, so it will
460+
be reachable as long as the DHCP service is available at that IP address.
461+
This also means that recovery after a failure is tied to the renewal of the
462+
DHCP lease, since that route will only change if the DHCP server for a VM
463+
changes.
464+
465+
With IPv6, the well known metadata IPv6 address (`fe80::a9fe:a9fe`) is used,
466+
but directly configured in the DHCP agent network namespace.
467+
Due to the enforcement of duplicate address detection (DAD), this address
468+
can only be configured in at most one DHCP network namespaces at any time.
469+
See `RFC 4862 <https://www.rfc-editor.org/rfc/rfc4862#section-5.4>`_ for
470+
details on the DAD process.
471+
472+
For this reason, even when you have multiple DHCP agents, an arbitrary one
473+
(where the metadata IPv6 address is not in `dadfailed` state) will serve all
474+
metadata requests over IPv6. When that metadata service instance becomes
475+
unreachable there is no failover and the service will become unreachable.
476+
445477
Disabling and removing an agent
446478
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
447479

neutron/agent/linux/dhcp.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
from neutron.agent.linux import ip_lib
4141
from neutron.agent.linux import iptables_manager
4242
from neutron.cmd import runtime_checks as checks
43+
from neutron.common import _constants as common_constants
4344
from neutron.common import utils as common_utils
4445
from neutron.ipam import utils as ipam_utils
4546
from neutron.privileged.agent.linux import dhcp as priv_dhcp
@@ -1773,7 +1774,7 @@ def setup(self, network):
17731774
if self.conf.force_metadata or self.conf.enable_isolated_metadata:
17741775
ip_cidrs.append(constants.METADATA_CIDR)
17751776
if netutils.is_ipv6_enabled():
1776-
ip_cidrs.append(constants.METADATA_V6_CIDR)
1777+
ip_cidrs.append(common_constants.METADATA_V6_CIDR)
17771778

17781779
self.driver.init_l3(interface_name, ip_cidrs,
17791780
namespace=network.namespace)

neutron/agent/linux/ip_lib.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ class AddressNotReady(exceptions.NeutronException):
103103
"become ready: %(reason)s")
104104

105105

106+
class DADFailed(AddressNotReady):
107+
pass
108+
109+
106110
InvalidArgument = privileged.InvalidArgument
107111

108112

@@ -581,7 +585,7 @@ def wait_until_address_ready(self, address, wait_time=30):
581585
"""Wait until an address is no longer marked 'tentative' or 'dadfailed'
582586
583587
raises AddressNotReady if times out, address not present on interface
584-
or DAD fails
588+
raises DADFailed if Duplicate Address Detection fails
585589
"""
586590
def is_address_ready():
587591
try:
@@ -593,7 +597,7 @@ def is_address_ready():
593597
# Since both 'dadfailed' and 'tentative' will be set if DAD fails,
594598
# check 'dadfailed' first just to be explicit
595599
if addr_info['dadfailed']:
596-
raise AddressNotReady(
600+
raise DADFailed(
597601
address=address, reason=_('Duplicate address detected'))
598602
if addr_info['tentative']:
599603
return False

neutron/agent/metadata/driver.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
from neutron.agent.linux import external_process
3434
from neutron.agent.linux import ip_lib
3535
from neutron.agent.linux import utils as linux_utils
36+
from neutron.common import _constants as common_constants
3637
from neutron.common import coordination
3738
from neutron.common import utils as common_utils
3839

@@ -266,9 +267,30 @@ def spawn_monitored_metadata_proxy(cls, monitor, ns_name, port, conf,
266267
# HAProxy cannot bind() until IPv6 Duplicate Address Detection
267268
# completes. We must wait until the address leaves its 'tentative'
268269
# state.
269-
ip_lib.IpAddrCommand(
270-
parent=ip_lib.IPDevice(name=bind_interface, namespace=ns_name)
271-
).wait_until_address_ready(address=bind_address_v6)
270+
try:
271+
ip_lib.IpAddrCommand(
272+
parent=ip_lib.IPDevice(name=bind_interface,
273+
namespace=ns_name)
274+
).wait_until_address_ready(address=bind_address_v6)
275+
except ip_lib.DADFailed as exc:
276+
# This failure means that another DHCP agent has already
277+
# configured this metadata address, so all requests will
278+
# be via that single agent.
279+
LOG.info('DAD failed for address %(address)s on interface '
280+
'%(interface)s in namespace %(namespace)s on network '
281+
'%(network)s, deleting it. Exception: %(exception)s',
282+
{'address': bind_address_v6,
283+
'interface': bind_interface,
284+
'namespace': ns_name,
285+
'network': network_id,
286+
'exception': str(exc)})
287+
try:
288+
ip_lib.delete_ip_address(bind_address_v6, bind_interface,
289+
namespace=ns_name)
290+
except Exception as exc:
291+
# do not re-raise a delete failure, just log
292+
LOG.info('Address deletion failure: %s', str(exc))
293+
return
272294
pm.enable()
273295
monitor.register(uuid, METADATA_SERVICE_NAME, pm)
274296
cls.monitors[router_id] = pm
@@ -363,6 +385,6 @@ def apply_metadata_nat_rules(router, proxy):
363385
if netutils.is_ipv6_enabled():
364386
for c, r in proxy.metadata_nat_rules(
365387
proxy.metadata_port,
366-
metadata_address=(constants.METADATA_V6_IP + '/128')):
388+
metadata_address=(common_constants.METADATA_V6_CIDR)):
367389
router.iptables_manager.ipv6['nat'].add_rule(c, r)
368390
router.iptables_manager.apply()

neutron/common/_constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,6 @@
8181

8282
# The lowest binding index for L3 agents and DHCP agents.
8383
LOWEST_AGENT_BINDING_INDEX = 1
84+
85+
# Neutron-lib defines this with a /64 but it should be /128
86+
METADATA_V6_CIDR = constants.METADATA_V6_IP + '/128'

neutron/conf/agent/database/agentschedulers_db.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232
'network. If this number is greater than 1, the '
3333
'scheduler automatically assigns multiple DHCP agents '
3434
'for a given tenant network, providing high '
35-
'availability for DHCP service.')),
35+
'availability for the DHCP service. However this does '
36+
'not provide high availability for the IPv6 metadata '
37+
'service in isolated networks.')),
3638
cfg.BoolOpt('enable_services_on_agents_with_admin_state_down',
3739
default=False,
3840
help=_('Enable services on an agent with admin_state_up '

neutron/db/securitygroups_db.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,12 @@ def delete_security_group(self, context, id):
253253
if sg['name'] == 'default' and not context.is_admin:
254254
raise ext_sg.SecurityGroupCannotRemoveDefault()
255255

256+
# Check if there are rules with remote_group_id ponting to
257+
# the security_group to be deleted
258+
rules_ids_as_remote = self._get_security_group_rules_by_remote(
259+
context=context, remote_id=id,
260+
)
261+
256262
self._registry_publish(resources.SECURITY_GROUP,
257263
events.BEFORE_DELETE,
258264
exc_cls=ext_sg.SecurityGroupInUse, id=id,
@@ -285,6 +291,20 @@ def delete_security_group(self, context, id):
285291
context, resource_id=id, states=(sec_group,),
286292
metadata={'security_group_rule_ids': sgr_ids,
287293
'name': sg['name']}))
294+
for rule in rules_ids_as_remote:
295+
registry.publish(
296+
resources.SECURITY_GROUP_RULE,
297+
events.AFTER_DELETE,
298+
self,
299+
payload=events.DBEventPayload(
300+
context,
301+
resource_id=rule['id'],
302+
metadata={'security_group_id': rule['security_group_id'],
303+
'remote_group_id': rule['remote_group_id'],
304+
'rule': rule
305+
}
306+
)
307+
)
288308

289309
@db_api.retry_if_session_inactive()
290310
def update_security_group(self, context, id, security_group):
@@ -371,6 +391,23 @@ def _get_port_security_group_bindings(self, context,
371391
self._make_security_group_binding_dict,
372392
filters=filters, fields=fields)
373393

394+
def _get_security_group_rules_by_remote(self, context, remote_id):
395+
return model_query.get_collection(
396+
context, sg_models.SecurityGroupRule,
397+
self._make_security_group_rule_dict,
398+
filters={'remote_group_id': [remote_id]},
399+
fields=['id',
400+
'remote_group_id',
401+
'security_group_id',
402+
'direction',
403+
'ethertype',
404+
'protocol',
405+
'port_range_min',
406+
'port_range_max',
407+
'normalized_cidr'
408+
]
409+
)
410+
374411
@db_api.retry_if_session_inactive()
375412
def _delete_port_security_group_bindings(self, context, port_id):
376413
with db_api.CONTEXT_WRITER.using(context):

neutron/objects/db/api.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# TODO(ihrachys): cover the module with functional tests targeting supported
1414
# backends
1515

16+
from neutron_lib.db import api as db_api
1617
from neutron_lib.db import model_query
1718
from neutron_lib import exceptions as n_exc
1819
from neutron_lib.objects import utils as obj_utils
@@ -31,16 +32,18 @@ def _get_filter_query(obj_cls, context, query_field=None, query_limit=None,
3132

3233

3334
def get_object(obj_cls, context, **kwargs):
34-
return _get_filter_query(obj_cls, context, **kwargs).first()
35+
with db_api.CONTEXT_READER.using(context):
36+
return _get_filter_query(obj_cls, context, **kwargs).first()
3537

3638

3739
def count(obj_cls, context, query_field=None, query_limit=None, **kwargs):
38-
if not query_field and obj_cls.primary_keys:
39-
query_field = obj_cls.primary_keys[0]
40-
if query_field in obj_cls.fields_need_translation:
41-
query_field = obj_cls.fields_need_translation[query_field]
42-
return _get_filter_query(obj_cls, context, query_field=query_field,
43-
query_limit=query_limit, **kwargs).count()
40+
with db_api.CONTEXT_READER.using(context):
41+
if not query_field and obj_cls.primary_keys:
42+
query_field = obj_cls.primary_keys[0]
43+
if query_field in obj_cls.fields_need_translation:
44+
query_field = obj_cls.fields_need_translation[query_field]
45+
return _get_filter_query(obj_cls, context, query_field=query_field,
46+
query_limit=query_limit, **kwargs).count()
4447

4548

4649
def _kwargs_to_filters(**kwargs):

neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -253,9 +253,6 @@ def subscribe(self):
253253
registry.subscribe(self._create_security_group,
254254
resources.SECURITY_GROUP,
255255
events.AFTER_CREATE)
256-
registry.subscribe(self._delete_security_group_precommit,
257-
resources.SECURITY_GROUP,
258-
events.PRECOMMIT_DELETE)
259256
registry.subscribe(self._delete_security_group,
260257
resources.SECURITY_GROUP,
261258
events.AFTER_DELETE)
@@ -268,6 +265,9 @@ def subscribe(self):
268265
registry.subscribe(self._process_sg_rule_notification,
269266
resources.SECURITY_GROUP_RULE,
270267
events.BEFORE_DELETE)
268+
registry.subscribe(self._process_sg_rule_after_del_notification,
269+
resources.SECURITY_GROUP_RULE,
270+
events.AFTER_DELETE)
271271

272272
def _clean_hash_ring(self, *args, **kwargs):
273273
admin_context = n_context.get_admin_context()
@@ -384,14 +384,6 @@ def _create_security_group(self, resource, event, trigger, payload):
384384
self._ovn_client.create_security_group(context,
385385
security_group)
386386

387-
def _delete_security_group_precommit(self, resource, event, trigger,
388-
payload):
389-
context = n_context.get_admin_context()
390-
security_group_id = payload.resource_id
391-
for sg_rule in self._plugin.get_security_group_rules(
392-
context, filters={'remote_group_id': [security_group_id]}):
393-
self._ovn_client.delete_security_group_rule(context, sg_rule)
394-
395387
def _delete_security_group(self, resource, event, trigger, payload):
396388
context = payload.context
397389
security_group_id = payload.resource_id
@@ -449,6 +441,12 @@ def _process_sg_rule_notification(
449441
context,
450442
sg_rule)
451443

444+
def _process_sg_rule_after_del_notification(
445+
self, resource, event, trigger, payload):
446+
context = payload.context
447+
sg_rule = payload.metadata['rule']
448+
self._ovn_client.delete_security_group_rule(context, sg_rule)
449+
452450
def _sg_has_rules_with_same_normalized_cidr(self, sg_rule):
453451
compare_keys = [
454452
'ethertype', 'direction', 'protocol',

neutron/services/qos/qos_plugin.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ def _extend_port_resource_request_bulk(ports_res, noop):
366366
def _get_ports_with_policy(self, context, policy):
367367
networks_ids = policy.get_bound_networks()
368368
ports_with_net_policy = ports_object.Port.get_objects(
369-
context, network_id=networks_ids)
369+
context, network_id=networks_ids) if networks_ids else []
370370

371371
# Filter only this ports which don't have overwritten policy
372372
ports_with_net_policy = [
@@ -376,7 +376,7 @@ def _get_ports_with_policy(self, context, policy):
376376

377377
ports_ids = policy.get_bound_ports()
378378
ports_with_policy = ports_object.Port.get_objects(
379-
context, id=ports_ids)
379+
context, id=ports_ids) if ports_ids else []
380380
return list(set(ports_with_policy + ports_with_net_policy))
381381

382382
def _validate_create_port_callback(self, resource, event, trigger,

0 commit comments

Comments
 (0)