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.

Cherry-picked from change Ief9a0215ea2399b91d1d03a8048e73e6d7bedd64

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

View File

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

View File

@ -214,6 +214,10 @@ class Connection(object):
self.config._keystone_session = 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()
for service in service_type_manager.services:
@ -253,7 +257,7 @@ class Connection(object):
service_name=self.config.get_service_name(service_type),
interface=self.config.get_interface(service_type),
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

View File

@ -34,6 +34,7 @@ and then returned to the caller.
import collections
import itertools
from keystoneauth1 import adapter
from requests import structures
from openstack import exceptions
@ -649,6 +650,29 @@ class Resource(object):
self._header.attributes.update(headers)
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):
"""Create a remote resource based on this instance.
@ -665,6 +689,7 @@ class Resource(object):
if not self.allow_create:
raise exceptions.MethodNotSupported(self, "create")
session = self._get_session(session)
if self.create_method == 'PUT':
request = self._prepare_request(requires_id=True,
prepend_key=prepend_key)
@ -697,6 +722,7 @@ class Resource(object):
raise exceptions.MethodNotSupported(self, "get")
request = self._prepare_request(requires_id=requires_id)
session = self._get_session(session)
response = session.get(request.url)
kwargs = {}
if error_message:
@ -720,6 +746,7 @@ class Resource(object):
request = self._prepare_request()
session = self._get_session(session)
response = session.head(request.url,
headers={"Accept": ""})
@ -750,6 +777,7 @@ class Resource(object):
raise exceptions.MethodNotSupported(self, "update")
request = self._prepare_request(prepend_key=prepend_key)
session = self._get_session(session)
if self.update_method == 'PATCH':
response = session.patch(
@ -781,6 +809,7 @@ class Resource(object):
raise exceptions.MethodNotSupported(self, "delete")
request = self._prepare_request()
session = self._get_session(session)
response = session.delete(request.url,
headers={"Accept": ""})
@ -824,6 +853,7 @@ class Resource(object):
"""
if not cls.allow_list:
raise exceptions.MethodNotSupported(cls, "list")
session = cls._get_session(session)
expected_params = utils.get_string_format_keys(cls.base_path)
expected_params += cls._query_mapping._mapping.keys()

View File

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

View File

@ -13,6 +13,7 @@
import json
import operator
from keystoneauth1 import adapter
import mock
import requests
import testtools
@ -100,7 +101,7 @@ class TestImage(testtools.TestCase):
self.resp = mock.Mock()
self.resp.body = None
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)
def test_basic(self):

View File

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

View File

@ -12,7 +12,7 @@
import itertools
from keystoneauth1 import session
from keystoneauth1 import adapter
import mock
import requests
import six
@ -944,7 +944,7 @@ class TestResourceActions(base.TestCase):
self.sot._prepare_request = mock.Mock(return_value=self.request)
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.get = mock.Mock(return_value=self.response)
self.session.put = mock.Mock(return_value=self.response)