Provide compatibility for people passing raw sessions

Code used to pass raw sessions to Resource methods. A common way to do
that was to do thins like FloatingIP.get(conn.session, name_or_id). As
conn.session is a session.Session that doesn't work anymore.

To help ease upgrade path issues, attach a reference to the Connection
into conn.session and so that we can pull the right adapter for a given
resource back out.

Add the neutron-grenade job to verify this works.

Change-Id: Ief9a0215ea2399b91d1d03a8048e73e6d7bedd64
This commit is contained in:
Monty Taylor 2018-01-29 10:37:54 -06:00
parent 26e14550fe
commit 163f502345
No known key found for this signature in database
GPG Key ID: 7BAE94BC7141A594
7 changed files with 46 additions and 7 deletions

@ -219,6 +219,7 @@
- openstacksdk-functional-devstack-python3 - openstacksdk-functional-devstack-python3
- osc-functional-devstack-tips: - osc-functional-devstack-tips:
voting: false voting: false
- neutron-grenade
gate: gate:
jobs: jobs:
- build-openstack-sphinx-docs: - build-openstack-sphinx-docs:
@ -226,3 +227,4 @@
sphinx_python: python3 sphinx_python: python3
- openstacksdk-functional-devstack - openstacksdk-functional-devstack
- openstacksdk-functional-devstack-python3 - openstacksdk-functional-devstack-python3
- neutron-grenade

@ -214,6 +214,10 @@ class Connection(object):
self.config._keystone_session = session self.config._keystone_session = session
self.session = self.config.get_session() self.session = self.config.get_session()
# Hide a reference to the connection on the session to help with
# backwards compatibility for folks trying to just pass conn.session
# to a Resource method's session argument.
self.session._sdk_connection = self
service_type_manager = os_service_types.ServiceTypes() service_type_manager = os_service_types.ServiceTypes()
for service in service_type_manager.services: for service in service_type_manager.services:
@ -253,7 +257,7 @@ class Connection(object):
service_name=self.config.get_service_name(service_type), service_name=self.config.get_service_name(service_type),
interface=self.config.get_interface(service_type), interface=self.config.get_interface(service_type),
region_name=self.config.region_name, region_name=self.config.region_name,
version=self.config.get_api_version(service_type) version=self.config.get_api_version(service_type),
) )
# Register the proxy class with every known alias # Register the proxy class with every known alias

