From 4d43262955f8882cdeee2a042e852eaa8396178b Mon Sep 17 00:00:00 2001 From: Pavlo Shchelokovskyy Date: Wed, 21 Jun 2017 07:46:03 +0000 Subject: [PATCH] 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 --- devstack/lib/ironic | 2 +- .../include/configure-ironic-conductor.rst | 2 +- etc/ironic/ironic.conf.sample | 72 ++++- ironic/common/keystone.py | 22 -- ironic/common/neutron.py | 109 ++++---- ironic/conf/neutron.py | 19 +- ironic/dhcp/base.py | 16 +- ironic/dhcp/neutron.py | 17 +- ironic/dhcp/none.py | 3 +- ironic/drivers/modules/network/common.py | 17 +- ironic/drivers/modules/network/flat.py | 14 +- ironic/drivers/modules/network/neutron.py | 29 +- ironic/tests/unit/common/test_keystone.py | 15 -- ironic/tests/unit/common/test_neutron.py | 252 ++++++++++++------ ironic/tests/unit/dhcp/test_neutron.py | 47 ++-- .../drivers/modules/network/test_common.py | 49 ++-- .../unit/drivers/modules/network/test_flat.py | 13 +- .../drivers/modules/network/test_neutron.py | 44 +-- ...recated-neutron-opts-2e1d9e65f00301d3.yaml | 49 ++++ .../keystoneauth-config-1baa45a0a2dd93b4.yaml | 4 + 20 files changed, 510 insertions(+), 285 deletions(-) create mode 100644 releasenotes/notes/deprecated-neutron-opts-2e1d9e65f00301d3.yaml diff --git a/devstack/lib/ironic b/devstack/lib/ironic index 0600ab3dc3..04b23579e0 100644 --- a/devstack/lib/ironic +++ b/devstack/lib/ironic @@ -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 diff --git a/doc/source/install/include/configure-ironic-conductor.rst b/doc/source/install/include/configure-ironic-conductor.rst index 2279a63072..cfb4734997 100644 --- a/doc/source/install/include/configure-ironic-conductor.rst +++ b/doc/source/install/include/configure-ironic-conductor.rst @@ -150,7 +150,7 @@ Configuring ironic-conductor service [neutron] # URL for connecting to neutron. (string value) - url= + endpoint_override = #. Configure a specific ironic-api service URL - only if you do not want to use discovery of the Baremetal service endpoint from keystone catalog diff --git a/etc/ironic/ironic.conf.sample b/etc/ironic/ironic.conf.sample index e821dcbf6d..9fe966a53d 100644 --- a/etc/ironic/ironic.conf.sample +++ b/etc/ironic/ironic.conf.sample @@ -2587,11 +2587,16 @@ # Authentication URL (string value) #auth_url = -# 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= instead #auth_strategy = keystone # Authentication type to load (string value) @@ -2637,12 +2642,28 @@ # Domain name to scope to (string value) #domain_name = +# Always use this endpoint URL for requests for this client. +# (string value) +#endpoint_override = + # Verify HTTPS connections. (boolean value) #insecure = false # PEM encoded client certificate key file (string value) #keyfile = +# 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 = + +# 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 = + # User's password (string value) #password = @@ -2679,10 +2700,22 @@ # is used. (list value) #provisioning_network_security_groups = +# The default region_name for endpoint URL discovery. (string +# value) +#region_name = + # 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 = + +# The default service_type for endpoint URL discovery. (string +# value) +#service_type = network + # Tenant ID (string value) #tenant_id = @@ -2695,14 +2728,24 @@ # Trust ID (string value) #trust_id = -# 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 = -# 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 = +# 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 = + [oneview] diff --git a/ironic/common/keystone.py b/ironic/common/keystone.py index 53f2637169..ba167a1b1c 100644 --- a/ironic/common/keystone.py +++ b/ironic/common/keystone.py @@ -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) diff --git a/ironic/common/neutron.py b/ironic/common/neutron.py index 4bba8d124f..aa30c0a50f 100644 --- a/ironic/common/neutron.py +++ b/ironic/common/neutron.py @@ -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}) + 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() - return clientv20.Client(**params) + 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): +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 diff --git a/ironic/conf/neutron.py b/ironic/conf/neutron.py index 2012daf376..86d0fca1b4 100644 --- a/ironic/conf/neutron.py +++ b/ironic/conf/neutron.py @@ -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=" + " 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') diff --git a/ironic/dhcp/base.py b/ironic/dhcp/base.py index 6e694be01e..88dd3e4e0d 100644 --- a/ironic/dhcp/base.py +++ b/ironic/dhcp/base.py @@ -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): diff --git a/ironic/dhcp/neutron.py b/ironic/dhcp/neutron.py index b885c18512..27c4366d0e 100644 --- a/ironic/dhcp/neutron.py +++ b/ironic/dhcp/neutron.py @@ -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( diff --git a/ironic/dhcp/none.py b/ironic/dhcp/none.py index eadf114749..c38484b0bf 100644 --- a/ironic/dhcp/none.py +++ b/ironic/dhcp/none.py @@ -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): diff --git a/ironic/drivers/modules/network/common.py b/ironic/drivers/modules/network/common.py index d01f9cfb3a..563f09130f 100644 --- a/ironic/drivers/modules/network/common.py +++ b/ironic/drivers/modules/network/common.py @@ -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) diff --git a/ironic/drivers/modules/network/flat.py b/ironic/drivers/modules/network/flat.py index bdb6486fb4..51d5ffa065 100644 --- a/ironic/drivers/modules/network/flat.py +++ b/ironic/drivers/modules/network/flat.py @@ -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 diff --git a/ironic/drivers/modules/network/neutron.py b/ironic/drivers/modules/network/neutron.py index fef7e76d89..b88b477031 100644 --- a/ironic/drivers/modules/network/neutron.py +++ b/ironic/drivers/modules/network/neutron.py @@ -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) diff --git a/ironic/tests/unit/common/test_keystone.py b/ironic/tests/unit/common/test_keystone.py index 9babe028c4..c9c54cff09 100644 --- a/ironic/tests/unit/common/test_keystone.py +++ b/ironic/tests/unit/common/test_keystone.py @@ -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) diff --git a/ironic/tests/unit/common/test_neutron.py b/ironic/tests/unit/common/test_neutron.py index e498e06317..15cb50e686 100644 --- a/ironic/tests/unit/common/test_neutron.py +++ b/ironic/tests/unit/common/test_neutron.py @@ -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') + # force-reset the global session object + neutron._NEUTRON_SESSION = None + self.context = context.RequestContext(global_request_id='global') - 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'} + 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_client_init.return_value = None - neutron.get_client(token=token) - mock_client_init.assert_called_once_with(mock.ANY, **expected) + @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) + 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_region(self, mock_client_init, - mock_session): + 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(auth_strategy='noauth', - url='test-url', + self.config(url='neutron_url', + url_timeout=10, + timeout=None, + service_type=None, group='neutron') - expected = {'ca_cert': 'test-file', - 'insecure': False, - 'endpoint_url': 'test-url', - 'timeout': 30, - 'retries': 2, - 'auth_strategy': 'noauth'} + 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') - 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, + mock_adapter, mock_auth, mock_sauth): + self.config(auth_strategy='noauth', + endpoint_override='neutron_url', + url_timeout=None, + auth_type=None, + timeout=10, + group='neutron') + mock_adapter.return_value = adapter = mock.Mock() + adapter.get_endpoint.return_value = 'neutron_url' - def test_out_range_auth_strategy(self, mock_client_init, mock_session): + 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 ' diff --git a/ironic/tests/unit/dhcp/test_neutron.py b/ironic/tests/unit/dhcp/test_neutron.py index 0f10d66be6..ac7fee7f0a 100644 --- a/ironic/tests/unit/dhcp/test_neutron.py +++ b/ironic/tests/unit/dhcp/test_neutron.py @@ -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, '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_not_called() + mock_updo.assert_called_once_with('vif-uuid', opts, + context=task.context) def test__get_fixed_ip_address(self): port_id = 'fake-port-id' diff --git a/ironic/tests/unit/drivers/modules/network/test_common.py b/ironic/tests/unit/drivers/modules/network/test_common.py index 6c76540b2d..4fc0d513a3 100644 --- a/ironic/tests/unit/drivers/modules/network/test_common.py +++ b/ironic/tests/unit/drivers/modules/network/test_common.py @@ -680,10 +680,11 @@ class TestNeutronVifPortIDMixin(db_base.DbTestCase): mock_gfp.return_value = self.port with task_manager.acquire(self.context, self.node.id) as task: self.interface.vif_attach(task, vif) - mock_client.assert_called_once_with() + mock_client.assert_called_once_with(context=task.context) + mock_upa.assert_called_once_with( + "fake_vif_id", self.port.address, context=task.context) self.assertFalse(mock_gpbpi.called) mock_gfp.assert_called_once_with(task, 'fake_vif_id', set()) - mock_upa.assert_called_once_with("fake_vif_id", self.port.address) mock_save.assert_called_once_with(self.port, "fake_vif_id") @mock.patch.object(common.VIFPortIDMixin, '_save_vif_to_port_like_obj') @@ -717,11 +718,12 @@ class TestNeutronVifPortIDMixin(db_base.DbTestCase): mock_gfp.return_value = self.port with task_manager.acquire(self.context, self.node.id) as task: self.interface.vif_attach(task, vif) - mock_client.assert_called_once_with() + mock_client.assert_called_once_with(context=task.context) + mock_upa.assert_called_once_with( + "fake_vif_id", self.port.address, context=task.context) mock_gpbpi.assert_called_once_with(mock_client.return_value, 'fake_vif_id') mock_gfp.assert_called_once_with(task, 'fake_vif_id', {'physnet1'}) - mock_upa.assert_called_once_with("fake_vif_id", self.port.address) mock_save.assert_called_once_with(self.port, "fake_vif_id") @mock.patch.object(common.VIFPortIDMixin, '_save_vif_to_port_like_obj') @@ -739,10 +741,12 @@ class TestNeutronVifPortIDMixin(db_base.DbTestCase): mock_gfp.return_value = self.port with task_manager.acquire(self.context, self.node.id) as task: self.interface.vif_attach(task, vif) - mock_client.assert_called_once_with() + mock_client.assert_called_once_with(context=task.context) + mock_upa.assert_called_once_with( + "fake_vif_id", self.port.address, context=task.context) self.assertFalse(mock_gpbpi.called) mock_gfp.assert_called_once_with(task, 'fake_vif_id', set()) - mock_upa.assert_called_once_with("fake_vif_id", self.port.address) + mock_save.assert_called_once_with(self.port, "fake_vif_id") mock_plug.assert_called_once_with(task, self.port, mock.ANY) @mock.patch.object(common.VIFPortIDMixin, '_save_vif_to_port_like_obj') @@ -763,10 +767,11 @@ class TestNeutronVifPortIDMixin(db_base.DbTestCase): with task_manager.acquire(self.context, self.node.id) as task: self.assertRaises(exception.NetworkError, self.interface.vif_attach, task, vif) - mock_client.assert_called_once_with() + mock_client.assert_called_once_with(context=task.context) + mock_upa.assert_called_once_with( + "fake_vif_id", self.port.address, context=task.context) self.assertFalse(mock_gpbpi.called) mock_gfp.assert_called_once_with(task, 'fake_vif_id', set()) - mock_upa.assert_called_once_with("fake_vif_id", self.port.address) mock_save.assert_called_once_with(self.port, "fake_vif_id") mock_plug.assert_called_once_with(task, self.port, mock.ANY) @@ -784,7 +789,7 @@ class TestNeutronVifPortIDMixin(db_base.DbTestCase): vif = {'id': "fake_vif_id"} with task_manager.acquire(self.context, self.node.id) as task: self.interface.vif_attach(task, vif) - mock_client.assert_called_once_with() + mock_client.assert_called_once_with(context=task.context) self.assertFalse(mock_gpbpi.called) mock_gfp.assert_called_once_with(task, 'fake_vif_id', set()) self.assertFalse(mock_client.return_value.show_port.called) @@ -809,7 +814,7 @@ class TestNeutronVifPortIDMixin(db_base.DbTestCase): self.assertRaisesRegex( exception.NetworkError, "can not update Neutron port", self.interface.vif_attach, task, vif) - mock_client.assert_called_once_with() + mock_client.assert_called_once_with(context=task.context) mock_gpbpi.assert_called_once_with(mock_client.return_value, 'fake_vif_id') self.assertFalse(mock_save.called) @@ -833,7 +838,7 @@ class TestNeutronVifPortIDMixin(db_base.DbTestCase): self.assertRaises( exception.PortgroupPhysnetInconsistent, self.interface.vif_attach, task, vif) - mock_client.assert_called_once_with() + mock_client.assert_called_once_with(context=task.context) mock_gpbpi.assert_called_once_with(mock_client.return_value, 'fake_vif_id') self.assertFalse(mock_upa.called) @@ -859,7 +864,7 @@ class TestNeutronVifPortIDMixin(db_base.DbTestCase): self.assertRaises( exception.VifInvalidForAttach, self.interface.vif_attach, task, vif) - mock_client.assert_called_once_with() + mock_client.assert_called_once_with(context=task.context) mock_gpbpi.assert_called_once_with(mock_client.return_value, 'fake_vif_id') self.assertFalse(mock_gfp.called) @@ -913,9 +918,10 @@ class TestNeutronVifPortIDMixin(db_base.DbTestCase): mock_get.return_value = self.port with task_manager.acquire(self.context, self.node.id) as task: self.interface.vif_detach(task, 'fake_vif_id') + mock_unp.assert_called_once_with('fake_vif_id', + context=task.context) mock_get.assert_called_once_with(task, 'fake_vif_id') mock_clear.assert_called_once_with(self.port) - mock_unp.assert_called_once_with('fake_vif_id') @mock.patch.object(common.VIFPortIDMixin, '_clear_vif_from_port_like_obj') @mock.patch.object(neutron_common, 'unbind_neutron_port', autospec=True) @@ -929,9 +935,10 @@ class TestNeutronVifPortIDMixin(db_base.DbTestCase): with task_manager.acquire(self.context, self.node.id) as task: self.assertRaises(exception.NetworkError, self.interface.vif_detach, task, 'fake_vif_id') + mock_unp.assert_called_once_with('fake_vif_id', + context=task.context) mock_get.assert_called_once_with(task, 'fake_vif_id') mock_clear.assert_called_once_with(self.port) - mock_unp.assert_called_once_with('fake_vif_id') @mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id', autospec=True) @@ -942,7 +949,8 @@ class TestNeutronVifPortIDMixin(db_base.DbTestCase): with task_manager.acquire(self.context, self.node.id) as task: self.interface.port_changed(task, self.port) mac_update_mock.assert_called_once_with( - self.port.extra['vif_port_id'], new_address) + self.port.extra['vif_port_id'], new_address, + context=task.context) self.assertFalse(mock_warn.called) @mock.patch.object(neutron_common, 'update_port_address', autospec=True) @@ -956,7 +964,8 @@ class TestNeutronVifPortIDMixin(db_base.DbTestCase): self.interface.port_changed, task, self.port) mac_update_mock.assert_called_once_with( - self.port.extra['vif_port_id'], new_address) + self.port.extra['vif_port_id'], new_address, + context=task.context) @mock.patch.object(neutron_common, 'update_port_address', autospec=True) def test_port_changed_address_no_vif_id(self, mac_update_mock): @@ -975,7 +984,7 @@ class TestNeutronVifPortIDMixin(db_base.DbTestCase): with task_manager.acquire(self.context, self.node.id) as task: self.interface.port_changed(task, self.port) dhcp_update_mock.assert_called_once_with( - 'fake-id', expected_dhcp_opts) + 'fake-id', expected_dhcp_opts, context=task.context) @mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id', autospec=True) @@ -1194,7 +1203,8 @@ class TestNeutronVifPortIDMixin(db_base.DbTestCase): pg.address = new_address with task_manager.acquire(self.context, self.node.id) as task: self.interface.portgroup_changed(task, pg) - mac_update_mock.assert_called_once_with('fake-id', new_address) + mac_update_mock.assert_called_once_with( + 'fake-id', new_address, context=task.context) @mock.patch.object(neutron_common, 'update_port_address', autospec=True) def test_update_portgroup_remove_address(self, mac_update_mock): @@ -1261,7 +1271,8 @@ class TestNeutronVifPortIDMixin(db_base.DbTestCase): self.assertRaises(exception.FailedToUpdateMacOnPort, self.interface.portgroup_changed, task, pg) - mac_update_mock.assert_called_once_with('fake-id', new_address) + mac_update_mock.assert_called_once_with( + 'fake-id', new_address, context=task.context) @mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id', autospec=True) diff --git a/ironic/tests/unit/drivers/modules/network/test_flat.py b/ironic/tests/unit/drivers/modules/network/test_flat.py index 1c4228dbb2..0a72122f65 100644 --- a/ironic/tests/unit/drivers/modules/network/test_flat.py +++ b/ironic/tests/unit/drivers/modules/network/test_flat.py @@ -77,11 +77,12 @@ class TestFlatInterface(db_base.DbTestCase): def test_validate(self, validate_mock): with task_manager.acquire(self.context, self.node.id) as task: self.interface.validate(task) - validate_mock.assert_called_once_with(CONF.neutron.cleaning_network, - 'cleaning network') + validate_mock.assert_called_once_with( + CONF.neutron.cleaning_network, + 'cleaning network', context=task.context) @mock.patch.object(neutron, 'validate_network', - side_effect=lambda n, t: n) + side_effect=lambda n, t, context=None: n) @mock.patch.object(neutron, 'add_ports_to_network') @mock.patch.object(neutron, 'rollback_ports') def test_add_cleaning_network(self, rollback_mock, add_mock, @@ -95,13 +96,13 @@ class TestFlatInterface(db_base.DbTestCase): task, CONF.neutron.cleaning_network) validate_mock.assert_called_once_with( CONF.neutron.cleaning_network, - 'cleaning network') + 'cleaning network', context=task.context) self.port.refresh() self.assertEqual('vif-port-id', self.port.internal_info['cleaning_vif_port_id']) @mock.patch.object(neutron, 'validate_network', - side_effect=lambda n, t: n) + side_effect=lambda n, t, context=None: n) @mock.patch.object(neutron, 'remove_ports_from_network') def test_remove_cleaning_network(self, remove_mock, validate_mock): with task_manager.acquire(self.context, self.node.id) as task: @@ -110,7 +111,7 @@ class TestFlatInterface(db_base.DbTestCase): task, CONF.neutron.cleaning_network) validate_mock.assert_called_once_with( CONF.neutron.cleaning_network, - 'cleaning network') + 'cleaning network', context=task.context) self.port.refresh() self.assertNotIn('cleaning_vif_port_id', self.port.internal_info) diff --git a/ironic/tests/unit/drivers/modules/network/test_neutron.py b/ironic/tests/unit/drivers/modules/network/test_neutron.py index 65c413b0e8..704b6f9984 100644 --- a/ironic/tests/unit/drivers/modules/network/test_neutron.py +++ b/ironic/tests/unit/drivers/modules/network/test_neutron.py @@ -86,14 +86,16 @@ class NeutronInterfaceTestCase(db_base.DbTestCase): def test_validate(self, validate_mock): with task_manager.acquire(self.context, self.node.id) as task: self.interface.validate(task) - self.assertEqual([mock.call(CONF.neutron.cleaning_network, - 'cleaning network'), - mock.call(CONF.neutron.provisioning_network, - 'provisioning network')], - validate_mock.call_args_list) + self.assertEqual([mock.call(CONF.neutron.cleaning_network, + 'cleaning network', + context=task.context), + mock.call(CONF.neutron.provisioning_network, + 'provisioning network', + context=task.context)], + validate_mock.call_args_list) @mock.patch.object(neutron_common, 'validate_network', - side_effect=lambda n, t: n) + 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_provisioning_network(self, add_ports_mock, rollback_mock, @@ -110,13 +112,13 @@ class NeutronInterfaceTestCase(db_base.DbTestCase): security_groups=[]) validate_mock.assert_called_once_with( CONF.neutron.provisioning_network, - 'provisioning network') + 'provisioning network', context=task.context) self.port.refresh() self.assertEqual(self.neutron_port['id'], self.port.internal_info['provisioning_vif_port_id']) @mock.patch.object(neutron_common, 'validate_network', - lambda n, t: n) + 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_provisioning_network_with_sg(self, add_ports_mock, @@ -141,7 +143,7 @@ class NeutronInterfaceTestCase(db_base.DbTestCase): self.port.internal_info['provisioning_vif_port_id']) @mock.patch.object(neutron_common, 'validate_network', - side_effect=lambda n, t: n) + side_effect=lambda n, t, context=None: n) @mock.patch.object(neutron_common, 'remove_ports_from_network') def test_remove_provisioning_network(self, remove_ports_mock, validate_mock): @@ -153,12 +155,12 @@ class NeutronInterfaceTestCase(db_base.DbTestCase): task, CONF.neutron.provisioning_network) validate_mock.assert_called_once_with( CONF.neutron.provisioning_network, - 'provisioning network') + 'provisioning network', context=task.context) self.port.refresh() self.assertNotIn('provisioning_vif_port_id', self.port.internal_info) @mock.patch.object(neutron_common, 'validate_network', - side_effect=lambda n, t: n) + 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_cleaning_network(self, add_ports_mock, rollback_mock, @@ -171,13 +173,13 @@ class NeutronInterfaceTestCase(db_base.DbTestCase): self.assertEqual(res, add_ports_mock.return_value) validate_mock.assert_called_once_with( CONF.neutron.cleaning_network, - 'cleaning network') + 'cleaning network', context=task.context) self.port.refresh() self.assertEqual(self.neutron_port['id'], self.port.internal_info['cleaning_vif_port_id']) @mock.patch.object(neutron_common, 'validate_network', - lambda n, t: n) + 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_cleaning_network_with_sg(self, add_ports_mock, rollback_mock): @@ -199,7 +201,7 @@ class NeutronInterfaceTestCase(db_base.DbTestCase): self.port.internal_info['cleaning_vif_port_id']) @mock.patch.object(neutron_common, 'validate_network', - side_effect=lambda n, t: n) + side_effect=lambda n, t, context=None: n) @mock.patch.object(neutron_common, 'remove_ports_from_network') def test_remove_cleaning_network(self, remove_ports_mock, validate_mock): @@ -211,7 +213,7 @@ class NeutronInterfaceTestCase(db_base.DbTestCase): task, CONF.neutron.cleaning_network) validate_mock.assert_called_once_with( CONF.neutron.cleaning_network, - 'cleaning network') + 'cleaning network', context=task.context) self.port.refresh() self.assertNotIn('cleaning_vif_port_id', self.port.internal_info) @@ -220,7 +222,7 @@ class NeutronInterfaceTestCase(db_base.DbTestCase): with task_manager.acquire(self.context, self.node.id) as task: self.interface.unconfigure_tenant_networks(task) mock_unbind_port.assert_called_once_with( - self.port.extra['vif_port_id']) + self.port.extra['vif_port_id'], context=task.context) def test_configure_tenant_networks_no_ports_for_node(self): n = utils.create_test_node(self.context, network_interface='neutron', @@ -243,7 +245,7 @@ class NeutronInterfaceTestCase(db_base.DbTestCase): 'associated with node', self.interface.configure_tenant_networks, task) - client_mock.assert_called_once_with() + client_mock.assert_called_once_with(context=task.context) upd_mock.assert_not_called() self.assertIn('No neutron ports or portgroups are associated with', log_mock.error.call_args[0][0]) @@ -267,7 +269,7 @@ class NeutronInterfaceTestCase(db_base.DbTestCase): client_mock.return_value.update_port = upd_mock with task_manager.acquire(self.context, self.node.id) as task: self.interface.configure_tenant_networks(task) - client_mock.assert_called_once_with() + client_mock.assert_called_once_with(context=task.context) upd_mock.assert_called_once_with(self.port.extra['vif_port_id'], expected_body) @@ -280,7 +282,7 @@ class NeutronInterfaceTestCase(db_base.DbTestCase): self.assertRaisesRegex( exception.NetworkError, 'Could not add', self.interface.configure_tenant_networks, task) - client_mock.assert_called_once_with() + client_mock.assert_called_once_with(context=task.context) @mock.patch.object(neutron_common, 'get_client') def _test_configure_tenant_networks(self, client_mock, is_client_id=False, @@ -333,7 +335,7 @@ class NeutronInterfaceTestCase(db_base.DbTestCase): [{'opt_name': '61', 'opt_value': client_ids[1]}]) with task_manager.acquire(self.context, self.node.id) as task: self.interface.configure_tenant_networks(task) - client_mock.assert_called_once_with() + client_mock.assert_called_once_with(context=task.context) if vif_int_info: portid1 = self.port.internal_info['tenant_vif_port_id'] portid2 = second_port.internal_info['tenant_vif_port_id'] @@ -414,7 +416,7 @@ class NeutronInterfaceTestCase(db_base.DbTestCase): # this portgroup object. task.portgroups = [pg] self.interface.configure_tenant_networks(task) - client_mock.assert_called_once_with() + client_mock.assert_called_once_with(context=task.context) glgi_mock.assert_called_once_with(task, pg) upd_mock.assert_has_calls( [mock.call(self.port.extra['vif_port_id'], call1_body), diff --git a/releasenotes/notes/deprecated-neutron-opts-2e1d9e65f00301d3.yaml b/releasenotes/notes/deprecated-neutron-opts-2e1d9e65f00301d3.yaml new file mode 100644 index 0000000000..dd280e24ba --- /dev/null +++ b/releasenotes/notes/deprecated-neutron-opts-2e1d9e65f00301d3.yaml @@ -0,0 +1,49 @@ +--- +deprecations: + - | + Configuration option ``[neutron]/url`` is deprecated + and will be ignored in the Rocky release. + Instead, use ``[neutron]/endpoint_override`` configuration option to set + specific neutron API address when automatic discovery of neutron API + endpoint from keystone catalog is not desired. + This option has no default value, and must be set explicitly + for a stand alone deployment of ironic and neutron + (when ``[neutron]/auth_type`` is set to ``none``), since the + service catalog is not available in this case. + Otherwise it is generally recommended to rely on keystone service catalog + for service endpoint discovery. + + - | + Configuration option ``[neutron]/url_timeout`` is deprecated + and will be ignored in the Rocky release. + Instead, use ``[neutron]/timeout`` configuration option. + This new option has no default value and must be set explicitly + to ``30`` to keep previous default behavior. + + - | + Configuration option ``[neutron]/auth_strategy`` is deprecated + and will be ignored in the Rocky release. + Instead, set ``[neutron]/auth_type`` configuration option to ``none``, + and provide neutron API address as ``[neutron]/endpoint_override`` + configuration option. + +other: + - | + Signatures of several networking-related functions/methods have been + changed to include request context as an optional keyword argument. + + The functions/methods in question are: + + - ``ironic.common.neutron.get_client`` + - ``ironic.common.neutron.unbind_neutron_port`` + - ``ironic.common.neutron.update_port_address`` + - ``ironic.common.neutron.validate_network`` + - ``ironic.common.neutron.NeutronNetworkInterfaceMixin.get_cleaning_network`` + - ``ironic.common.neutron.NeutronNetworkInterfaceMixin.get_provisioning_network`` + - ``ironic.dhcp.neutron.NeutronDHCPApi.update_port_dhcp_opts`` + - ``ironic.dhcp.none.NeutronDHCPApi.update_port_dhcp_opts`` + + If you are using any of the above functions/methods in your out-of-tree + ironic driver or driver interface code, you should update the code + to pass an instance of ``ironic.common.context.RequestContext`` class + as a ``context`` keyword argument to those functions/methods. diff --git a/releasenotes/notes/keystoneauth-config-1baa45a0a2dd93b4.yaml b/releasenotes/notes/keystoneauth-config-1baa45a0a2dd93b4.yaml index 81a3f0753c..5d9ad4d4cf 100644 --- a/releasenotes/notes/keystoneauth-config-1baa45a0a2dd93b4.yaml +++ b/releasenotes/notes/keystoneauth-config-1baa45a0a2dd93b4.yaml @@ -16,3 +16,7 @@ features: Adds the ability to set keystoneauth settings in the ``[swift]`` configuration section for service automatic discovery. + - | + Adds the ability to set keystoneauth settings in the + ``[neutron]`` configuration section for service automatic + discovery.