Add rescue related methods to network interface

Adds methods `add_rescuing_network` and `remove_rescuing_network`
to add/remove rescuing network to `network` interface.
These methods are not added to `flat` network interface.
The 'flat' network uses same network for tenant and provisioning.
It makes sense to use the same for rescuing as well; as opposed
to a separate network like we have for cleaning.

Change-Id: I8f4123bfe7d293e8ff6f3bfc2f25445a39c94c73
Related-bug: #1526449
Co-Authored-By: Jay Faulkner <jay@jvf.cc>
Co-Authored-By: Mario Villaplana <mario.villaplana@gmail.com>
Co-Authored-By: Jesse J. Cook <jesse.j.cook@member.fsf.org>
Co-Authored-By: Aparna <aparnavtce@gmail.com>
Co-Authored-By: Shivanand Tendulker <stendulker@gmail.com>
This commit is contained in:
Shivanand Tendulker 2017-11-16 11:43:16 -05:00
parent 084da02a32
commit fbee0981ad
11 changed files with 334 additions and 40 deletions

View File

@ -7,7 +7,9 @@
# Authentication strategy used by ironic-api. "noauth" should
# not be used in a production environment because all
# authentication will be disabled. (string value)
# Allowed values: noauth, keystone
# Possible values:
# noauth - <No description provided>
# keystone - <No description provided>
#auth_strategy = keystone
# Return server tracebacks in the API response for any error
@ -362,7 +364,12 @@
# Specifies the minimum level for which to send notifications.
# If not set, no notifications will be sent. The default is
# for this option to be unset. (string value)
# Allowed values: debug, info, warning, error, critical
# Possible values:
# debug - <No description provided>
# info - <No description provided>
# warning - <No description provided>
# error - <No description provided>
# critical - <No description provided>
#notification_level = <None>
# Directory where the ironic python module is installed.
@ -397,7 +404,13 @@
# doing a rolling upgrade from version N to version N+1, set
# (to pin) this to N. To unpin (default), leave it unset and
# the latest versions will be used. (string value)
# Allowed values: pike, 9.2, 9.1, 9.0, 8.0, 10.0
# Possible values:
# pike - <No description provided>
# 9.2 - <No description provided>
# 9.1 - <No description provided>
# 9.0 - <No description provided>
# 8.0 - <No description provided>
# 10.0 - <No description provided>
#pin_release_version = <None>
# Path to the rootwrap configuration file to use for running
@ -554,7 +567,10 @@
#rpc_zmq_bind_address = *
# MatchMaker driver. (string value)
# Allowed values: redis, sentinel, dummy
# Possible values:
# redis - <No description provided>
# sentinel - <No description provided>
# dummy - <No description provided>
#rpc_zmq_matchmaker = redis
# Number of ZeroMQ contexts, defaults to 1. (integer value)
@ -630,7 +646,9 @@
# Default serialization mechanism for
# serializing/deserializing outgoing/incoming messages (string
# value)
# Allowed values: json, msgpack
# Possible values:
# json - <No description provided>
# msgpack - <No description provided>
#rpc_zmq_serialization = json
# This option configures round-robin mode in zmq socket. True
@ -821,12 +839,17 @@
# Whether Ironic should collect the deployment logs on
# deployment failure (on_failure), always or never. (string
# value)
# Allowed values: always, on_failure, never
# Possible values:
# always - <No description provided>
# on_failure - <No description provided>
# never - <No description provided>
#deploy_logs_collect = on_failure
# The name of the storage backend where the logs will be
# stored. (string value)
# Allowed values: local, swift
# Possible values:
# local - <No description provided>
# swift - <No description provided>
#deploy_logs_storage_backend = local
# The path to the directory where the logs should be stored,
@ -1596,7 +1619,9 @@
# but it will be changed to "local" in the future. It is
# recommended to set an explicit value for this option.
# (string value)
# Allowed values: netboot, local
# Possible values:
# netboot - <No description provided>
# local - <No description provided>
#default_boot_option = <None>
# Whether to upload the config drive to object store. Set this
@ -1607,7 +1632,9 @@
# Type of object store endpoint type to be used as a backend
# (string value)
# Allowed values: swift, radosgw
# Possible values:
# swift - <No description provided>
# radosgw - <No description provided>
# Deprecated group/name - [glance]/temp_url_endpoint_type
#object_store_endpoint_type = swift
@ -1694,7 +1721,9 @@
# DEPRECATED: Authentication strategy to use when connecting
# to glance. (string value)
# Allowed values: keystone, noauth
# Possible values:
# keystone - <No description provided>
# noauth - <No description provided>
# This option is deprecated for removal.
# Its value may be silently ignored in the future.
# Reason: To configure glance in noauth mode, set
@ -1999,7 +2028,10 @@
# for backward compatibility. When "auto" is specified,
# default boot mode will be selected based on boot mode
# settings on the system. (string value)
# Allowed values: auto, bios, uefi
# Possible values:
# auto - <No description provided>
# bios - <No description provided>
# uefi - <No description provided>
#default_boot_mode = auto
@ -2191,7 +2223,9 @@
#remote_image_server = <None>
# Share type of virtual media (string value)
# Allowed values: CIFS, NFS
# Possible values:
# CIFS - <No description provided>
# NFS - <No description provided>
#remote_image_share_type = CIFS
# share name of remote_image_server (string value)
@ -2209,23 +2243,32 @@
# Port to be used for iRMC operations (port value)
# Minimum value: 0
# Maximum value: 65535
# Allowed values: 443, 80
# Possible values:
# 443 - <No description provided>
# 80 - <No description provided>
#port = 443
# Authentication method to be used for iRMC operations (string
# value)
# Allowed values: basic, digest
# Possible values:
# basic - <No description provided>
# digest - <No description provided>
#auth_method = basic
# Timeout (in seconds) for iRMC operations (integer value)
#client_timeout = 60
# Sensor data retrieval method. (string value)
# Allowed values: ipmitool, scci
# Possible values:
# ipmitool - <No description provided>
# scci - <No description provided>
#sensor_method = ipmitool
# SNMP protocol version (string value)
# Allowed values: v1, v2c, v3
# Possible values:
# v1 - <No description provided>
# v2c - <No description provided>
# v3 - <No description provided>
#snmp_version = v2c
# SNMP port (port value)
@ -2416,7 +2459,10 @@
# token data is encrypted and authenticated in the cache. If
# the value is not one of these options or empty, auth_token
# will raise an exception on initialization. (string value)
# Allowed values: None, MAC, ENCRYPT
# Possible values:
# None - <No description provided>
# MAC - <No description provided>
# ENCRYPT - <No description provided>
#memcache_security_strategy = None
# (Optional, mandatory if memcache_security_strategy is
@ -2603,7 +2649,9 @@
#
# Backend to use for the metrics system. (string value)
# Allowed values: noop, statsd
# Possible values:
# noop - <No description provided>
# statsd - <No description provided>
#backend = noop
# Prepend the hostname to all metric names. The format of
@ -2667,7 +2715,9 @@
# to neutron. Running neutron in noauth mode (related to but
# not affected by this setting) is insecure and should only be
# used for testing. (string value)
# Allowed values: keystone, noauth
# Possible values:
# keystone - <No description provided>
# noauth - <No description provided>
# This option is deprecated for removal.
# Its value may be silently ignored in the future.
# Reason: To configure neutron for noauth mode, set
@ -2782,6 +2832,25 @@
# value)
#region_name = <None>
# Neutron network UUID or name for booting the ramdisk for
# rescue mode. This is not the network that the rescue ramdisk
# will use post-boot -- the tenant network is used for that.
# Required for "neutron" network interface, if rescue mode
# will be used. It is not used for the "flat" or "noop"
# network interfaces. If a name is provided, it must be unique
# among all networks or rescue will fail. This option is part
# of rescue feature work, which is not currently exposed to
# users. (string value)
#rescuing_network = <None>
# List of Neutron Security Group UUIDs to be applied during
# the node rescue process. Optional for the "neutron" network
# interface and not used for the "flat" or "noop" network
# interfaces. If not specified, the default security group is
# used. This option is part of rescue feature work, which is
# not currently exposed to users. (list value)
#rescuing_network_security_groups =
# Client retries in the case of a failed request. (integer
# value)
#retries = 3
@ -3164,15 +3233,24 @@
# value)
#kafka_consumer_timeout = 1.0
# Pool Size for Kafka Consumers (integer value)
# DEPRECATED: Pool Size for Kafka Consumers (integer value)
# This option is deprecated for removal.
# Its value may be silently ignored in the future.
# Reason: Driver no longer uses connection pool.
#pool_size = 10
# The pool size limit for connections expiration policy
# (integer value)
# DEPRECATED: The pool size limit for connections expiration
# policy (integer value)
# This option is deprecated for removal.
# Its value may be silently ignored in the future.
# Reason: Driver no longer uses connection pool.
#conn_pool_min_size = 2
# The time-to-live in sec of idle connections in the pool
# (integer value)
# DEPRECATED: The time-to-live in sec of idle connections in
# the pool (integer value)
# This option is deprecated for removal.
# Its value may be silently ignored in the future.
# Reason: Driver no longer uses connection pool.
#conn_pool_ttl = 1200
# Group id for Kafka consumer. Consumers in one group will
@ -3271,7 +3349,9 @@
# one we are currently connected to becomes unavailable. Takes
# effect only if more than one RabbitMQ node is provided in
# config. (string value)
# Allowed values: round-robin, shuffle
# Possible values:
# round-robin - <No description provided>
# shuffle - <No description provided>
#kombu_failover_strategy = round-robin
# DEPRECATED: The RabbitMQ broker address where a single node
@ -3310,7 +3390,10 @@
#rabbit_password = guest
# The RabbitMQ login method. (string value)
# Allowed values: PLAIN, AMQPLAIN, RABBIT-CR-DEMO
# Possible values:
# PLAIN - <No description provided>
# AMQPLAIN - <No description provided>
# RABBIT-CR-DEMO - <No description provided>
#rabbit_login_method = AMQPLAIN
# DEPRECATED: The RabbitMQ virtual host. (string value)
@ -3397,7 +3480,10 @@
#host_connection_reconnect_delay = 0.25
# Connection factory implementation (string value)
# Allowed values: new, single, read_write
# Possible values:
# new - <No description provided>
# single - <No description provided>
# read_write - <No description provided>
#connection_factory = single
# Maximum number of connections to keep queued. (integer
@ -3425,7 +3511,9 @@
# Default serialization mechanism for
# serializing/deserializing outgoing/incoming messages (string
# value)
# Allowed values: json, msgpack
# Possible values:
# json - <No description provided>
# msgpack - <No description provided>
#default_serializer_type = json
# Persist notification messages. (boolean value)
@ -3497,7 +3585,10 @@
#rpc_zmq_bind_address = *
# MatchMaker driver. (string value)
# Allowed values: redis, sentinel, dummy
# Possible values:
# redis - <No description provided>
# sentinel - <No description provided>
# dummy - <No description provided>
#rpc_zmq_matchmaker = redis
# Number of ZeroMQ contexts, defaults to 1. (integer value)
@ -3573,7 +3664,9 @@
# Default serialization mechanism for
# serializing/deserializing outgoing/incoming messages (string
# value)
# Allowed values: json, msgpack
# Possible values:
# json - <No description provided>
# msgpack - <No description provided>
#rpc_zmq_serialization = json
# This option configures round-robin mode in zmq socket. True
@ -3682,7 +3775,10 @@
# Content Type to send and receive data for REST based policy
# check (string value)
# Allowed values: application/x-www-form-urlencoded, application/json
# Possible values:
# application/x-www-form-urlencoded - <No description
# provided>
# application/json - <No description provided>
#remote_content_type = application/x-www-form-urlencoded
# server identity verification for REST based policy check
@ -3914,7 +4010,9 @@
# The IP version that will be used for PXE booting. Defaults
# to 4. EXPERIMENTAL (string value)
# Allowed values: 4, 6
# Possible values:
# 4 - <No description provided>
# 6 - <No description provided>
#ip_version = 4
# Download deploy images directly from swift using temporary