@ -34,6 +34,7 @@ and then returned to the caller.
import collections import collections
import itertools import itertools
from keystoneauth1 import adapter
from requests import structures from requests import structures
from openstack import exceptions from openstack import exceptions
@ -649,6 +650,29 @@ class Resource(object):
self._header.attributes.update(headers) self._header.attributes.update(headers)
self._header.clean() self._header.clean()
@classmethod
def _get_session(cls, session):
"""Attempt to get an Adapter from a raw session.
Some older code used conn.session has the session argument to Resource
methods. That does not work anymore, as Resource methods expect an
Adapter not a session. We've hidden an _sdk_connection on the Session
stored on the connection. If we get something that isn't an Adapter,
pull the connection from the Session and look up the adapter by
service_type.
"""
# TODO(mordred) We'll need to do this for every method in every
# Resource class that is calling session.$something to be complete.
if isinstance(session, adapter.Adapter):
return session
if hasattr(session, '_sdk_connection'):
service_type = cls.service['service_type']
return getattr(session._sdk_connection, service_type)
raise ValueError(
"The session argument to Resource methods requires either an"
" instance of an openstack.proxy.Proxy object or at the very least"
" a raw keystoneauth1.adapter.Adapter.")
def create(self, session, prepend_key=True): def create(self, session, prepend_key=True):
"""Create a remote resource based on this instance. """Create a remote resource based on this instance.
@ -665,6 +689,7 @@ class Resource(object):
if not self.allow_create: if not self.allow_create:
raise exceptions.MethodNotSupported(self, "create") raise exceptions.MethodNotSupported(self, "create")
session = self._get_session(session)
if self.create_method == 'PUT': if self.create_method == 'PUT':
request = self._prepare_request(requires_id=True, request = self._prepare_request(requires_id=True,
prepend_key=prepend_key) prepend_key=prepend_key)
@ -697,6 +722,7 @@ class Resource(object):
raise exceptions.MethodNotSupported(self, "get") raise exceptions.MethodNotSupported(self, "get")
request = self._prepare_request(requires_id=requires_id) request = self._prepare_request(requires_id=requires_id)
session = self._get_session(session)
response = session.get(request.url) response = session.get(request.url)
kwargs = {} kwargs = {}
if error_message: if error_message:
@ -720,6 +746,7 @@ class Resource(object):
request = self._prepare_request() request = self._prepare_request()
session = self._get_session(session)
response = session.head(request.url, response = session.head(request.url,
headers={"Accept": ""}) headers={"Accept": ""})
@ -750,6 +777,7 @@ class Resource(object):
raise exceptions.MethodNotSupported(self, "update") raise exceptions.MethodNotSupported(self, "update")
request = self._prepare_request(prepend_key=prepend_key) request = self._prepare_request(prepend_key=prepend_key)
session = self._get_session(session)
if self.update_method == 'PATCH': if self.update_method == 'PATCH':
response = session.patch( response = session.patch(
@ -781,6 +809,7 @@ class Resource(object):
raise exceptions.MethodNotSupported(self, "delete") raise exceptions.MethodNotSupported(self, "delete")
request = self._prepare_request() request = self._prepare_request()
session = self._get_session(session)
response = session.delete(request.url, response = session.delete(request.url,
headers={"Accept": ""}) headers={"Accept": ""})
@ -824,6 +853,7 @@ class Resource(object):
""" """
if not cls.allow_list: if not cls.allow_list:
raise exceptions.MethodNotSupported(cls, "list") raise exceptions.MethodNotSupported(cls, "list")
session = cls._get_session(session)
expected_params = utils.get_string_format_keys(cls.base_path) expected_params = utils.get_string_format_keys(cls.base_path)
expected_params += cls._query_mapping._mapping.keys() expected_params += cls._query_mapping._mapping.keys()

@ -12,6 +12,7 @@
import copy import copy
from keystoneauth1 import adapter
import mock import mock
import testtools import testtools
@ -146,7 +147,7 @@ class TestLimits(testtools.TestCase):
self.assertFalse(sot.allow_list) self.assertFalse(sot.allow_list)
def test_get(self): def test_get(self):
sess = mock.Mock() sess = mock.Mock(spec=adapter.Adapter)
resp = mock.Mock() resp = mock.Mock()
sess.get.return_value = resp sess.get.return_value = resp
resp.json.return_value = copy.deepcopy(LIMITS_BODY) resp.json.return_value = copy.deepcopy(LIMITS_BODY)

@ -13,6 +13,7 @@
import json import json
import operator import operator
from keystoneauth1 import adapter
import mock import mock
import requests import requests
import testtools import testtools
@ -100,7 +101,7 @@ class TestImage(testtools.TestCase):
self.resp = mock.Mock() self.resp = mock.Mock()
self.resp.body = None self.resp.body = None
self.resp.json = mock.Mock(return_value=self.resp.body) self.resp.json = mock.Mock(return_value=self.resp.body)
self.sess = mock.Mock() self.sess = mock.Mock(spec=adapter.Adapter)
self.sess.post = mock.Mock(return_value=self.resp) self.sess.post = mock.Mock(return_value=self.resp)
def test_basic(self): def test_basic(self):

@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from keystoneauth1 import adapter
import mock import mock
import testtools import testtools
@ -67,7 +68,7 @@ class TestFloatingIP(testtools.TestCase):
self.assertEqual(EXAMPLE['subnet_id'], sot.subnet_id) self.assertEqual(EXAMPLE['subnet_id'], sot.subnet_id)
def test_find_available(self): def test_find_available(self):
mock_session = mock.Mock() mock_session = mock.Mock(spec=adapter.Adapter)
mock_session.get_filter = mock.Mock(return_value={}) mock_session.get_filter = mock.Mock(return_value={})
data = {'id': 'one', 'floating_ip_address': '10.0.0.1'} data = {'id': 'one', 'floating_ip_address': '10.0.0.1'}
fake_response = mock.Mock() fake_response = mock.Mock()
@ -85,7 +86,7 @@ class TestFloatingIP(testtools.TestCase):
params={'port_id': ''}) params={'port_id': ''})
def test_find_available_nada(self): def test_find_available_nada(self):
mock_session = mock.Mock() mock_session = mock.Mock(spec=adapter.Adapter)
fake_response = mock.Mock() fake_response = mock.Mock()
body = {floating_ip.FloatingIP.resources_key: []} body = {floating_ip.FloatingIP.resources_key: []}
fake_response.json = mock.Mock(return_value=body) fake_response.json = mock.Mock(return_value=body)

@ -12,7 +12,7 @@
import itertools import itertools
from keystoneauth1 import session from keystoneauth1 import adapter
import mock import mock
import requests import requests
import six import six
@ -944,7 +944,7 @@ class TestResourceActions(base.TestCase):
self.sot._prepare_request = mock.Mock(return_value=self.request) self.sot._prepare_request = mock.Mock(return_value=self.request)
self.sot._translate_response = mock.Mock() self.sot._translate_response = mock.Mock()
self.session = mock.Mock(spec=session.Session) self.session = mock.Mock(spec=adapter.Adapter)
self.session.create = mock.Mock(return_value=self.response) self.session.create = mock.Mock(return_value=self.response)
self.session.get = mock.Mock(return_value=self.response) self.session.get = mock.Mock(return_value=self.response)
self.session.put = mock.Mock(return_value=self.response) self.session.put = mock.Mock(return_value=self.response)