Merge pull request #61 from guewen/10.0-swift-auth-session

[10.0] attachment_swift: share a session for all connections
This commit is contained in:
Guewen Baconnier
2019-05-21 14:52:13 +02:00
committed by GitHub
co-authored by GitHub
7 changed files with 122 additions and 18 deletions
-1
View File
@@ -13,7 +13,6 @@ addons:
- python-lxml # because pip installation is slow - python-lxml # because pip installation is slow
- python-simplejson - python-simplejson
- python-serial - python-serial
- python-yaml
python: python:
- '2.7' - '2.7'
+2
View File
@@ -16,6 +16,7 @@ Configure accesses with environment variables:
* ``SWIFT_TENANT_NAME`` * ``SWIFT_TENANT_NAME``
* ``SWIFT_ACCOUNT`` * ``SWIFT_ACCOUNT``
* ``SWIFT_PASSWORD`` * ``SWIFT_PASSWORD``
* ``SWIFT_REGION_NAME`` : optional region
* ``SWIFT_WRITE_CONTAINER`` : Name of the container to use in the store (created if not existing) * ``SWIFT_WRITE_CONTAINER`` : Name of the container to use in the store (created if not existing)
Read-only mode: Read-only mode:
@@ -44,6 +45,7 @@ The python-swiftclient can be used from the command line, useful to test:
export OS_USERNAME={SWIFT_ACCOUNT} export OS_USERNAME={SWIFT_ACCOUNT}
export OS_PASSWORD={SWIFT_PASSWORD} export OS_PASSWORD={SWIFT_PASSWORD}
export OS_TENANT_NAME={SWIFT_TENANT_NAME} export OS_TENANT_NAME={SWIFT_TENANT_NAME}
export SWIFT_REGION_NAME={SWIFT_REGION_NAME}
export OS_AUTH_URL=https://auth.cloud.ovh.net/v2.0 export OS_AUTH_URL=https://auth.cloud.ovh.net/v2.0
swift stat swift stat
+1
View File
@@ -13,6 +13,7 @@
'external_dependencies': { 'external_dependencies': {
'python': ['swiftclient', 'python': ['swiftclient',
'keystoneclient', 'keystoneclient',
'keystoneauth1',
], ],
}, },
'website': 'https://www.camptocamp.com', 'website': 'https://www.camptocamp.com',
+58 -4
View File
@@ -14,6 +14,9 @@ _logger = logging.getLogger(__name__)
try: try:
import swiftclient import swiftclient
import keystoneauth1
import keystoneauth1.identity
import keystoneauth1.session
from swiftclient.exceptions import ClientException from swiftclient.exceptions import ClientException
except ImportError: except ImportError:
swiftclient = None swiftclient = None
@@ -21,6 +24,54 @@ except ImportError:
_logger.debug("Cannot 'import swiftclient'.") _logger.debug("Cannot 'import swiftclient'.")
SWIFT_TIMEOUT = 15
class SwiftSessionStore(object):
"""Keep in memory the current Swift Auth session
The auth endpoint has a rate limit on swift, if every operation
on the filestore authenticate, the limit is exhausted and
operations rejected with an HTTP error code 429.
Swift connections can reuse the same session by asking a session
matching their connection parameters with ``get_session``.
The keystoneauth1's session automatically creates a new token
if the previous one is expired.
The best documentation I found about sessions is
https://docs.openstack.org/keystoneauth/latest/using-sessions.html
"""
def __init__(self):
self._sessions = {}
def _get_key(self, auth_url, username, password, tenant_name):
return (auth_url, username, password, tenant_name)
def get_session(self, auth_url=None, username=None, password=None,
tenant_name=None):
key = self._get_key(auth_url, username, password, tenant_name)
session = self._sessions.get(key)
if not session:
auth = keystoneauth1.identity.v2.Password(
username=username,
password=password,
tenant_name=tenant_name,
auth_url=auth_url,
)
session = keystoneauth1.session.Session(
auth=auth,
timeout=SWIFT_TIMEOUT,
)
self._sessions[key] = session
return session
swift_session_store = SwiftSessionStore()
class IrAttachment(models.Model): class IrAttachment(models.Model):
_inherit = 'ir.attachment' _inherit = 'ir.attachment'
@@ -47,11 +98,14 @@ class IrAttachment(models.Model):
"SWIFT_TENANT_NAME) properly set?" "SWIFT_TENANT_NAME) properly set?"
)) ))
try: try:
conn = swiftclient.client.Connection(authurl=host, session = swift_session_store.get_session(
user=account, username=account,
key=password, password=password,
tenant_name=tenant_name, tenant_name=tenant_name,
auth_version='2.0', auth_url=host,
)
conn = swiftclient.client.Connection(
session=session,
os_options=os_options, os_options=os_options,
) )
except ClientException: except ClientException:
+43 -6
View File
@@ -2,11 +2,16 @@
# Copyright 2017 Camptocamp SA # Copyright 2017 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
import mock
import os import os
from mock import patch from mock import patch
import keystoneauth1
from odoo.addons.base.tests.test_ir_attachment import TestIrAttachment from odoo.addons.base.tests.test_ir_attachment import TestIrAttachment
from ..swift_uri import SwiftUri from odoo.addons.attachment_swift.models.ir_attachment import SwiftSessionStore
from odoo.addons.attachment_swift.swift_uri import SwiftUri
class TestAttachmentSwift(TestIrAttachment): class TestAttachmentSwift(TestIrAttachment):
@@ -16,6 +21,34 @@ class TestAttachmentSwift(TestIrAttachment):
self.env['ir.config_parameter'].set_param('ir_attachment.location', self.env['ir.config_parameter'].set_param('ir_attachment.location',
'swift') 'swift')
def test_session_store_get_session(self):
auth_url = 'auth_url'
username = 'username'
password = 'password'
tenant_name = 'tenant_name'
store = SwiftSessionStore()
session = store.get_session(
auth_url=auth_url,
username=username,
password=password,
tenant_name=tenant_name,
)
self.assertEqual(session.auth.auth_url, auth_url)
self.assertEqual(session.auth.username, username)
self.assertEqual(session.auth.password, password)
self.assertEqual(session.auth.tenant_name, tenant_name)
# get the same session on a second call
self.assertEqual(
store.get_session(
auth_url=auth_url,
username=username,
password=password,
tenant_name=tenant_name,
),
session
)
@patch('swiftclient.client') @patch('swiftclient.client')
def test_connection(self, mock_swift_client): def test_connection(self, mock_swift_client):
""" Test the connection to the store""" """ Test the connection to the store"""
@@ -27,13 +60,17 @@ class TestAttachmentSwift(TestIrAttachment):
attachment = self.Attachment attachment = self.Attachment
attachment._get_swift_connection() attachment._get_swift_connection()
mock_swift_client.Connection.assert_called_once_with( mock_swift_client.Connection.assert_called_once_with(
authurl=os.environ.get('SWIFT_AUTH_URL'), session=mock.ANY,
user=os.environ.get('SWIFT_ACCOUNT'),
key=os.environ.get('SWIFT_PASSWORD'),
tenant_name=os.environ.get('SWIFT_TENANT_NAME'),
auth_version='2.0',
os_options={'region_name': os.environ.get('SWIFT_REGION_NAME')}, os_options={'region_name': os.environ.get('SWIFT_REGION_NAME')},
) )
__, kwargs = mock_swift_client.Connection.call_args
session = kwargs['session']
self.assertTrue(isinstance(session, keystoneauth1.session.Session))
self.assertEqual(session.auth.auth_url, os.environ['SWIFT_AUTH_URL'])
self.assertEqual(session.auth.username, os.environ['SWIFT_ACCOUNT'])
self.assertEqual(session.auth.password, os.environ['SWIFT_PASSWORD'])
self.assertEqual(session.auth.tenant_name,
os.environ['SWIFT_TENANT_NAME'])
def test_store_file_on_swift(self): def test_store_file_on_swift(self):
""" """
+5 -2
View File
@@ -2,5 +2,8 @@ boto==2.42.0
redis==2.10.5 redis==2.10.5
python-json-logger==0.1.5 python-json-logger==0.1.5
statsd==3.2.1 statsd==3.2.1
python-swiftclient==3.4.0 python-swiftclient==3.7.0
python-keystoneclient==3.13.0 python-keystoneclient==3.19.0
keystoneauth1==3.14.0
# error with 5.x (ConstructorError: could not determine a constructor for the tag '!record')
PyYAML==4.2b4
+9 -1
View File
@@ -2,5 +2,13 @@ import setuptools
setuptools.setup( setuptools.setup(
setup_requires=['setuptools-odoo'], setup_requires=['setuptools-odoo'],
odoo_addon=True, odoo_addon={
'external_dependencies_override': {
'python': {
'swiftclient': 'python-swiftclient>=3.7.0',
'keystoneclient': 'python-keystoneclient>=3.19.0',
'keystoneauth1': 'keystoneauth1>=3.14.0',
},
},
}
) )