Files
session_redis_public/attachment_swift/models/ir_attachment.py
T
Yannick Payot d17d229b13 Change CI to GitHub actions
Use copier template from oca/oca-addons-repo-template

Apply linting
2023-05-24 18:22:55 +02:00

177 lines
6.2 KiB
Python

# Copyright 2017-2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
import logging
import os
from odoo import _, api, exceptions, models
from ..swift_uri import SwiftUri
_logger = logging.getLogger(__name__)
try:
import keystoneauth1
import keystoneauth1.identity
import keystoneauth1.session
import swiftclient
from swiftclient.exceptions import ClientException
except ImportError:
swiftclient = None
ClientException = None
_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, project_name):
return (auth_url, username, password, project_name)
def get_session(
self, auth_url=None, username=None, password=None, project_name=None
):
key = self._get_key(auth_url, username, password, project_name)
session = self._sessions.get(key)
if not session:
auth = keystoneauth1.identity.v3.Password(
username=username,
password=password,
project_name=project_name,
auth_url=auth_url,
project_domain_id="default",
user_domain_id="default",
)
session = keystoneauth1.session.Session(
auth=auth,
timeout=SWIFT_TIMEOUT,
)
self._sessions[key] = session
return session
swift_session_store = SwiftSessionStore()
class IrAttachment(models.Model):
_inherit = "ir.attachment"
def _get_stores(self):
return ["swift"] + super()._get_stores()
@api.model
def _get_swift_connection(self):
"""Returns a connection object for the Swift object store"""
host = os.environ.get("SWIFT_AUTH_URL")
account = os.environ.get("SWIFT_ACCOUNT")
password = os.environ.get("SWIFT_PASSWORD")
project_name = os.environ.get("SWIFT_PROJECT_NAME")
if not project_name and os.environ.get("SWIFT_TENANT_NAME"):
project_name = os.environ["SWIFT_TENANT_NAME"]
_logger.warning(
"SWIFT_TENANT_NAME is deprecated and "
"must be replaced by SWIFT_PROJECT_NAME"
)
region = os.environ.get("SWIFT_REGION_NAME")
os_options = {}
if region:
os_options["region_name"] = region
if not (host and account and password and project_name):
raise exceptions.UserError(
_(
"Problem connecting to Swift store, are the env variables "
"(SWIFT_AUTH_URL, SWIFT_ACCOUNT, SWIFT_PASSWORD, "
"SWIFT_TENANT_NAME) properly set?"
)
)
try:
session = swift_session_store.get_session(
username=account,
password=password,
project_name=project_name,
auth_url=host,
)
conn = swiftclient.client.Connection(
session=session,
os_options=os_options,
)
except ClientException:
_logger.exception("Error connecting to Swift object store")
raise exceptions.UserError(_("Error on Swift connection")) from None
return conn
@api.model
def _store_file_read(self, fname):
if fname.startswith("swift://"):
swifturi = SwiftUri(fname)
try:
conn = self._get_swift_connection()
except exceptions.UserError:
_logger.exception(
"error reading attachment '%s' from object storage", fname
)
return ""
try:
resp, read = conn.get_object(swifturi.container(), swifturi.item())
except ClientException:
read = ""
_logger.exception("Error reading object from Swift object store")
return read
else:
return super()._store_file_read(fname)
def _store_file_write(self, key, bin_data):
if self._storage() == "swift":
container = os.environ.get("SWIFT_WRITE_CONTAINER")
conn = self._get_swift_connection()
conn.put_container(container)
filename = "swift://{}/{}".format(container, key)
try:
conn.put_object(container, key, bin_data)
except ClientException:
_logger.exception("Error writing to Swift object store")
raise exceptions.UserError(_("Error writing to Swift")) from None
else:
_super = super()
filename = _super._store_file_write(key, bin_data)
return filename
@api.model
def _store_file_delete(self, fname):
if fname.startswith("swift://"):
swifturi = SwiftUri(fname)
container = swifturi.container()
# delete the file only if it is on the current configured bucket
# otherwise, we might delete files used on a different environment
if container == os.environ.get("SWIFT_WRITE_CONTAINER"):
conn = self._get_swift_connection()
try:
conn.delete_object(container, swifturi.item())
except ClientException:
_logger.exception(_("Error deleting an object on the Swift store"))
# we ignore the error, file will stay on the object
# storage but won't disrupt the process
else:
return super()._file_delete_from_store(fname)