View File

@ -566,6 +566,7 @@ class NeutronNetworkInterfaceMixin(object):
_cleaning_network_uuid = None
_provisioning_network_uuid = None
_rescuing_network_uuid = None
def get_cleaning_network_uuid(self, context=None):
if self._cleaning_network_uuid is None:
@ -580,3 +581,12 @@ class NeutronNetworkInterfaceMixin(object):
CONF.neutron.provisioning_network,
_('provisioning network'), context=context)
return self._provisioning_network_uuid
def get_rescuing_network_uuid(self, context=None):
# TODO(stendulker): FlatNetwork should not use this method.
# FlatNetwork uses tenant network for rescue operation.
if self._rescuing_network_uuid is None:
self._rescuing_network_uuid = validate_network(
CONF.neutron.rescuing_network,
_('rescuing network'), context=context)
return self._rescuing_network_uuid

View File

@ -90,6 +90,26 @@ opts = [
'used for the "flat" or "noop" network interfaces. '
'If not specified, default security group '
'is used.')),
cfg.StrOpt('rescuing_network',
help=_('Neutron network UUID or name for booting the ramdisk '
'for rescue mode. This is not the network that the '
'rescue ramdisk will use post-boot -- the tenant '
'network is used for that. Required for "neutron" '
'network interface, if rescue mode will be used. It '
'is not used for the "flat" or "noop" network '
'interfaces. If a name is provided, it must be unique '
'among all networks or rescue will fail. This option '
'is part of rescue feature work, which is not currently '
'exposed to users.')),
cfg.ListOpt('rescuing_network_security_groups',
default=[],
help=_('List of Neutron Security Group UUIDs to be applied '
'during the node rescue process. Optional for the '
'"neutron" network interface and not used for the '
'"flat" or "noop" network interfaces. If not '
'specified, the default security group is used. This '
'option is part of rescue feature work, which is '
'not currently exposed to users.')),
]

