Oslo Database Library
oslo.db is an OpenStack project library that provides database connectivity and common utilities for interacting with various SQL database backends (e.g., PostgreSQL, MySQL, SQLite). It simplifies database session management, model definition, and migration processes for OpenStack components. The library is actively maintained, with version 18.0.0 being the latest stable release, and follows the OpenStack release cadence.
Common errors
-
oslo_db.exception.DBConnectionError: (pymysql.err.OperationalError) (2006, "MySQL server has gone away (BrokenPipeError(32, 'Broken pipe'))")
cause The database connection was idle for too long and was closed by the MySQL server, or a network issue occurred.fixAdjust the `[database]pool_recycle` option in your `oslo.db` configuration to a value (in seconds) less than the database's `wait_timeout` setting. For example, `pool_recycle = 3600` if `wait_timeout` is 7200 seconds. Also, ensure `all_tcp = true` in relevant service configurations if using MySQL/Designate. -
ImportError: cannot import name 'enginefacade' from 'oslo_db'
cause Attempting to import `enginefacade` directly from the top-level `oslo_db` package, or an incorrect `oslo.db` installation.fixThe correct import path for `enginefacade` is `from oslo_db.sqlalchemy import enginefacade`. Ensure `oslo.db` and its dependencies are correctly installed. -
oslo_db.exception.DBDeadlock: DB exceeded retry limit.
cause Concurrent database operations led to a deadlock, and oslo.db's retry mechanism was exhausted. This is common in high-concurrency environments, especially during resource-intensive operations like instance creation/deletion in OpenStack services.fixReview application logic for potential deadlock scenarios. Consider optimizing database queries, implementing finer-grained locking, or adjusting transaction isolation levels. In OpenStack, this often indicates a need to scale database resources or optimize service configurations.
Warnings
- gotcha oslo.db tightly integrates with SQLAlchemy, and direct manipulation of SQLAlchemy session/engine objects outside of oslo.db's `enginefacade` can lead to unexpected behavior or resource leaks. Always use the provided context managers or decorators.
- breaking The `[DEFAULT]/sql_connection` and `[DEFAULT]/db_backend` configuration options have been deprecated in favor of `[database]/connection` and `[database]/backend` respectively. Using the old options may result in unapplied configurations or future errors.
- gotcha When using `psycopg2` (PostgreSQL) or `PyMySQL` (MySQL) as backends, system-level development packages (e.g., `libpq-dev` for PostgreSQL, `libmysqlclient-dev` for MySQL) are often required before `pip install` can succeed for the Python packages.
- gotcha Handling database connection loss (`DBConnectionError`, 'MySQL server has gone away') requires careful configuration of connection pooling, including `pool_recycle` and `wait_timeout` settings, to ensure idle connections are properly reaped and re-established.
Install
-
pip install oslo.db -
pip install PyMySQL -
sudo apt-get install libpq-dev && pip install psycopg2
Imports
- enginefacade
from oslo_db.sqlalchemy import enginefacade
- models
from oslo_db.sqlalchemy import models
- db_api
from oslo_db.api import DBAPI
from oslo_db import api as db_api
Quickstart
import os
from sqlalchemy import Column, Integer, String
from oslo_db.sqlalchemy import enginefacade, models
# Configure a simple SQLite database for demonstration
os.environ['OSLO_DB_CONNECTION'] = 'sqlite:///./test.sqlite'
# Initialize the enginefacade
enginefacade.configure(
sqlite_synchronous=False # For better performance in SQLite, though less safe
)
# Define a base model for our application
class MyModelBase(models.ModelBase, models.TimestampMixin):
__abstract__ = True
class User(MyModelBase):
__tablename__ = 'users'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(255), nullable=False)
email = Column(String(255), unique=True, nullable=False)
def __repr__(self):
return f"<User(id='{self.id}', name='{self.name}', email='{self.email}')>"
# Create tables (usually done via migration tools like Alembic in production)
# For quickstart, we'll create directly if not exists
with enginefacade.transaction() as session:
MyModelBase.metadata.create_all(session.bind)
# Example usage: adding and querying data
class MyContext:
pass
@enginefacade.transaction_context_manager
def add_user(context, name, email):
new_user = User(name=name, email=email)
context.session.add(new_user)
print(f"Added user: {new_user}")
return new_user
@enginefacade.reader_context_manager
def get_users(context):
users = context.session.query(User).all()
print("All users:")
for user in users:
print(f"- {user}")
return users
if __name__ == '__main__':
# Use a dummy context object, as oslo_db often expects one
ctx = MyContext()
add_user(ctx, "Alice", "alice@example.com")
add_user(ctx, "Bob", "bob@example.com")
get_users(ctx)
# Clean up test.sqlite if it exists
if os.path.exists('./test.sqlite'):
os.remove('./test.sqlite')
print("Cleaned up test.sqlite")