Use adapters for neutronclient

deprecates the following options in [neutron] section:
- url
- url_timeout
- auth_strategy

Changes some internal networking-related functions/methods
to accept a request context as optional keyword argument (defaults to
None).
This allows to pass a global request id to neutron client and
in future will simplify creating a user auth plugin from request
context.
For backward compatibility, when calling those functions/methods
without a request context, a dummy request context will be generated
automatically.

Change-Id: Ib327c7a141cfbca63b870027ad8e901c0f48bb2d
Partial-Bug: #1699547
changes/70/476170/33
Pavlo Shchelokovskyy 6 years ago
parent 39a63602c7
commit 4d43262955

@ -1122,7 +1122,7 @@ function configure_ironic_conductor {
# TODO(pas-ha) this block is for transition period only,
# after all clients are moved to use keystoneauth adapters,
# it will be deleted
local sections_with_adapter="service_catalog glance cinder inspector swift"
local sections_with_adapter="service_catalog glance cinder inspector swift neutron"
for conf_section in $sections_with_adapter; do
configure_adapter_for $conf_section
done

@ -150,7 +150,7 @@ Configuring ironic-conductor service
[neutron]
# URL for connecting to neutron. (string value)
url=<NETWORKING_SERVICE_ENDPOINT>
endpoint_override = <NETWORKING_SERVICE_ENDPOINT>
#. Configure a specific ironic-api service URL - only if you do not want
to use discovery of the Baremetal service endpoint from keystone catalog

@ -2587,11 +2587,16 @@
# Authentication URL (string value)
#auth_url = <None>
# Authentication strategy to use when connecting 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)
# DEPRECATED: Authentication strategy to use when connecting
# 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
# This option is deprecated for removal.
# Its value may be silently ignored in the future.
# Reason: To configure neutron for noauth mode, set
# [neutron]/auth_type = none and
# [neutron]/endpoint_override=<NEUTRON_API_URL> instead
#auth_strategy = keystone
# Authentication type to load (string value)
@ -2637,12 +2642,28 @@
# Domain name to scope to (string value)
#domain_name = <None>
# Always use this endpoint URL for requests for this client.
# (string value)
#endpoint_override = <None>
# Verify HTTPS connections. (boolean value)
#insecure = false
# PEM encoded client certificate key file (string value)
#keyfile = <None>
# The maximum major version of a given API, intended to be
# used as the upper bound of a range with min_version.
# Mutually exclusive with version. (string value)
#max_version = <None>
# The minimum major version of a given API, intended to be
# used as the lower bound of a range with max_version.
# Mutually exclusive with version. If min_version is given
# with no max_version it is as if max version is "latest".
# (string value)
#min_version = <None>
# User's password (string value)
#password = <None>
@ -2679,10 +2700,22 @@
# is used. (list value)
#provisioning_network_security_groups =
# The default region_name for endpoint URL discovery. (string
# value)
#region_name = <None>
# Client retries in the case of a failed request. (integer
# value)
#retries = 3
# The default service_name for endpoint URL discovery. (string
# value)
#service_name = <None>
# The default service_type for endpoint URL discovery. (string
# value)
#service_type = network
# Tenant ID (string value)
#tenant_id = <None>
@ -2695,14 +2728,24 @@
# Trust ID (string value)
#trust_id = <None>
# URL for connecting to neutron. Default value translates to
# 'http://$my_ip:9696' when auth_strategy is 'noauth', and to
# discovery from Keystone catalog when auth_strategy is
# 'keystone'. (string value)
# DEPRECATED: URL for connecting to neutron. Default value
# translates to 'http://$my_ip:9696' when auth_strategy is
# 'noauth', and to discovery from Keystone catalog when
# auth_strategy is 'keystone'. (string value)
# This option is deprecated for removal.
# Its value may be silently ignored in the future.
# Reason: Use [neutron]/endpoint_override option instead. It
# has no default value and must be set explicitly if required
# to connect to specific neutron URL, for example when
# [neutron]auth_strategy is noauth.
#url = <None>
# Timeout value for connecting to neutron in seconds. (integer
# value)
# DEPRECATED: Timeout value for connecting to neutron in
# seconds. (integer value)
# This option is deprecated for removal.
# Its value may be silently ignored in the future.
# Reason: Use [neutron]/timeout option instead. It has no
# default value and must be set explicitly.
#url_timeout = 30
# User's domain id (string value)
@ -2718,6 +2761,15 @@
# Deprecated group/name - [neutron]/user_name
#username = <None>
# List of interfaces, in order of preference, for endpoint
# URL. (list value)
#valid_interfaces = internal,public
# Minimum Major API version within a given Major API version
# for endpoint URL discovery. Mutually exclusive with
# min_version and max_version (string value)
#version = <None>
[oneview]

@ -22,7 +22,6 @@ from oslo_log import log as logging
import six
from ironic.common import exception
from ironic.conf import auth as auth_conf
from ironic.conf import CONF
@ -118,24 +117,3 @@ def get_service_auth(context, endpoint, service_auth):
user_auth = token_endpoint.Token(endpoint, context.auth_token)
return service_token.ServiceTokenAuthWrapper(user_auth=user_auth,
service_auth=service_auth)
# NOTE(pas-ha) Used by neutronclient only
# FIXME(pas-ha) remove this while moving to kesytoneauth adapters
@ks_exceptions
def get_service_url(session, **kwargs):
"""Find endpoint for given service in keystone catalog.
If 'interface' is provided, fetches service url of this interface.
Otherwise, first tries to fetch 'internal' endpoint,
and then the 'public' one.
:param session: keystoneauth Session object
:param kwargs: any other arguments accepted by Session.get_endpoint method
"""
if 'interface' in kwargs:
return session.get_endpoint(**kwargs)
return session.get_endpoint(interface=auth_conf.DEFAULT_VALID_INTERFACES,
**kwargs)

@ -15,6 +15,7 @@ from neutronclient.v2_0 import client as clientv20
from oslo_log import log
from oslo_utils import uuidutils
from ironic.common import context as ironic_context
from ironic.common import exception
from ironic.common.i18n import _
from ironic.common import keystone
@ -23,6 +24,8 @@ from ironic.conf import CONF
LOG = log.getLogger(__name__)
# TODO(pas-ha) remove in Rocky, until then it is a default
# for CONF.neutron.url in noauth case when endpoint_override is not set
DEFAULT_NEUTRON_URL = 'http://%s:9696' % CONF.my_ip
_NEUTRON_SESSION = None
@ -39,49 +42,53 @@ SEGMENTS_PARAM_NAME = 'segments'
def _get_neutron_session():
global _NEUTRON_SESSION
if not _NEUTRON_SESSION:
auth = keystone.get_auth('neutron')
_NEUTRON_SESSION = keystone.get_session('neutron', auth=auth)
_NEUTRON_SESSION = keystone.get_session(
'neutron',
# TODO(pas-ha) remove in Rocky
timeout=CONF.neutron.timeout or CONF.neutron.url_timeout)
return _NEUTRON_SESSION
def get_client(token=None):
params = {'retries': CONF.neutron.retries}
url = CONF.neutron.url
if CONF.neutron.auth_strategy == 'noauth':
params['endpoint_url'] = url or DEFAULT_NEUTRON_URL
params['auth_strategy'] = 'noauth'
params.update({
'timeout': CONF.neutron.url_timeout or CONF.neutron.timeout,
'insecure': CONF.neutron.insecure,
'ca_cert': CONF.neutron.cafile})
# TODO(pas-ha) remove deprecated options handling in Rocky
# until then it might look ugly due to all if's.
def get_client(token=None, context=None):
if not context:
context = ironic_context.RequestContext(auth_token=token)
# NOTE(pas-ha) neutronclient supports passing both session
# and the auth to client separately, makes things easier
session = _get_neutron_session()
service_auth = keystone.get_auth('neutron')
# TODO(pas-ha) remove in Rocky, always simply load from config
# 'noauth' then would correspond to 'auth_type=none' and
# 'endpoint_override'
adapter_params = {}
if (CONF.neutron.auth_strategy == 'noauth' and
CONF.neutron.auth_type is None):
CONF.set_override('auth_type', 'none', group='neutron')
if not CONF.neutron.endpoint_override:
adapter_params['endpoint_override'] = (CONF.neutron.url or
DEFAULT_NEUTRON_URL)
else:
session = _get_neutron_session()
if token is None:
params['session'] = session
# NOTE(pas-ha) endpoint_override==None will auto-discover
# endpoint from Keystone catalog.
# Region is needed only in this case.
# SSL related options are ignored as they are already embedded
# in keystoneauth Session object
if url:
params['endpoint_override'] = url
else:
params['region_name'] = CONF.keystone.region_name
else:
params['token'] = token
params['endpoint_url'] = url or keystone.get_service_url(
session,
service_type='network',
region_name=CONF.keystone.region_name)
params.update({
'timeout': CONF.neutron.url_timeout or CONF.neutron.timeout,
'insecure': CONF.neutron.insecure,
'ca_cert': CONF.neutron.cafile})
return clientv20.Client(**params)
def unbind_neutron_port(port_id, client=None):
if CONF.keystone.region_name and not CONF.neutron.region_name:
adapter_params['region_name'] = CONF.keystone.region_name
if CONF.neutron.url and not CONF.neutron.endpoint_override:
adapter_params['endpoint_override'] = CONF.neutron.url
adapter = keystone.get_adapter('neutron', session=session,
auth=service_auth, **adapter_params)
endpoint = adapter.get_endpoint()
user_auth = None
if CONF.neutron.auth_type != 'none' and context.auth_token:
user_auth = keystone.get_service_auth(context, endpoint, service_auth)
return clientv20.Client(session=session,
auth=user_auth or service_auth,
endpoint_override=endpoint,
retries=CONF.neutron.retries,
global_request_id=context.global_id)
def unbind_neutron_port(port_id, client=None, context=None):
"""Unbind a neutron port
Remove a neutron port's binding profile and host ID so that it returns to
@ -89,11 +96,13 @@ def unbind_neutron_port(port_id, client=None):
:param port_id: Neutron port ID.
:param client: Optional a Neutron client object.
:param context: request context
:type context: ironic.common.context.RequestContext
:raises: NetworkError
"""
if not client:
client = get_client()
client = get_client(context=context)
body = {'port': {'binding:host_id': '',
'binding:profile': {}}}
@ -111,14 +120,16 @@ def unbind_neutron_port(port_id, client=None):
raise exception.NetworkError(msg)
def update_port_address(port_id, address):
def update_port_address(port_id, address, context=None):
"""Update a port's mac address.
:param port_id: Neutron port id.
:param address: new MAC address.
:param context: request context
:type context: ironic.common.context.RequestContext
:raises: FailedToUpdateMacOnPort
"""
client = get_client()
client = get_client(context=context)
port_req_body = {'port': {'mac_address': address}}
try:
@ -134,7 +145,7 @@ def update_port_address(port_id, address):
msg = (_("Failed to remove the current binding from "
"Neutron port %s, while updating its MAC "
"address.") % port_id)
unbind_neutron_port(port_id, client=client)
unbind_neutron_port(port_id, client=client, context=context)
msg = (_("Failed to update MAC address on Neutron port %s.") % port_id)
client.update_port(port_id, port_req_body)
@ -196,7 +207,7 @@ def add_ports_to_network(task, network_uuid, security_groups=None):
:raises: NetworkError
:returns: a dictionary in the form {port.uuid: neutron_port['id']}
"""
client = get_client()
client = get_client(context=task.context)
node = task.node
# If Security Groups are specified, verify that they exist
@ -300,7 +311,7 @@ def remove_neutron_ports(task, params):
:param params: Dict of params to filter ports.
:raises: NetworkError
"""
client = get_client()
client = get_client(context=task.context)
node_uuid = task.node.uuid
try:
@ -410,11 +421,13 @@ def rollback_ports(task, network_uuid):
{'node': task.node.uuid, 'network': network_uuid})
def validate_network(uuid_or_name, net_type=_('network')):
def validate_network(uuid_or_name, net_type=_('network'), context=None):
"""Check that the given network is present.
:param uuid_or_name: network UUID or name
:param net_type: human-readable network type for error messages
:param context: request context
:type context: ironic.common.context.RequestContext
:return: network UUID
:raises: MissingParameterValue if uuid_or_name is empty
:raises: NetworkError on failure to contact Neutron
@ -424,7 +437,7 @@ def validate_network(uuid_or_name, net_type=_('network')):
raise exception.MissingParameterValue(
_('UUID or name of %s is not set in configuration') % net_type)
client = get_client()
client = get_client(context=context)
network = _get_network_by_uuid_or_name(client, uuid_or_name,
net_type=net_type, fields=['id'])
return network['id']
@ -554,16 +567,16 @@ class NeutronNetworkInterfaceMixin(object):
_cleaning_network_uuid = None
_provisioning_network_uuid = None
def get_cleaning_network_uuid(self):
def get_cleaning_network_uuid(self, context=None):
if self._cleaning_network_uuid is None:
self._cleaning_network_uuid = validate_network(
CONF.neutron.cleaning_network,
_('cleaning network'))
_('cleaning network'), context=context)
return self._cleaning_network_uuid
def get_provisioning_network_uuid(self):
def get_provisioning_network_uuid(self, context=None):
if self._provisioning_network_uuid is None:
self._provisioning_network_uuid = validate_network(
CONF.neutron.provisioning_network,
_('provisioning network'))
_('provisioning network'), context=context)
return self._provisioning_network_uuid

@ -21,6 +21,13 @@ from ironic.conf import auth
opts = [
cfg.StrOpt('url',
deprecated_for_removal=True,
deprecated_reason=_("Use [neutron]/endpoint_override option "
"instead. It has no default value and must "
"be set explicitly if required to connect "
"to specific neutron URL, for example "
"in stand alone mode when "
"[neutron]/auth_type is 'none'."),
help=_("URL for connecting to neutron. "
"Default value translates to 'http://$my_ip:9696' "
"when auth_strategy is 'noauth', "
@ -28,6 +35,9 @@ opts = [
"when auth_strategy is 'keystone'.")),
cfg.IntOpt('url_timeout',
default=30,
deprecated_for_removal=True,
deprecated_reason=_("Set the desired value explicitly using "
"the [neutron]/timeout option instead."),
help=_('Timeout value for connecting to neutron in seconds.')),
cfg.IntOpt('port_setup_delay',
default=0,
@ -40,6 +50,11 @@ opts = [
cfg.StrOpt('auth_strategy',
default='keystone',
choices=['keystone', 'noauth'],
deprecated_for_removal=True,
deprecated_reason=_("To configure neutron for noauth mode, "
"set [neutron]/auth_type = none and "
"[neutron]/endpoint_override="
"<NEUTRON_API_URL> instead"),
help=_('Authentication strategy to use when connecting to '
'neutron. Running neutron in noauth mode (related to '
'but not affected by this setting) is insecure and '
@ -80,8 +95,8 @@ opts = [
def register_opts(conf):
conf.register_opts(opts, group='neutron')
auth.register_auth_opts(conf, 'neutron')
auth.register_auth_opts(conf, 'neutron', service_type='network')
def list_opts():
return auth.add_auth_opts(opts)
return auth.add_auth_opts(opts, service_type='network')

@ -19,15 +19,19 @@ Abstract base class for dhcp providers.
import abc
from oslo_log import log as logging
import six
LOG = logging.getLogger(__name__)
@six.add_metaclass(abc.ABCMeta)
class BaseDHCP(object):
"""Base class for DHCP provider APIs."""
@abc.abstractmethod
def update_port_dhcp_opts(self, port_id, dhcp_options, token=None):
def update_port_dhcp_opts(self, port_id, dhcp_options, token=None,
context=None):
"""Update one or more DHCP options on the specified port.
:param port_id: designate which port these attributes
@ -40,10 +44,16 @@ class BaseDHCP(object):
'opt_value': 'pxelinux.0'},
{'opt_name': '66',
'opt_value': '123.123.123.456'}]
:param token: An optional authentication token.
:param token: An optional authentication token. Deprecated, use context
:param context: request context
:type context: ironic.common.context.RequestContext
:raises: FailedToUpdateDHCPOptOnPort
"""
# TODO(pas-ha) ignore token arg in Rocky
if token:
LOG.warning("Using the 'token' argument is deprecated, "
"use the 'context' argument to pass the "
"full request context instead.")
@abc.abstractmethod
def update_dhcp_opts(self, task, options, vifs=None):

@ -34,7 +34,8 @@ LOG = logging.getLogger(__name__)
class NeutronDHCPApi(base.BaseDHCP):
"""API for communicating to neutron 2.x API."""
def update_port_dhcp_opts(self, port_id, dhcp_options, token=None):
def update_port_dhcp_opts(self, port_id, dhcp_options, token=None,
context=None):
"""Update a port's attributes.
Update one or more DHCP options on the specified port.
@ -51,13 +52,17 @@ class NeutronDHCPApi(base.BaseDHCP):
'opt_value': 'pxelinux.0'},
{'opt_name': '66',
'opt_value': '123.123.123.456'}]
:param token: optional auth token.
:param token: optional auth token. Deprecated, use context.
:param context: request context
:type context: ironic.common.context.RequestContext
:raises: FailedToUpdateDHCPOptOnPort
"""
super(NeutronDHCPApi, self).update_port_dhcp_opts(
port_id, dhcp_options, token=token, context=context)
port_req_body = {'port': {'extra_dhcp_opts': dhcp_options}}
try:
neutron.get_client(token).update_port(port_id, port_req_body)
neutron.get_client(token=token, context=context).update_port(
port_id, port_req_body)
except neutron_client_exc.NeutronClientException:
LOG.exception("Failed to update Neutron port %s.", port_id)
raise exception.FailedToUpdateDHCPOptOnPort(port_id=port_id)
@ -99,7 +104,7 @@ class NeutronDHCPApi(base.BaseDHCP):
vif_list = [vif for pdict in vifs.values() for vif in pdict.values()]
for vif in vif_list:
try:
self.update_port_dhcp_opts(vif, options)
self.update_port_dhcp_opts(vif, options, context=task.context)
except exception.FailedToUpdateDHCPOptOnPort:
failures.append(vif)
@ -228,7 +233,7 @@ class NeutronDHCPApi(base.BaseDHCP):
:returns: List of IP addresses associated with
task's ports/portgroups.
"""
client = neutron.get_client()
client = neutron.get_client(context=task.context)
port_ip_addresses = self._get_ip_addresses(task, task.ports, client)
portgroup_ip_addresses = self._get_ip_addresses(

@ -19,7 +19,8 @@ from ironic.dhcp import base
class NoneDHCPApi(base.BaseDHCP):
"""No-op DHCP API."""
def update_port_dhcp_opts(self, port_id, dhcp_options, token=None):
def update_port_dhcp_opts(self, port_id, dhcp_options, token=None,
context=None):
pass
def update_dhcp_opts(self, task, options, vifs=None):

@ -267,7 +267,7 @@ def plug_port_to_tenant_network(task, port_like_obj, client=None):
body['port']['extra_dhcp_opts'] = [client_id_opt]
if not client:
client = neutron.get_client()
client = neutron.get_client(context=task.context)
try:
client.update_port(vif_id, body)
@ -402,7 +402,8 @@ class NeutronVIFPortIDMixin(VIFPortIDMixin):
vif = self._get_vif_id_by_port_like_obj(port_obj)
if 'address' in port_obj.obj_what_changed():
if vif:
neutron.update_port_address(vif, port_obj.address)
neutron.update_port_address(vif, port_obj.address,
context=task.context)
if 'extra' in port_obj.obj_what_changed():
original_port = objects.Port.get_by_id(context, port_obj.id)
@ -421,7 +422,7 @@ class NeutronVIFPortIDMixin(VIFPortIDMixin):
'opt_value': updated_client_id}
api.provider.update_port_dhcp_opts(
vif, [client_id_opt])
vif, [client_id_opt], context=task.context)
# Log warning if there is no VIF and an instance
# is associated with the node.
elif node.instance_uuid:
@ -467,7 +468,8 @@ class NeutronVIFPortIDMixin(VIFPortIDMixin):
portgroup_obj.address):
pg_vif = self._get_vif_id_by_port_like_obj(portgroup_obj)
if pg_vif:
neutron.update_port_address(pg_vif, portgroup_obj.address)
neutron.update_port_address(pg_vif, portgroup_obj.address,
context=task.context)
if 'extra' in portgroup_obj.obj_what_changed():
original_portgroup = objects.Portgroup.get_by_id(context,
@ -522,7 +524,7 @@ class NeutronVIFPortIDMixin(VIFPortIDMixin):
network.
"""
vif_id = vif_info['id']
client = neutron.get_client()
client = neutron.get_client(context=task.context)
# Determine whether any of the node's ports have a physical network. If
# not, we don't need to check the VIF's network's physical networks as
@ -549,7 +551,8 @@ class NeutronVIFPortIDMixin(VIFPortIDMixin):
# Address is optional for portgroups
if port_like_obj.address:
try:
neutron.update_port_address(vif_id, port_like_obj.address)
neutron.update_port_address(vif_id, port_like_obj.address,
context=task.context)
except exception.FailedToUpdateMacOnPort:
raise exception.NetworkError(_(
"Unable to attach VIF %(vif)s because Ironic can not "
@ -580,4 +583,4 @@ class NeutronVIFPortIDMixin(VIFPortIDMixin):
# NOTE(vsaienko) allow to unplug VIFs from ACTIVE instance.
if task.node.provision_state == states.ACTIVE:
neutron.unbind_neutron_port(vif_id)
neutron.unbind_neutron_port(vif_id, context=task.context)

@ -51,7 +51,7 @@ class FlatNetwork(common.NeutronVIFPortIDMixin,
is invalid.
:raises: MissingParameterValue, if some parameters are missing.
"""
self.get_cleaning_network_uuid()
self.get_cleaning_network_uuid(context=task.context)
def add_provisioning_network(self, task):
"""Add the provisioning network to a node.
@ -65,7 +65,7 @@ class FlatNetwork(common.NeutronVIFPortIDMixin,
if not host_id:
return
client = neutron.get_client()
client = neutron.get_client(context=task.context)
for port_like_obj in task.ports + task.portgroups:
vif_port_id = (
port_like_obj.internal_info.get(common.TENANT_VIF_KEY) or
@ -116,10 +116,12 @@ class FlatNetwork(common.NeutronVIFPortIDMixin,
:raises: NetworkError, InvalidParameterValue
"""
# If we have left over ports from a previous cleaning, remove them
neutron.rollback_ports(task, self.get_cleaning_network_uuid())
neutron.rollback_ports(task,
self.get_cleaning_network_uuid(
context=task.context))
LOG.info('Adding cleaning network to node %s', task.node.uuid)
vifs = neutron.add_ports_to_network(
task, self.get_cleaning_network_uuid())
task, self.get_cleaning_network_uuid(context=task.context))
for port in task.ports:
if port.uuid in vifs:
internal_info = port.internal_info
@ -136,8 +138,8 @@ class FlatNetwork(common.NeutronVIFPortIDMixin,
"""
LOG.info('Removing ports from cleaning network for node %s',
task.node.uuid)
neutron.remove_ports_from_network(task,
self.get_cleaning_network_uuid())
neutron.remove_ports_from_network(
task, self.get_cleaning_network_uuid(context=task.context))
for port in task.ports:
if 'cleaning_vif_port_id' in port.internal_info:
internal_info = port.internal_info

@ -57,8 +57,8 @@ class NeutronNetwork(common.NeutronVIFPortIDMixin,
is invalid.
:raises: MissingParameterValue, if some parameters are missing.
"""
self.get_cleaning_network_uuid()
self.get_provisioning_network_uuid()
self.get_cleaning_network_uuid(context=task.context)
self.get_provisioning_network_uuid(context=task.context)
def add_provisioning_network(self, task):
"""Add the provisioning network to a node.
@ -68,11 +68,12 @@ class NeutronNetwork(common.NeutronVIFPortIDMixin,
"""
# If we have left over ports from a previous provision attempt, remove
# them
neutron.rollback_ports(task, self.get_provisioning_network_uuid())
neutron.rollback_ports(
task, self.get_provisioning_network_uuid(context=task.context))
LOG.info('Adding provisioning network to node %s',
task.node.uuid)
vifs = neutron.add_ports_to_network(
task, self.get_provisioning_network_uuid(),
task, self.get_provisioning_network_uuid(context=task.context),
security_groups=CONF.neutron.provisioning_network_security_groups)
for port in task.ports:
if port.uuid in vifs:
@ -90,7 +91,7 @@ class NeutronNetwork(common.NeutronVIFPortIDMixin,
LOG.info('Removing provisioning network from node %s',
task.node.uuid)
neutron.remove_ports_from_network(
task, self.get_provisioning_network_uuid())
task, self.get_provisioning_network_uuid(context=task.context))
for port in task.ports:
if 'provisioning_vif_port_id' in port.internal_info:
internal_info = port.internal_info
@ -106,12 +107,14 @@ class NeutronNetwork(common.NeutronVIFPortIDMixin,
:returns: a dictionary in the form {port.uuid: neutron_port['id']}
"""
# If we have left over ports from a previous cleaning, remove them
neutron.rollback_ports(task, self.get_cleaning_network_uuid())
neutron.rollback_ports(task, self.get_cleaning_network_uuid(
context=task.context))
LOG.info('Adding cleaning network to node %s', task.node.uuid)
security_groups = CONF.neutron.cleaning_network_security_groups
vifs = neutron.add_ports_to_network(task,
self.get_cleaning_network_uuid(),
security_groups=security_groups)
vifs = neutron.add_ports_to_network(
task,
self.get_cleaning_network_uuid(context=task.context),
security_groups=security_groups)
for port in task.ports:
if port.uuid in vifs:
internal_info = port.internal_info
@ -128,8 +131,8 @@ class NeutronNetwork(common.NeutronVIFPortIDMixin,
"""
LOG.info('Removing cleaning network from node %s',
task.node.uuid)
neutron.remove_ports_from_network(task,
self.get_cleaning_network_uuid())
neutron.remove_ports_from_network(
task, self.get_cleaning_network_uuid(context=task.context))
for port in task.ports:
if 'cleaning_vif_port_id' in port.internal_info:
internal_info = port.internal_info
@ -158,7 +161,7 @@ class NeutronNetwork(common.NeutronVIFPortIDMixin,
ports = [p for p in ports if not p.portgroup_id]
portgroups = task.portgroups
client = neutron.get_client()
client = neutron.get_client(context=task.context)
pobj_without_vif = 0
for port_like_obj in ports + portgroups:
@ -196,4 +199,4 @@ class NeutronNetwork(common.NeutronVIFPortIDMixin,
port_like_obj.extra.get('vif_port_id'))
if not vif_port_id:
continue
neutron.unbind_neutron_port(vif_port_id)
neutron.unbind_neutron_port(vif_port_id, context=task.context)

@ -70,21 +70,6 @@ class KeystoneTestCase(base.TestCase):
keystone.get_auth,
self.test_group)
def test_get_service_url_with_interface(self):
session = mock.Mock()
session.get_endpoint.return_value = 'spam'
params = {'interface': 'admin', 'ham': 'eggs'}
self.assertEqual('spam', keystone.get_service_url(session, **params))
session.get_endpoint.assert_called_once_with(**params)
def test_get_service_url(self):
session = mock.Mock()
session.get_endpoint.return_value = 'spam'
params = {'ham': 'eggs'}
self.assertEqual('spam', keystone.get_service_url(session, **params))
session.get_endpoint.assert_called_once_with(
interface=['internal', 'public'], **params)
def test_get_adapter_from_config(self):
self.config(valid_interfaces=['internal', 'public'],
group=self.test_group)

@ -10,12 +10,14 @@
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1 import loading as kaloading
import mock
from neutronclient.common import exceptions as neutron_client_exc
from neutronclient.v2_0 import client
from oslo_config import cfg
from oslo_utils import uuidutils
from ironic.common import context
from ironic.common import exception
from ironic.common import neutron
from ironic.conductor import task_manager
@ -25,85 +27,145 @@ from ironic.tests.unit.db import base as db_base
from ironic.tests.unit.objects import utils as object_utils
@mock.patch.object(neutron, '_get_neutron_session', autospec=True)
@mock.patch.object(client.Client, "__init__", autospec=True)
@mock.patch('ironic.common.keystone.get_service_auth', autospec=True,
return_value=mock.sentinel.sauth)
@mock.patch('ironic.common.keystone.get_auth', autospec=True,
return_value=mock.sentinel.auth)
@mock.patch('ironic.common.keystone.get_adapter', autospec=True)
@mock.patch('ironic.common.keystone.get_session', autospec=True,
return_value=mock.sentinel.session)
@mock.patch.object(client.Client, "__init__", return_value=None, autospec=True)
class TestNeutronClient(base.TestCase):
def setUp(self):
super(TestNeutronClient, self).setUp()
self.config(url_timeout=30,
retries=2,
# NOTE(pas-ha) register keystoneauth dynamic options manually
plugin = kaloading.get_plugin_loader('password')
opts = kaloading.get_auth_plugin_conf_options(plugin)
self.cfg_fixture.register_opts(opts, group='neutron')
self.config(retries=2,
group='neutron')
self.config(admin_user='test-admin-user',
admin_tenant_name='test-admin-tenant',
admin_password='test-admin-password',
auth_uri='test-auth-uri',
group='keystone_authtoken')
# TODO(pas-ha) register session options to test legacy path
self.config(insecure=False,
cafile='test-file',
self.config(username='test-admin-user',
project_name='test-admin-tenant',
password='test-admin-password',
auth_url='test-auth-uri',
auth_type='password',
interface='internal',
service_type='network',
timeout=10,
group='neutron')
def test_get_neutron_client_with_token(self, mock_client_init,
mock_session):
token = 'test-token-123'
sess = mock.Mock()
sess.get_endpoint.return_value = 'fake-url'
mock_session.return_value = sess
expected = {'timeout': 30,
'retries': 2,
'insecure': False,
'ca_cert': 'test-file',
'token': token,
'endpoint_url': 'fake-url'}
mock_client_init.return_value = None
neutron.get_client(token=token)
mock_client_init.assert_called_once_with(mock.ANY, **expected)
# force-reset the global session object
neutron._NEUTRON_SESSION = None
self.context = context.RequestContext(global_request_id='global')
def _call_and_assert_client(self, client_mock, url,
auth=mock.sentinel.auth):
neutron.get_client(context=self.context)
client_mock.assert_called_once_with(mock.ANY, # this is 'self'
session=mock.sentinel.session,
auth=auth, retries=2,
endpoint_override=url,
global_request_id='global')
@mock.patch('ironic.common.context.RequestContext', autospec=True)
def test_get_neutron_client_with_token(self, mock_ctxt, mock_client_init,
mock_session, mock_adapter,
mock_auth, mock_sauth):
mock_ctxt.return_value = ctxt = mock.Mock()
ctxt.auth_token = 'test-token-123'
mock_adapter.return_value = adapter = mock.Mock()
adapter.get_endpoint.return_value = 'neutron_url'
neutron.get_client(token='test-token-123')
mock_ctxt.assert_called_once_with(auth_token='test-token-123')
mock_client_init.assert_called_once_with(
mock.ANY, # this is 'self'
session=mock.sentinel.session,
auth=mock.sentinel.sauth,
retries=2,
endpoint_override='neutron_url',
global_request_id=ctxt.global_id)
# testing handling of default url_timeout
mock_session.assert_called_once_with('neutron', timeout=10)
mock_adapter.assert_called_once_with('neutron',
session=mock.sentinel.session,
auth=mock.sentinel.auth)
mock_sauth.assert_called_once_with(mock_ctxt.return_value,
'neutron_url', mock.sentinel.auth)
def test_get_neutron_client_with_context(self, mock_client_init,
mock_session, mock_adapter,
mock_auth, mock_sauth):
self.context = context.RequestContext(global_request_id='global',
auth_token='test-token-123')
mock_adapter.return_value = adapter = mock.Mock()
adapter.get_endpoint.return_value = 'neutron_url'
self._call_and_assert_client(mock_client_init, 'neutron_url',
auth=mock.sentinel.sauth)
# testing handling of default url_timeout
mock_session.assert_called_once_with('neutron', timeout=10)
mock_adapter.assert_called_once_with('neutron',
session=mock.sentinel.session,
auth=mock.sentinel.auth)
mock_sauth.assert_called_once_with(self.context, 'neutron_url',
mock.sentinel.auth)
def test_get_neutron_client_without_token(self, mock_client_init,
mock_session):
self.config(url='test-url',
group='neutron')
sess = mock.Mock()
mock_session.return_value = sess
expected = {'retries': 2,
'endpoint_override': 'test-url',
'session': sess}
mock_client_init.return_value = None
neutron.get_client(token=None)
mock_client_init.assert_called_once_with(mock.ANY, **expected)
def test_get_neutron_client_with_region(self, mock_client_init,
mock_session):
mock_session, mock_adapter,
mock_auth, mock_sauth):
mock_adapter.return_value = adapter = mock.Mock()
adapter.get_endpoint.return_value = 'neutron_url'
self._call_and_assert_client(mock_client_init, 'neutron_url')
mock_session.assert_called_once_with('neutron', timeout=10)
mock_adapter.assert_called_once_with('neutron',
session=mock.sentinel.session,
auth=mock.sentinel.auth)
self.assertEqual(0, mock_sauth.call_count)
def test_get_neutron_client_with_deprecated_opts(self, mock_client_init,
mock_session,
mock_adapter, mock_auth,
mock_sauth):
self.config(region_name='fake_region',
group='keystone')
sess = mock.Mock()
mock_session.return_value = sess
expected = {'retries': 2,
'region_name': 'fake_region',
'session': sess}
mock_client_init.return_value = None
neutron.get_client(token=None)
mock_client_init.assert_called_once_with(mock.ANY, **expected)
def test_get_neutron_client_noauth(self, mock_client_init, mock_session):
self.config(url='neutron_url',
url_timeout=10,
timeout=None,
service_type=None,
group='neutron')
mock_adapter.return_value = adapter = mock.Mock()
adapter.get_endpoint.return_value = 'neutron_url'
self._call_and_assert_client(mock_client_init, 'neutron_url')
mock_session.assert_called_once_with('neutron', timeout=10)
mock_adapter.assert_called_once_with('neutron',
session=mock.sentinel.session,
auth=mock.sentinel.auth,
region_name='fake_region',
endpoint_override='neutron_url')
def test_get_neutron_client_noauth(self, mock_client_init, mock_session,
mock_adapter, mock_auth, mock_sauth):
self.config(auth_strategy='noauth',
url='test-url',
endpoint_override='neutron_url',
url_timeout=None,
auth_type=None,
timeout=10,
group='neutron')
expected = {'ca_cert': 'test-file',
'insecure': False,
'endpoint_url': 'test-url',
'timeout': 30,
'retries': 2,
'auth_strategy': 'noauth'}
mock_client_init.return_value = None
neutron.get_client(token=None)
mock_client_init.assert_called_once_with(mock.ANY, **expected)
def test_out_range_auth_strategy(self, mock_client_init, mock_session):
mock_adapter.return_value = adapter = mock.Mock()
adapter.get_endpoint.return_value = 'neutron_url'
self._call_and_assert_client(mock_client_init, 'neutron_url')
self.assertEqual('none', neutron.CONF.neutron.auth_type)
mock_session.assert_called_once_with('neutron', timeout=10)
mock_adapter.assert_called_once_with('neutron',
session=mock.sentinel.session,
auth=mock.sentinel.auth)
mock_auth.assert_called_once_with('neutron')
self.assertEqual(0, mock_sauth.call_count)
def test_out_range_auth_strategy(self, mock_client_init, mock_session,
mock_adapter, mock_auth, mock_eauth):
self.assertRaises(ValueError, cfg.CONF.set_override,
'auth_strategy', 'fake', 'neutron')
@ -473,6 +535,7 @@ class TestValidateNetwork(base.TestCase):
super(TestValidateNetwork, self).setUp()
self.uuid = uuidutils.generate_uuid()
self.context = context.RequestContext()
def test_by_uuid(self, client_mock):
net_mock = client_mock.return_value.list_networks
@ -482,7 +545,8 @@ class TestValidateNetwork(base.TestCase):
]
}
self.assertEqual(self.uuid, neutron.validate_network(self.uuid))
self.assertEqual(self.uuid, neutron.validate_network(
self.uuid, context=self.context))
net_mock.assert_called_once_with(fields=['id'],
id=self.uuid)
@ -494,7 +558,8 @@ class TestValidateNetwork(base.TestCase):
]
}
self.assertEqual(self.uuid, neutron.validate_network('name'))
self.assertEqual(self.uuid, neutron.validate_network(
'name', context=self.context))
net_mock.assert_called_once_with(fields=['id'],
name='name')
@ -506,7 +571,8 @@ class TestValidateNetwork(base.TestCase):
self.assertRaisesRegex(exception.InvalidParameterValue,
'was not found',
neutron.validate_network, self.uuid)
neutron.validate_network,
self.uuid, context=self.context)
net_mock.assert_called_once_with(fields=['id'],
id=self.uuid)
@ -515,7 +581,8 @@ class TestValidateNetwork(base.TestCase):
net_mock.side_effect = neutron_client_exc.NeutronClientException('foo')
self.assertRaisesRegex(exception.NetworkError, 'foo',
neutron.validate_network, 'name')
neutron.validate_network, 'name',
context=self.context)
net_mock.assert_called_once_with(fields=['id'],
name='name')
@ -528,7 +595,8 @@ class TestValidateNetwork(base.TestCase):
self.assertRaisesRegex(exception.InvalidParameterValue,
'More than one network',
neutron.validate_network, 'name')
neutron.validate_network, 'name',
context=self.context)
net_mock.assert_called_once_with(fields=['id'],
name='name')
@ -536,13 +604,17 @@ class TestValidateNetwork(base.TestCase):
@mock.patch.object(neutron, 'get_client', autospec=True)
class TestUpdatePortAddress(base.TestCase):
def setUp(self):
super(TestUpdatePortAddress, self).setUp()
self.context = context.RequestContext()
def test_update_port_address(self, mock_client):
address = 'fe:54:00:77:07:d9'
port_id = 'fake-port-id'
expected = {'port': {'mac_address': address}}
mock_client.return_value.show_port.return_value = {}
neutron.update_port_address(port_id, address)
neutron.update_port_address(port_id, address, context=self.context)
mock_client.return_value.update_port.assert_called_once_with(port_id,
expected)
@ -559,8 +631,11 @@ class TestUpdatePortAddress(base.TestCase):
mock.call(port_id, {'port': {'binding:host_id': 'host',
'binding:profile': 'foo'}})]
neutron.update_port_address(port_id, address)
mock_unp.assert_called_once_with(port_id, client=mock_client())
neutron.update_port_address(port_id, address, context=self.context)
mock_unp.assert_called_once_with(
port_id,
client=mock_client(context=self.context),
context=self.context)
mock_client.return_value.update_port.assert_has_calls(calls)
@mock.patch.object(neutron, 'unbind_neutron_port', autospec=True)
@ -571,7 +646,7 @@ class TestUpdatePortAddress(base.TestCase):
mock_client.return_value.show_port.return_value = {
'port': {'binding:profile': 'foo'}}
neutron.update_port_address(port_id, address)
neutron.update_port_address(port_id, address, context=self.context)
self.assertFalse(mock_unp.called)
mock_client.return_value.update_port.assert_any_call(port_id, expected)
@ -582,7 +657,8 @@ class TestUpdatePortAddress(base.TestCase):
neutron_client_exc.NeutronClientException())
self.assertRaises(exception.FailedToUpdateMacOnPort,
neutron.update_port_address, port_id, address)
neutron.update_port_address,
port_id, address, context=self.context)
self.assertFalse(mock_client.return_value.update_port.called)
@mock.patch.object(neutron, 'unbind_neutron_port', autospec=True)
@ -595,8 +671,12 @@ class TestUpdatePortAddress(base.TestCase):
'binding:host_id': 'host'}}
mock_unp.side_effect = (exception.NetworkError('boom'))
self.assertRaises(exception.FailedToUpdateMacOnPort,
neutron.update_port_address, port_id, address)
mock_unp.assert_called_once_with(port_id, client=mock_client())
neutron.update_port_address,
port_id, address, context=self.context)
mock_unp.assert_called_once_with(
port_id,
client=mock_client(context=self.context),
context=self.context)
self.assertFalse(mock_client.return_value.update_port.called)
@mock.patch.object(neutron, 'unbind_neutron_port', autospec=True)
@ -610,12 +690,16 @@ class TestUpdatePortAddress(base.TestCase):
self.assertRaises(exception.FailedToUpdateMacOnPort,
neutron.update_port_address,
port_id, address)
port_id, address, context=self.context)
@mock.patch.object(neutron, 'get_client', autospec=True)
class TestUnbindPort(base.TestCase):
def setUp(self):
super(TestUnbindPort, self).setUp()
self.context = context.RequestContext()
def test_unbind_neutron_port_client_passed(self, mock_client):
port_id = 'fake-port-id'
body = {
@ -624,7 +708,9 @@ class TestUnbindPort(base.TestCase):
'binding:profile': {}
}
}
neutron.unbind_neutron_port(port_id, mock_client())
neutron.unbind_neutron_port(port_id,
mock_client(context=self.context),
context=self.context)
self.assertEqual(1, mock_client.call_count)
mock_client.return_value.update_port.assert_called_once_with(port_id,
body)
@ -641,8 +727,8 @@ class TestUnbindPort(base.TestCase):
}
port_id = 'fake-port-id'
self.assertRaises(exception.NetworkError, neutron.unbind_neutron_port,
port_id)
mock_client.assert_called_once_with()
port_id, context=self.context)
mock_client.assert_called_once_with(context=self.context)
mock_client.return_value.update_port.assert_called_once_with(port_id,
body)
mock_log.exception.assert_called_once()
@ -655,8 +741,8 @@ class TestUnbindPort(base.TestCase):
'binding:profile': {}
}
}
neutron.unbind_neutron_port(port_id)
mock_client.assert_called_once_with()
neutron.unbind_neutron_port(port_id, context=self.context)
mock_client.assert_called_once_with(context=self.context)
mock_client.return_value.update_port.assert_called_once_with(port_id,
body)
@ -671,8 +757,8 @@ class TestUnbindPort(base.TestCase):
'binding:profile': {}
}
}
neutron.unbind_neutron_port(port_id)
mock_client.assert_called_once_with()
neutron.unbind_neutron_port(port_id, context=self.context)
mock_client.assert_called_once_with(context=self.context)
mock_client.return_value.update_port.assert_called_once_with(port_id,
body)
mock_log.info.assert_called_once_with('Port %s was not found while '

@ -63,7 +63,9 @@ class TestNeutron(db_base.DbTestCase):
expected = {'port': {'extra_dhcp_opts': opts}}
api = dhcp_factory.DHCPFactory()
api.provider.update_port_dhcp_opts(port_id, opts)
with task_manager.acquire(self.context, self.node.uuid) as task:
api.provider.update_port_dhcp_opts(port_id, opts,
context=task.context)
client_mock.return_value.update_port.assert_called_once_with(
port_id, expected)
@ -75,10 +77,11 @@ class TestNeutron(db_base.DbTestCase):
neutron_client_exc.NeutronClientException())
api = dhcp_factory.DHCPFactory()
self.assertRaises(
exception.FailedToUpdateDHCPOptOnPort,
api.provider.update_port_dhcp_opts,
port_id, opts)
with task_manager.acquire(self.context, self.node.uuid) as task:
self.assertRaises(
exception.FailedToUpdateDHCPOptOnPort,
api.provider.update_port_dhcp_opts,
port_id, opts, context=task.context)
@mock.patch('ironic.dhcp.neutron.NeutronDHCPApi.update_port_dhcp_opts',
autospec=True)
@ -92,7 +95,8 @@ class TestNeutron(db_base.DbTestCase):
opts = pxe_utils.dhcp_options_for_instance(task)
api = dhcp_factory.DHCPFactory()
api.update_dhcp(task, opts)
mock_updo.assert_called_once_with(mock.ANY, 'vif-uuid', opts)
mock_updo.assert_called_once_with(mock.ANY, 'vif-uuid', opts,
context=task.context)
@mock.patch('ironic.dhcp.neutron.NeutronDHCPApi.update_port_dhcp_opts',
autospec=True)
@ -144,10 +148,8 @@ class TestNeutron(db_base.DbTestCase):
@mock.patch.object(neutron, 'LOG', autospec=True)
@mock.patch('time.sleep', autospec=True)
@mock.patch.object(neutron.NeutronDHCPApi, 'update_port_dhcp_opts',
autospec=True)
@mock.patch('ironic.common.network.get_node_vif_ids', autospec=True)
def test_update_dhcp_set_sleep_and_fake(self, mock_gnvi, mock_updo,
def test_update_dhcp_set_sleep_and_fake(self, mock_gnvi,
mock_ts, mock_log):
mock_gnvi.return_value = {'ports': {'port-uuid': 'vif-uuid'},
'portgroups': {}}
@ -156,27 +158,30 @@ class TestNeutron(db_base.DbTestCase):
self.node.uuid) as task:
opts = pxe_utils.dhcp_options_for_instance(task)
api = dhcp_factory.DHCPFactory()
api.update_dhcp(task, opts)
mock_log.debug.assert_called_once_with(
"Waiting %d seconds for Neutron.", 30)
mock_ts.assert_called_with(30)
mock_updo.assert_called_once_with(mock.ANY, 'vif-uuid', opts)
with mock.patch.object(api.provider, 'update_port_dhcp_opts',
autospec=True) as mock_updo:
api.update_dhcp(task, opts)
mock_log.debug.assert_called_once_with(
"Waiting %d seconds for Neutron.", 30)
mock_ts.assert_called_with(30)
mock_updo.assert_called_once_with('vif-uuid', opts,
context=task.context)
@mock.patch.object(neutron, 'LOG', autospec=True)
@mock.patch.object(neutron.NeutronDHCPApi, 'update_port_dhcp_opts',
autospec=True)
@mock.patch('ironic.common.network.get_node_vif_ids', autospec=True)
def test_update_dhcp_unset_sleep_and_fake(self, mock_gnvi, mock_updo,
mock_log):
def test_update_dhcp_unset_sleep_and_fake(self, mock_gnvi, mock_log):
mock_gnvi.return_value = {'ports': {'port-uuid': 'vif-uuid'},
'portgroups': {}}
with task_manager.acquire(self.context,
self.node.uuid) as task:
opts = pxe_utils.dhcp_options_for_instance(task)
api = dhcp_factory.DHCPFactory()
api.update_dhcp(task, opts)
mock_log.debug.assert_not_called()
mock_updo.assert_called_once_with(mock.ANY,