View File

@ -1036,8 +1036,9 @@ class NetworkInterface(BaseInterface):
"""Returns the currently used VIF associated with port or portgroup
We are booting the node only in one network at a time, and presence of
cleaning_vif_port_id means we're doing cleaning, of
provisioning_vif_port_id - provisioning.
cleaning_vif_port_id means we're doing cleaning,
of provisioning_vif_port_id - provisioning,
of rescuing_vif_port_id - rescuing.
Otherwise it's a tenant network.
:param task: A TaskManager instance.
@ -1092,6 +1093,28 @@ class NetworkInterface(BaseInterface):
:raises: NetworkError
"""
def add_rescuing_network(self, task):
"""Add the rescuing network to the node.
:param task: A TaskManager instance.
:returns: a dictionary in the form {port.uuid: neutron_port['id']}
:raises: NetworkError
:raises: InvalidParameterValue, if the network interface configuration
is invalid.
"""
return {}
def remove_rescuing_network(self, task):
"""Removes the rescuing network from a node.
:param task: A TaskManager instance.
:raises: NetworkError
:raises: InvalidParameterValue, if the network interface configuration
is invalid.
:raises: MissingParameterValue, if some parameters are missing.
"""
pass
@six.add_metaclass(abc.ABCMeta)
class StorageInterface(BaseInterface):

View File

@ -362,8 +362,9 @@ class VIFPortIDMixin(object):
"""Returns the currently used VIF associated with port or portgroup
We are booting the node only in one network at a time, and presence of
cleaning_vif_port_id means we're doing cleaning, of
provisioning_vif_port_id - provisioning.
cleaning_vif_port_id means we're doing cleaning,
of provisioning_vif_port_id - provisioning,
of rescuing_vif_port_id - rescuing.
Otherwise it's a tenant network
:param task: A TaskManager instance.
@ -373,6 +374,7 @@ class VIFPortIDMixin(object):
return (p_obj.internal_info.get('cleaning_vif_port_id') or
p_obj.internal_info.get('provisioning_vif_port_id') or
p_obj.internal_info.get('rescuing_vif_port_id') or
self._get_vif_id_by_port_like_obj(p_obj) or None)

View File

@ -140,6 +140,46 @@ class NeutronNetwork(common.NeutronVIFPortIDMixin,
port.internal_info = internal_info
port.save()
def add_rescuing_network(self, task):
"""Create neutron ports for each port to boot the rescue ramdisk.
:param task: a TaskManager instance.
:returns: a dictionary in the form {port.uuid: neutron_port['id']}
"""
# If we have left over ports from a previous rescue, remove them
neutron.rollback_ports(task, self.get_rescuing_network_uuid(
context=task.context))
LOG.info('Adding rescuing network to node %s', task.node.uuid)
security_groups = CONF.neutron.rescuing_network_security_groups
vifs = neutron.add_ports_to_network(
task,
self.get_rescuing_network_uuid(context=task.context),
security_groups=security_groups)
for port in task.ports:
if port.uuid in vifs:
internal_info = port.internal_info
internal_info['rescuing_vif_port_id'] = vifs[port.uuid]
port.internal_info = internal_info
port.save()
return vifs
def remove_rescuing_network(self, task):
"""Deletes neutron port created for booting the rescue ramdisk.
:param task: a TaskManager instance.
:raises: NetworkError
"""
LOG.info('Removing rescuing network from node %s',
task.node.uuid)
neutron.remove_ports_from_network(
task, self.get_rescuing_network_uuid(context=task.context))
for port in task.ports:
if 'rescuing_vif_port_id' in port.internal_info:
internal_info = port.internal_info
del internal_info['rescuing_vif_port_id']
port.internal_info = internal_info
port.save()
def configure_tenant_networks(self, task):
"""Configure tenant networks for a node.

View File

@ -67,8 +67,9 @@ class NoopNetwork(base.NetworkInterface):
"""Returns the currently used VIF associated with port or portgroup
We are booting the node only in one network at a time, and presence of
cleaning_vif_port_id means we're doing cleaning, of
provisioning_vif_port_id - provisioning.
cleaning_vif_port_id means we're doing cleaning,
of provisioning_vif_port_id - provisioning
of rescuing_vif_port_id - rescuing.
Otherwise it's a tenant network
:param task: A TaskManager instance.

View File

@ -129,6 +129,8 @@ class TestCase(oslo_test_base.BaseTestCase):
group='neutron')
self.config(provisioning_network=uuidutils.generate_uuid(),
group='neutron')
self.config(rescuing_network=uuidutils.generate_uuid(),
group='neutron')
self.config(enabled_drivers=['fake'])
self.config(enabled_hardware_types=['fake-hardware'])
self.config(enabled_network_interfaces=['flat', 'noop', 'neutron'])

View File

@ -153,6 +153,9 @@ class TestNetwork(db_base.DbTestCase):
def test_get_node_vif_ids_during_provisioning(self):
self._test_get_node_vif_ids_multitenancy('provisioning_vif_port_id')
def test_get_node_vif_ids_during_rescuing(self):
self._test_get_node_vif_ids_multitenancy('rescuing_vif_port_id')
class GetPortgroupByIdTestCase(db_base.DbTestCase):

View File

@ -626,7 +626,7 @@ class TestVifPortIDMixin(db_base.DbTestCase):
def test_get_current_vif_internal_info_provisioning(self):
internal_info = {'provisioning_vif_port_id': 'foo',
'vif_port_id': 'bar'}
'tenant_vif_port_id': 'bar'}
self.port.internal_info = internal_info
self.port.save()
with task_manager.acquire(self.context, self.node.id) as task:
@ -641,6 +641,15 @@ class TestVifPortIDMixin(db_base.DbTestCase):
vif = self.interface.get_current_vif(task, self.port)
self.assertEqual('bar', vif)
def test_get_current_vif_internal_info_rescuing(self):
internal_info = {'rescuing_vif_port_id': 'foo',
'tenant_vif_port_id': 'bar'}
self.port.internal_info = internal_info
self.port.save()
with task_manager.acquire(self.context, self.node.id) as task:
vif = self.interface.get_current_vif(task, self.port)
self.assertEqual('foo', vif)
def test_get_current_vif_none(self):
internal_info = extra = {}
self.port.internal_info = internal_info

View File

@ -20,6 +20,7 @@ from oslo_utils import uuidutils
from ironic.common import exception
from ironic.common import neutron as neutron_common
from ironic.conductor import task_manager
from ironic.drivers import base as drivers_base
from ironic.drivers.modules.network import neutron
from ironic.tests.unit.conductor import mgr_utils
from ironic.tests.unit.db import base as db_base
@ -35,10 +36,19 @@ class NeutronInterfaceTestCase(db_base.DbTestCase):
def setUp(self):
super(NeutronInterfaceTestCase, self).setUp()
self.config(enabled_drivers=['fake'])
self.config(enabled_hardware_types=['fake-hardware'])
for iface in drivers_base.ALL_INTERFACES:
name = 'fake'
if iface == 'network':
name = 'neutron'
config_kwarg = {'enabled_%s_interfaces' % iface: [name],
'default_%s_interface' % iface: name}
self.config(**config_kwarg)
mgr_utils.mock_the_extension_manager()
self.interface = neutron.NeutronNetwork()
self.node = utils.create_test_node(self.context,
driver='fake-hardware',
network_interface='neutron')
self.port = utils.create_test_port(
self.context, node_id=self.node.id,
@ -217,6 +227,82 @@ class NeutronInterfaceTestCase(db_base.DbTestCase):
self.port.refresh()
self.assertNotIn('cleaning_vif_port_id', self.port.internal_info)
@mock.patch.object(neutron_common, 'validate_network',
side_effect=lambda n, t, context=None: n)
@mock.patch.object(neutron_common, 'rollback_ports')
@mock.patch.object(neutron_common, 'add_ports_to_network')
def test_add_rescuing_network(self, add_ports_mock, rollback_mock,
validate_mock):
other_port = utils.create_test_port(
self.context, node_id=self.node.id,
address='52:54:00:cf:2d:33',
uuid=uuidutils.generate_uuid(),
extra={'vif_port_id': uuidutils.generate_uuid()})
neutron_other_port = {'id': uuidutils.generate_uuid(),
'mac_address': '52:54:00:cf:2d:33'}
add_ports_mock.return_value = {
other_port.uuid: neutron_other_port['id']}
with task_manager.acquire(self.context, self.node.id) as task:
res = self.interface.add_rescuing_network(task)
add_ports_mock.assert_called_once_with(
task, CONF.neutron.rescuing_network,
security_groups=[])
rollback_mock.assert_called_once_with(
task, CONF.neutron.rescuing_network)
self.assertEqual(add_ports_mock.return_value, res)
validate_mock.assert_called_once_with(
CONF.neutron.rescuing_network,
'rescuing network', context=task.context)
other_port.refresh()
self.assertEqual(neutron_other_port['id'],
other_port.internal_info['rescuing_vif_port_id'])
self.assertNotIn('rescuing_vif_port_id', self.port.internal_info)
@mock.patch.object(neutron_common, 'validate_network',
lambda n, t, context=None: n)
@mock.patch.object(neutron_common, 'rollback_ports')
@mock.patch.object(neutron_common, 'add_ports_to_network')
def test_add_rescuing_network_with_sg(self, add_ports_mock, rollback_mock):
add_ports_mock.return_value = {self.port.uuid: self.neutron_port['id']}
sg_ids = []
for i in range(2):
sg_ids.append(uuidutils.generate_uuid())
self.config(rescuing_network_security_groups=sg_ids, group='neutron')
with task_manager.acquire(self.context, self.node.id) as task:
res = self.interface.add_rescuing_network(task)
add_ports_mock.assert_called_once_with(
task, CONF.neutron.rescuing_network,
security_groups=CONF.neutron.rescuing_network_security_groups)
rollback_mock.assert_called_once_with(
task, CONF.neutron.rescuing_network)
self.assertEqual(add_ports_mock.return_value, res)
self.port.refresh()
self.assertEqual(self.neutron_port['id'],
self.port.internal_info['rescuing_vif_port_id'])
@mock.patch.object(neutron_common, 'validate_network',
side_effect=lambda n, t, context=None: n)
@mock.patch.object(neutron_common, 'remove_ports_from_network')
def test_remove_rescuing_network(self, remove_ports_mock,
validate_mock):
other_port = utils.create_test_port(
self.context, node_id=self.node.id,
address='52:54:00:cf:2d:33',
uuid=uuidutils.generate_uuid(),
extra={'vif_port_id': uuidutils.generate_uuid()})
other_port.internal_info = {'rescuing_vif_port_id': 'vif-port-id'}
other_port.save()
with task_manager.acquire(self.context, self.node.id) as task:
self.interface.remove_rescuing_network(task)
remove_ports_mock.assert_called_once_with(
task, CONF.neutron.rescuing_network)
validate_mock.assert_called_once_with(
CONF.neutron.rescuing_network,
'rescuing network', context=task.context)
other_port.refresh()
self.assertNotIn('rescuing_vif_port_id', self.port.internal_info)
self.assertNotIn('rescuing_vif_port_id', other_port.internal_info)
@mock.patch.object(neutron_common, 'unbind_neutron_port')
def test_unconfigure_tenant_networks(self, mock_unbind_port):
with task_manager.acquire(self.context, self.node.id) as task: