Browse Source

Copy Ironic's database model codebase

The Ironic codebase is pretty simple for database access.  This work
leads into the introduction of versioned object technology from nova
and ironic that will be entering oslo:

https://review.openstack.org/#/c/127532/

This will drastically speed up the process of implementing moving the
RPC objects across the network.

Change-Id: I38aa451b658b66f5b6f10ced03ea2e0355af4ecd
changes/80/138280/8
Steven Dake 7 years ago
parent
commit
bb2546f0eb
  1. 3
      MANIFEST.in
  2. 100
      magnum/cmd/db_manage.py
  3. 0
      magnum/db/__init__.py
  4. 470
      magnum/db/api.py
  5. 56
      magnum/db/migration.py
  6. 0
      magnum/db/sqlalchemy/__init__.py
  7. 54
      magnum/db/sqlalchemy/alembic.ini
  8. 54
      magnum/db/sqlalchemy/alembic/env.py
  9. 22
      magnum/db/sqlalchemy/alembic/script.py.mako
  10. 83
      magnum/db/sqlalchemy/alembic/versions/2581ebaf0cb2_initial_migration.py
  11. 784
      magnum/db/sqlalchemy/api.py
  12. 113
      magnum/db/sqlalchemy/migration.py
  13. 161
      magnum/db/sqlalchemy/models.py
  14. 2
      magnum/objects/sqlalchemy/__init__.py
  15. 1
      setup.cfg

3
MANIFEST.in

@ -3,4 +3,7 @@ include ChangeLog
exclude .gitignore
exclude .gitreview
include magnum/db/sqlalchemy/alembic.ini
include magnum/db/sqlalchemy/alembic/script.py.mako
global-exclude *.pyc

100
magnum/cmd/db_manage.py

@ -0,0 +1,100 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Starter script for magnum-db-manage."""
import os
from oslo.config import cfg
from oslo.db import options
from oslo.db.sqlalchemy.migration_cli import manager
from magnum.openstack.common import log as logging
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
def do_version(mgr):
print('Current DB revision is %s' % mgr.version())
def do_upgrade(mgr):
mgr.upgrade(CONF.command.revision)
def do_downgrade(mgr):
mgr.downgrade(CONF.command.revision)
def do_stamp(mgr):
mgr.stamp(CONF.command.revision)
def do_revision(mgr):
mgr.revision(message=CONF.command.message,
autogenerate=CONF.command.autogenerate)
def add_command_parsers(subparsers):
parser = subparsers.add_parser('version')
parser.set_defaults(func=do_version)
parser = subparsers.add_parser('upgrade')
parser.add_argument('revision', nargs='?')
parser.set_defaults(func=do_upgrade)
parser = subparsers.add_parser('downgrade')
parser.add_argument('revision', nargs='?')
parser.set_defaults(func=do_downgrade)
parser = subparsers.add_parser('stamp')
parser.add_argument('revision', nargs='?')
parser.set_defaults(func=do_stamp)
parser = subparsers.add_parser('revision')
parser.add_argument('-m', '--message')
parser.add_argument('--autogenerate', action='store_true')
parser.set_defaults(func=do_revision)
def get_manager():
if cfg.CONF.database.connection is None:
raise ValueError(
'Database connection not set in /etc/magnum/magnum.conf')
alembic_path = os.path.abspath(
os.path.join(os.path.dirname(__file__),
'..', 'db', 'sqlalchemy', 'alembic.ini'))
migrate_path = os.path.abspath(
os.path.join(os.path.dirname(__file__),
'..', 'db', 'sqlalchemy', 'alembic'))
migration_config = {'alembic_ini_path': alembic_path,
'alembic_repo_path': migrate_path,
'db_url': CONF.database.connection}
return manager.MigrationManager(migration_config)
def main():
command_opt = cfg.SubCommandOpt('command',
title='Command',
help='Available commands',
handler=add_command_parsers)
CONF.register_cli_opt(command_opt)
# set_defaults() is called to register the db options.
options.set_defaults(CONF)
print ('manager is %s' % get_manager())
CONF(project='magnum')
CONF.command.func(get_manager())

0
magnum/db/__init__.py

470
magnum/db/api.py

@ -0,0 +1,470 @@
# -*- encoding: utf-8 -*-
#
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Base classes for storage engines
"""
import abc
from oslo.config import cfg
from oslo.db import api as db_api
import six
_BACKEND_MAPPING = {'sqlalchemy': 'magnum.db.sqlalchemy.api'}
IMPL = db_api.DBAPI.from_config(cfg.CONF, backend_mapping=_BACKEND_MAPPING,
lazy=True)
def get_instance():
"""Return a DB API instance."""
return IMPL
@six.add_metaclass(abc.ABCMeta)
class Connection(object):
"""Base class for storage system connections."""
@abc.abstractmethod
def __init__(self):
"""Constructor."""
@abc.abstractmethod
def get_bay_list(self, columns=None, filters=None, limit=None,
marker=None, sort_key=None, sort_dir=None):
"""Get specific columns for matching bays.
Return a list of the specified columns for all bays that match the
specified filters.
:param columns: List of column names to return.
Defaults to 'id' column when columns == None.
:param filters: Filters to apply. Defaults to None.
:param limit: Maximum number of bays to return.
:param marker: the last item of the previous page; we return the next
result set.
:param sort_key: Attribute by which results should be sorted.
:param sort_dir: direction in which results should be sorted.
(asc, desc)
:returns: A list of tuples of the specified columns.
"""
@abc.abstractmethod
def reserve_bay(self, tag, bay_id):
"""Reserve a bay.
To prevent other ManagerServices from manipulating the given
Bay while a Task is performed, mark it reserved by this host.
:param tag: A string uniquely identifying the reservation holder.
:param bay_id: A bay id or uuid.
:returns: A Bay object.
:raises: BayNotFound if the bay is not found.
:raises: BayLocked if the bay is already reserved.
"""
@abc.abstractmethod
def release_bay(self, tag, bay_id):
"""Release the reservation on a bay.
:param tag: A string uniquely identifying the reservation holder.
:param bay_id: A bay id or uuid.
:raises: BayNotFound if the bay is not found.
:raises: BayLocked if the bay is reserved by another host.
:raises: BayNotLocked if the bay was found to not have a
reservation at all.
"""
@abc.abstractmethod
def create_bay(self, values):
"""Create a new bay.
:param values: A dict containing several items used to identify
and track the bay, and several dicts which are passed
into the Drivers when managing this bay. For example:
::
{
'uuid': utils.generate_uuid(),
'name': 'example',
'type': 'virt'
}
:returns: A bay.
"""
@abc.abstractmethod
def get_bay_by_id(self, bay_id):
"""Return a bay.
:param bay_id: The id of a bay.
:returns: A bay.
"""
@abc.abstractmethod
def get_bay_by_uuid(self, bay_uuid):
"""Return a bay.
:param bay_uuid: The uuid of a bay.
:returns: A bay.
"""
@abc.abstractmethod
def get_bay_by_instance(self, instance):
"""Return a bay.
:param instance: The instance name or uuid to search for.
:returns: A bay.
"""
@abc.abstractmethod
def destroy_bay(self, bay_id):
"""Destroy a bay and all associated interfaces.
:param bay_id: The id or uuid of a bay.
"""
@abc.abstractmethod
def update_bay(self, bay_id, values):
"""Update properties of a bay.
:param bay_id: The id or uuid of a bay.
:returns: A bay.
:raises: BayAssociated
:raises: BayNotFound
"""
@abc.abstractmethod
def get_container_list(self, columns=None, filters=None, limit=None,
marker=None, sort_key=None, sort_dir=None):
"""Get specific columns for matching containers.
Return a list of the specified columns for all containers that match
the specified filters.
:param columns: List of column names to return.
Defaults to 'id' column when columns == None.
:param filters: Filters to apply. Defaults to None.
:param limit: Maximum number of containers to return.
:param marker: the last item of the previous page; we return the next
result set.
:param sort_key: Attribute by which results should be sorted.
:param sort_dir: direction in which results should be sorted.
(asc, desc)
:returns: A list of tuples of the specified columns.
"""
@abc.abstractmethod
def reserve_container(self, tag, container_id):
"""Reserve a container.
To prevent other ManagerServices from manipulating the given
Bay while a Task is performed, mark it reserved by this host.
:param tag: A string uniquely identifying the reservation holder.
:param container_id: A container id or uuid.
:returns: A Bay object.
:raises: BayNotFound if the container is not found.
:raises: BayLocked if the container is already reserved.
"""
@abc.abstractmethod
def release_container(self, tag, container_id):
"""Release the reservation on a container.
:param tag: A string uniquely identifying the reservation holder.
:param container_id: A container id or uuid.
:raises: BayNotFound if the container is not found.
:raises: BayLocked if the container is reserved by another host.
:raises: BayNotLocked if the container was found to not have a
reservation at all.
"""
@abc.abstractmethod
def create_container(self, values):
"""Create a new container.
:param values: A dict containing several items used to identify
and track the container, and several dicts which are
passed
into the Drivers when managing this container. For
example:
::
{
'uuid': utils.generate_uuid(),
'name': 'example',
'type': 'virt'
}
:returns: A container.
"""
@abc.abstractmethod
def get_container_by_id(self, container_id):
"""Return a container.
:param container_id: The id of a container.
:returns: A container.
"""
@abc.abstractmethod
def get_container_by_uuid(self, container_uuid):
"""Return a container.
:param container_uuid: The uuid of a container.
:returns: A container.
"""
@abc.abstractmethod
def get_container_by_instance(self, instance):
"""Return a container.
:param instance: The instance name or uuid to search for.
:returns: A container.
"""
@abc.abstractmethod
def destroy_container(self, container_id):
"""Destroy a container and all associated interfaces.
:param container_id: The id or uuid of a container.
"""
@abc.abstractmethod
def update_container(self, container_id, values):
"""Update properties of a container.
:param container_id: The id or uuid of a container.
:returns: A container.
:raises: BayAssociated
:raises: BayNotFound
"""
@abc.abstractmethod
def get_pod_list(self, columns=None, filters=None, limit=None,
marker=None, sort_key=None, sort_dir=None):
"""Get specific columns for matching pods.
Return a list of the specified columns for all pods that match the
specified filters.
:param columns: List of column names to return.
Defaults to 'id' column when columns == None.
:param filters: Filters to apply. Defaults to None.
:param limit: Maximum number of pods to return.
:param marker: the last item of the previous page; we return the next
result set.
:param sort_key: Attribute by which results should be sorted.
:param sort_dir: direction in which results should be sorted.
(asc, desc)
:returns: A list of tuples of the specified columns.
"""
@abc.abstractmethod
def reserve_pod(self, tag, pod_id):
"""Reserve a pod.
To prevent other ManagerServices from manipulating the given
Bay while a Task is performed, mark it reserved by this host.
:param tag: A string uniquely identifying the reservation holder.
:param pod_id: A pod id or uuid.
:returns: A Bay object.
:raises: BayNotFound if the pod is not found.
:raises: BayLocked if the pod is already reserved.
"""
@abc.abstractmethod
def release_pod(self, tag, pod_id):
"""Release the reservation on a pod.
:param tag: A string uniquely identifying the reservation holder.
:param pod_id: A pod id or uuid.
:raises: BayNotFound if the pod is not found.
:raises: BayLocked if the pod is reserved by another host.
:raises: BayNotLocked if the pod was found to not have a
reservation at all.
"""
@abc.abstractmethod
def create_pod(self, values):
"""Create a new pod.
:param values: A dict containing several items used to identify
and track the pod, and several dicts which are passed
into the Drivers when managing this pod. For example:
::
{
'uuid': utils.generate_uuid(),
'name': 'example',
'type': 'virt'
}
:returns: A pod.
"""
@abc.abstractmethod
def get_pod_by_id(self, pod_id):
"""Return a pod.
:param pod_id: The id of a pod.
:returns: A pod.
"""
@abc.abstractmethod
def get_pod_by_uuid(self, pod_uuid):
"""Return a pod.
:param pod_uuid: The uuid of a pod.
:returns: A pod.
"""
@abc.abstractmethod
def get_pod_by_instance(self, instance):
"""Return a pod.
:param instance: The instance name or uuid to search for.
:returns: A pod.
"""
@abc.abstractmethod
def destroy_pod(self, pod_id):
"""Destroy a pod and all associated interfaces.
:param pod_id: The id or uuid of a pod.
"""
@abc.abstractmethod
def update_pod(self, pod_id, values):
"""Update properties of a pod.
:param pod_id: The id or uuid of a pod.
:returns: A pod.
:raises: BayAssociated
:raises: BayNotFound
"""
@abc.abstractmethod
def get_service_list(self, columns=None, filters=None, limit=None,
marker=None, sort_key=None, sort_dir=None):
"""Get specific columns for matching services.
Return a list of the specified columns for all services that match the
specified filters.
:param columns: List of column names to return.
Defaults to 'id' column when columns == None.
:param filters: Filters to apply. Defaults to None.
:param limit: Maximum number of services to return.
:param marker: the last item of the previous page; we return the next
result set.
:param sort_key: Attribute by which results should be sorted.
:param sort_dir: direction in which results should be sorted.
(asc, desc)
:returns: A list of tuples of the specified columns.
"""
@abc.abstractmethod
def reserve_service(self, tag, service_id):
"""Reserve a service.
To prevent other ManagerServices from manipulating the given
Bay while a Task is performed, mark it reserved by this host.
:param tag: A string uniquely identifying the reservation holder.
:param service_id: A service id or uuid.
:returns: A Bay object.
:raises: BayNotFound if the service is not found.
:raises: BayLocked if the service is already reserved.
"""
@abc.abstractmethod
def release_service(self, tag, service_id):
"""Release the reservation on a service.
:param tag: A string uniquely identifying the reservation holder.
:param service_id: A service id or uuid.
:raises: BayNotFound if the service is not found.
:raises: BayLocked if the service is reserved by another host.
:raises: BayNotLocked if the service was found to not have a
reservation at all.
"""
@abc.abstractmethod
def create_service(self, values):
"""Create a new service.
:param values: A dict containing several items used to identify
and track the service, and several dicts which are
passed into the Drivers when managing this service.
For example:
::
{
'uuid': utils.generate_uuid(),
'name': 'example',
'type': 'virt'
}
:returns: A service.
"""
@abc.abstractmethod
def get_service_by_id(self, service_id):
"""Return a service.
:param service_id: The id of a service.
:returns: A service.
"""
@abc.abstractmethod
def get_service_by_uuid(self, service_uuid):
"""Return a service.
:param service_uuid: The uuid of a service.
:returns: A service.
"""
@abc.abstractmethod
def get_service_by_instance(self, instance):
"""Return a service.
:param instance: The instance name or uuid to search for.
:returns: A service.
"""
@abc.abstractmethod
def destroy_service(self, service_id):
"""Destroy a service and all associated interfaces.
:param service_id: The id or uuid of a service.
"""
@abc.abstractmethod
def update_service(self, service_id, values):
"""Update properties of a service.
:param service_id: The id or uuid of a service.
:returns: A service.
:raises: BayAssociated
:raises: BayNotFound
"""

56
magnum/db/migration.py

@ -0,0 +1,56 @@
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Database setup and migration commands."""
from oslo.config import cfg
from stevedore import driver
_IMPL = None
def get_backend():
global _IMPL
if not _IMPL:
cfg.CONF.import_opt('backend', 'oslo.db.options', group='database')
_IMPL = driver.DriverManager("magnum.database.migration_backend",
cfg.CONF.database.backend).driver
return _IMPL
def upgrade(version=None):
"""Migrate the database to `version` or the most recent version."""
return get_backend().upgrade(version)
def downgrade(version=None):
return get_backend().downgrade(version)
def version():
return get_backend().version()
def stamp(version):
return get_backend().stamp(version)
def revision(message, autogenerate):
return get_backend().revision(message, autogenerate)
def create_schema():
return get_backend().create_schema()

0
magnum/db/sqlalchemy/__init__.py

54
magnum/db/sqlalchemy/alembic.ini

@ -0,0 +1,54 @@
# A generic, single database configuration.
[alembic]
# path to migration scripts
script_location = %(here)s/alembic
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# max length of characters to apply to the
# "slug" field
#truncate_slug_length = 40
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
#sqlalchemy.url = driver://user:pass@localhost/dbname
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

54
magnum/db/sqlalchemy/alembic/env.py

@ -0,0 +1,54 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from logging import config as log_config
from alembic import context
from magnum.db.sqlalchemy import api as sqla_api
from magnum.db.sqlalchemy import models
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
log_config.fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
target_metadata = models.Base.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
engine = sqla_api.get_engine()
with engine.connect() as connection:
context.configure(connection=connection,
target_metadata=target_metadata)
with context.begin_transaction():
context.run_migrations()
run_migrations_online()

22
magnum/db/sqlalchemy/alembic/script.py.mako

@ -0,0 +1,22 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision}
Create Date: ${create_date}
"""
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}

83
magnum/db/sqlalchemy/alembic/versions/2581ebaf0cb2_initial_migration.py

@ -0,0 +1,83 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""initial migration
Revision ID: 2581ebaf0cb2
Revises: None
Create Date: 2014-01-17 12:14:07.754448
"""
# revision identifiers, used by Alembic.
revision = '2581ebaf0cb2'
down_revision = None
from alembic import op
import sqlalchemy as sa
def upgrade():
# commands auto generated by Alembic - please adjust!
op.create_table(
'bay',
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('uuid', sa.String(length=36), nullable=True),
sa.Column('name', sa.String(length=255), nullable=True),
sa.Column('type', sa.String(length=20), nullable=True),
sa.PrimaryKeyConstraint('id'),
mysql_ENGINE='InnoDB',
mysql_DEFAULT_CHARSET='UTF8'
)
op.create_table(
'container',
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.Column('id', sa.Integer(), nullable=False),
sa.PrimaryKeyConstraint('id'),
mysql_ENGINE='InnoDB',
mysql_DEFAULT_CHARSET='UTF8'
)
op.create_table(
'pod',
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('uuid', sa.String(length=36), nullable=True),
sa.PrimaryKeyConstraint('id'),
mysql_ENGINE='InnoDB',
mysql_DEFAULT_CHARSET='UTF8'
)
op.create_table(
'service',
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('uuid', sa.String(length=36), nullable=True),
sa.PrimaryKeyConstraint('id'),
mysql_ENGINE='InnoDB',
mysql_DEFAULT_CHARSET='UTF8'
)
# end Alembic commands
def downgrade():
op.drop_table('bay')
op.drop_table('container')
op.drop_table('service')
op.drop_table('pod')
# We should probably remove the drops later ;-)
# raise NotImplementedError(('Downgrade from initial migration is'
# ' unsupported.'))

784
magnum/db/sqlalchemy/api.py

@ -0,0 +1,784 @@
# -*- encoding: utf-8 -*-
#
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""SQLAlchemy storage backend."""
import datetime
from oslo.config import cfg
from oslo.db import exception as db_exc
from oslo.db.sqlalchemy import session as db_session
from oslo.db.sqlalchemy import utils as db_utils
from oslo.utils import timeutils
from sqlalchemy.orm.exc import NoResultFound
from magnum.common import exception
from magnum.common import utils
from magnum.db import api
from magnum.db.sqlalchemy import models
from magnum.openstack.common._i18n import _
from magnum.openstack.common import log
CONF = cfg.CONF
LOG = log.getLogger(__name__)
_FACADE = None
def _create_facade_lazily():
global _FACADE
if _FACADE is None:
_FACADE = db_session.EngineFacade.from_config(CONF)
return _FACADE
def get_engine():
facade = _create_facade_lazily()
return facade.get_engine()
def get_session(**kwargs):
facade = _create_facade_lazily()
return facade.get_session(**kwargs)
def get_backend():
"""The backend is this module itself."""
return Connection()
def model_query(model, *args, **kwargs):
"""Query helper for simpler session usage.
:param session: if present, the session to use
"""
session = kwargs.get('session') or get_session()
query = session.query(model, *args)
return query
def add_identity_filter(query, value):
"""Adds an identity filter to a query.
Filters results by ID, if supplied value is a valid integer.
Otherwise attempts to filter results by UUID.
:param query: Initial query to add filter to.
:param value: Value for filtering results by.
:return: Modified query.
"""
if utils.is_int_like(value):
return query.filter_by(id=value)
elif utils.is_uuid_like(value):
return query.filter_by(uuid=value)
else:
raise exception.InvalidIdentity(identity=value)
def _check_port_change_forbidden(port, session):
bay_id = port['bay_id']
if bay_id is not None:
query = model_query(models.Bay, session=session)
query = query.filter_by(id=bay_id)
bay_ref = query.one()
if bay_ref['reservation'] is not None:
raise exception.BayLocked(bay=bay_ref['uuid'],
host=bay_ref['reservation'])
def _paginate_query(model, limit=None, marker=None, sort_key=None,
sort_dir=None, query=None):
if not query:
query = model_query(model)
sort_keys = ['id']
if sort_key and sort_key not in sort_keys:
sort_keys.insert(0, sort_key)
query = db_utils.paginate_query(query, model, limit, sort_keys,
marker=marker, sort_dir=sort_dir)
return query.all()
class Connection(api.Connection):
"""SqlAlchemy connection."""
def __init__(self):
pass
def _add_bays_filters(self, query, filters):
if filters is None:
filters = []
if 'associated' in filters:
if filters['associated']:
query = query.filter(models.Bay.instance_uuid is not None)
else:
query = query.filter(models.Bay.instance_uuid is None)
if 'reserved' in filters:
if filters['reserved']:
query = query.filter(models.Bay.reservation is not None)
else:
query = query.filter(models.Bay.reservation is None)
if 'maintenance' in filters:
query = query.filter_by(maintenance=filters['maintenance'])
if 'driver' in filters:
query = query.filter_by(driver=filters['driver'])
if 'provision_state' in filters:
query = query.filter_by(provision_state=filters['provision_state'])
if 'provisioned_before' in filters:
limit = timeutils.utcnow() - datetime.timedelta(
seconds=filters['provisioned_before'])
query = query.filter(models.Bay.provision_updated_at < limit)
return query
def get_bayinfo_list(self, columns=None, filters=None, limit=None,
marker=None, sort_key=None, sort_dir=None):
# list-ify columns default values because it is bad form
# to include a mutable list in function definitions.
if columns is None:
columns = [models.Bay.id]
else:
columns = [getattr(models.Bay, c) for c in columns]
query = model_query(*columns, base_model=models.Bay)
query = self._add_bays_filters(query, filters)
return _paginate_query(models.Bay, limit, marker,
sort_key, sort_dir, query)
def get_bay_list(self, filters=None, limit=None, marker=None,
sort_key=None, sort_dir=None):
query = model_query(models.Bay)
query = self._add_bays_filters(query, filters)
return _paginate_query(models.Bay, limit, marker,
sort_key, sort_dir, query)
def reserve_bay(self, tag, bay_id):
session = get_session()
with session.begin():
query = model_query(models.Bay, session=session)
query = add_identity_filter(query, bay_id)
# be optimistic and assume we usually create a reservation
count = query.filter_by(reservation=None).update(
{'reservation': tag}, synchronize_session=False)
try:
bay = query.one()
if count != 1:
# Nothing updated and bay exists. Must already be
# locked.
raise exception.BayLocked(bay=bay_id,
host=bay['reservation'])
return bay
except NoResultFound:
raise exception.BayNotFound(bay_id)
def release_bay(self, tag, bay_id):
session = get_session()
with session.begin():
query = model_query(models.Bay, session=session)
query = add_identity_filter(query, bay_id)
# be optimistic and assume we usually release a reservation
count = query.filter_by(reservation=tag).update(
{'reservation': None}, synchronize_session=False)
try:
if count != 1:
bay = query.one()
if bay['reservation'] is None:
raise exception.BayNotLocked(bay=bay_id)
else:
raise exception.BayLocked(bay=bay_id,
host=bay['reservation'])
except NoResultFound:
raise exception.BayNotFound(bay_id)
def create_bay(self, values):
# ensure defaults are present for new bays
if not values.get('uuid'):
values['uuid'] = utils.generate_uuid()
bay = models.Bay()
bay.update(values)
try:
bay.save()
except db_exc.DBDuplicateEntry as exc:
if 'instance_uuid' in exc.columns:
raise exception.InstanceAssociated(
instance_uuid=values['instance_uuid'],
bay=values['uuid'])
raise exception.BayAlreadyExists(uuid=values['uuid'])
return bay
def get_bay_by_id(self, bay_id):
query = model_query(models.Bay).filter_by(id=bay_id)
try:
return query.one()
except NoResultFound:
raise exception.BayNotFound(bay=bay_id)
def get_bay_by_uuid(self, bay_uuid):
query = model_query(models.Bay).filter_by(uuid=bay_uuid)
try:
return query.one()
except NoResultFound:
raise exception.BayNotFound(bay=bay_uuid)
def get_bay_by_instance(self, instance):
if not utils.is_uuid_like(instance):
raise exception.InvalidUUID(uuid=instance)
query = (model_query(models.Bay)
.filter_by(instance_uuid=instance))
try:
result = query.one()
except NoResultFound:
raise exception.InstanceNotFound(instance=instance)
return result
def destroy_bay(self, bay_id):
session = get_session()
with session.begin():
query = model_query(models.Bay, session=session)
query = add_identity_filter(query, bay_id)
query.delete()
def update_bay(self, bay_id, values):
# NOTE(dtantsur): this can lead to very strange errors
if 'uuid' in values:
msg = _("Cannot overwrite UUID for an existing Bay.")
raise exception.InvalidParameterValue(err=msg)
try:
return self._do_update_bay(bay_id, values)
except db_exc.DBDuplicateEntry:
raise exception.InstanceAssociated(
instance_uuid=values['instance_uuid'],
bay=bay_id)
def _do_update_bay(self, bay_id, values):
session = get_session()
with session.begin():
query = model_query(models.Bay, session=session)
query = add_identity_filter(query, bay_id)
try:
ref = query.with_lockmode('update').one()
except NoResultFound:
raise exception.BayNotFound(bay=bay_id)
# Prevent instance_uuid overwriting
if values.get("instance_uuid") and ref.instance_uuid:
raise exception.BayAssociated(bay=bay_id,
instance=ref.instance_uuid)
if 'provision_state' in values:
values['provision_updated_at'] = timeutils.utcnow()
ref.update(values)
return ref
def _add_containers_filters(self, query, filters):
if filters is None:
filters = []
if 'associated' in filters:
if filters['associated']:
query = query.filter(models.Container.instance_uuid is not
None)
else:
query = query.filter(models.Container.instance_uuid is None)
if 'reserved' in filters:
if filters['reserved']:
query = query.filter(models.Container.reservation is not None)
else:
query = query.filter(models.Container.reservation is None)
if 'maintenance' in filters:
query = query.filter_by(maintenance=filters['maintenance'])
if 'driver' in filters:
query = query.filter_by(driver=filters['driver'])
if 'provision_state' in filters:
query = query.filter_by(provision_state=filters['provision_state'])
if 'provisioned_before' in filters:
limit = timeutils.utcnow() - datetime.timedelta(
seconds=filters['provisioned_before'])
query = query.filter(models.Container.provision_updated_at < limit)
return query
def get_containerinfo_list(self, columns=None, filters=None, limit=None,
marker=None, sort_key=None, sort_dir=None):
# list-ify columns default values because it is bad form
# to include a mutable list in function definitions.
if columns is None:
columns = [models.Container.id]
else:
columns = [getattr(models.Container, c) for c in columns]
query = model_query(*columns, base_model=models.Container)
query = self._add_containers_filters(query, filters)
return _paginate_query(models.Container, limit, marker,
sort_key, sort_dir, query)
def get_container_list(self, filters=None, limit=None, marker=None,
sort_key=None, sort_dir=None):
query = model_query(models.Container)
query = self._add_containers_filters(query, filters)
return _paginate_query(models.Container, limit, marker,
sort_key, sort_dir, query)
def reserve_container(self, tag, container_id):
session = get_session()
with session.begin():
query = model_query(models.Container, session=session)
query = add_identity_filter(query, container_id)
# be optimistic and assume we usually create a reservation
count = query.filter_by(reservation=None).update(
{'reservation': tag}, synchronize_session=False)
try:
container = query.one()
if count != 1:
# Nothing updated and container exists. Must already be
# locked.
raise exception.ContainerLocked(container=container_id,
host=container['reservation'])
return container
except NoResultFound:
raise exception.ContainerNotFound(container_id)
def release_container(self, tag, container_id):
session = get_session()
with session.begin():
query = model_query(models.Container, session=session)
query = add_identity_filter(query, container_id)
# be optimistic and assume we usually release a reservation
count = query.filter_by(reservation=tag).update(
{'reservation': None}, synchronize_session=False)
try:
if count != 1:
container = query.one()
if container['reservation'] is None:
raise exception.ContainerNotLocked(
container=container_id)
else:
raise exception.ContainerLocked(container=container_id,
host=container['reservation'])
except NoResultFound:
raise exception.ContainerNotFound(container_id)
def create_container(self, values):
# ensure defaults are present for new containers
if not values.get('uuid'):
values['uuid'] = utils.generate_uuid()
container = models.Container()
container.update(values)
try:
container.save()
except db_exc.DBDuplicateEntry as exc:
if 'instance_uuid' in exc.columns:
raise exception.InstanceAssociated(
instance_uuid=values['instance_uuid'],
container=values['uuid'])
raise exception.ContainerAlreadyExists(uuid=values['uuid'])
return container
def get_container_by_id(self, container_id):
query = model_query(models.Container).filter_by(id=container_id)
try:
return query.one()
except NoResultFound:
raise exception.ContainerNotFound(container=container_id)
def get_container_by_uuid(self, container_uuid):
query = model_query(models.Container).filter_by(uuid=container_uuid)
try:
return query.one()
except NoResultFound:
raise exception.ContainerNotFound(container=container_uuid)
def get_container_by_instance(self, instance):
if not utils.is_uuid_like(instance):
raise exception.InvalidUUID(uuid=instance)
query = (model_query(models.Container)
.filter_by(instance_uuid=instance))
try:
result = query.one()
except NoResultFound:
raise exception.InstanceNotFound(instance=instance)
return result
def destroy_container(self, container_id):
session = get_session()
with session.begin():
query = model_query(models.Container, session=session)
query = add_identity_filter(query, container_id)
query.delete()
def update_container(self, container_id, values):
# NOTE(dtantsur): this can lead to very strange errors
if 'uuid' in values:
msg = _("Cannot overwrite UUID for an existing Container.")
raise exception.InvalidParameterValue(err=msg)
try:
return self._do_update_container(container_id, values)
except db_exc.DBDuplicateEntry:
raise exception.InstanceAssociated(
instance_uuid=values['instance_uuid'],
container=container_id)
def _do_update_container(self, container_id, values):
session = get_session()
with session.begin():
query = model_query(models.Container, session=session)
query = add_identity_filter(query, container_id)
try:
ref = query.with_lockmode('update').one()
except NoResultFound:
raise exception.ContainerNotFound(container=container_id)
# Prevent instance_uuid overwriting
if values.get("instance_uuid") and ref.instance_uuid:
raise exception.ContainerAssociated(container=container_id,
instance=ref.instance_uuid)
if 'provision_state' in values:
values['provision_updated_at'] = timeutils.utcnow()
ref.update(values)
return ref
def get_podinfo_list(self, columns=None, filters=None, limit=None,
marker=None, sort_key=None, sort_dir=None):
# list-ify columns default values because it is bad form
# to include a mutable list in function definitions.
if columns is None:
columns = [models.Pod.id]
else:
columns = [getattr(models.Pod, c) for c in columns]
query = model_query(*columns, base_model=models.Pod)
query = self._add_pods_filters(query, filters)
return _paginate_query(models.Pod, limit, marker,
sort_key, sort_dir, query)
def get_pod_list(self, filters=None, limit=None, marker=None,
sort_key=None, sort_dir=None):
query = model_query(models.Pod)
query = self._add_pods_filters(query, filters)
return _paginate_query(models.Pod, limit, marker,
sort_key, sort_dir, query)
def reserve_pod(self, tag, pod_id):
session = get_session()
with session.begin():
query = model_query(models.Pod, session=session)
query = add_identity_filter(query, pod_id)
# be optimistic and assume we usually create a reservation
count = query.filter_by(reservation=None).update(
{'reservation': tag}, synchronize_session=False)
try:
pod = query.one()
if count != 1:
# Nothing updated and pod exists. Must already be
# locked.
raise exception.PodLocked(pod=pod_id,
host=pod['reservation'])
return pod
except NoResultFound:
raise exception.PodNotFound(pod_id)
def release_pod(self, tag, pod_id):
session = get_session()
with session.begin():
query = model_query(models.Pod, session=session)
query = add_identity_filter(query, pod_id)
# be optimistic and assume we usually release a reservation
count = query.filter_by(reservation=tag).update(
{'reservation': None}, synchronize_session=False)
try:
if count != 1:
pod = query.one()
if pod['reservation'] is None:
raise exception.PodNotLocked(pod=pod_id)
else:
raise exception.PodLocked(pod=pod_id,
host=pod['reservation'])
except NoResultFound:
raise exception.PodNotFound(pod_id)
def create_pod(self, values):
# ensure defaults are present for new pods
if not values.get('uuid'):
values['uuid'] = utils.generate_uuid()
pod = models.Pod()
pod.update(values)
try:
pod.save()
except db_exc.DBDuplicateEntry as exc:
if 'instance_uuid' in exc.columns:
raise exception.InstanceAssociated(
instance_uuid=values['instance_uuid'],
pod=values['uuid'])
raise exception.PodAlreadyExists(uuid=values['uuid'])
return pod
def get_pod_by_id(self, pod_id):
query = model_query(models.Pod).filter_by(id=pod_id)
try:
return query.one()
except NoResultFound:
raise exception.PodNotFound(pod=pod_id)
def get_pod_by_uuid(self, pod_uuid):
query = model_query(models.Pod).filter_by(uuid=pod_uuid)
try:
return query.one()
except NoResultFound:
raise exception.PodNotFound(pod=pod_uuid)
def get_pod_by_instance(self, instance):
if not utils.is_uuid_like(instance):
raise exception.InvalidUUID(uuid=instance)
query = (model_query(models.Pod)
.filter_by(instance_uuid=instance))
try:
result = query.one()
except NoResultFound:
raise exception.InstanceNotFound(instance=instance)
return result
def destroy_pod(self, pod_id):
session = get_session()
with session.begin():
query = model_query(models.Pod, session=session)
query = add_identity_filter(query, pod_id)
query.delete()
def update_pod(self, pod_id, values):
# NOTE(dtantsur): this can lead to very strange errors
if 'uuid' in values:
msg = _("Cannot overwrite UUID for an existing Pod.")
raise exception.InvalidParameterValue(err=msg)
try:
return self._do_update_pod(pod_id, values)
except db_exc.DBDuplicateEntry:
raise exception.InstanceAssociated(
instance_uuid=values['instance_uuid'],
pod=pod_id)
def _do_update_pod(self, pod_id, values):
session = get_session()
with session.begin():
query = model_query(models.Pod, session=session)
query = add_identity_filter(query, pod_id)
try:
ref = query.with_lockmode('update').one()
except NoResultFound:
raise exception.PodNotFound(pod=pod_id)
# Prevent instance_uuid overwriting
if values.get("instance_uuid") and ref.instance_uuid:
raise exception.PodAssociated(pod=pod_id,
instance=ref.instance_uuid)
if 'provision_state' in values:
values['provision_updated_at'] = timeutils.utcnow()
ref.update(values)
return ref
def _add_services_filters(self, query, filters):
if filters is None:
filters = []
if 'associated' in filters:
if filters['associated']:
query = query.filter(models.Service.instance_uuid is not None)
else:
query = query.filter(models.Service.instance_uuid is None)
if 'reserved' in filters:
if filters['reserved']:
query = query.filter(models.Service.reservation is not None)
else:
query = query.filter(models.Service.reservation is None)
if 'maintenance' in filters:
query = query.filter_by(maintenance=filters['maintenance'])
if 'driver' in filters:
query = query.filter_by(driver=filters['driver'])
if 'provision_state' in filters:
query = query.filter_by(provision_state=filters['provision_state'])
if 'provisioned_before' in filters:
limit = timeutils.utcnow() - datetime.timedelta(
seconds=filters['provisioned_before'])
query = query.filter(models.Service.provision_updated_at < limit)
return query
def get_serviceinfo_list(self, columns=None, filters=None, limit=None,
marker=None, sort_key=None, sort_dir=None):
# list-ify columns default values because it is bad form
# to include a mutable list in function definitions.
if columns is None:
columns = [models.Service.id]
else:
columns = [getattr(models.Service, c) for c in columns]
query = model_query(*columns, base_model=models.Service)
query = self._add_services_filters(query, filters)
return _paginate_query(models.Service, limit, marker,
sort_key, sort_dir, query)
def get_service_list(self, filters=None, limit=None, marker=None,
sort_key=None, sort_dir=None):
query = model_query(models.Service)
query = self._add_services_filters(query, filters)
return _paginate_query(models.Service, limit, marker,
sort_key, sort_dir, query)
def reserve_service(self, tag, service_id):
session = get_session()
with session.begin():
query = model_query(models.Service, session=session)
query = add_identity_filter(query, service_id)
# be optimistic and assume we usually create a reservation
count = query.filter_by(reservation=None).update(
{'reservation': tag}, synchronize_session=False)
try:
service = query.one()
if count != 1:
# Nothing updated and service exists. Must already be
# locked.
raise exception.ServiceLocked(service=service_id,
host=service['reservation'])
return service
except NoResultFound:
raise exception.ServiceNotFound(service_id)
def release_service(self, tag, service_id):
session = get_session()
with session.begin():
query = model_query(models.Service, session=session)
query = add_identity_filter(query, service_id)
# be optimistic and assume we usually release a reservation
count = query.filter_by(reservation=tag).update(
{'reservation': None}, synchronize_session=False)
try:
if count != 1:
service = query.one()
if service['reservation'] is None:
raise exception.ServiceNotLocked(service=service_id)
else:
raise exception.ServiceLocked(service=service_id,
host=service['reservation'])
except NoResultFound:
raise exception.ServiceNotFound(service_id)
def create_service(self, values):
# ensure defaults are present for new services
if not values.get('uuid'):
values['uuid'] = utils.generate_uuid()
service = models.Service()
service.update(values)
try:
service.save()
except db_exc.DBDuplicateEntry as exc:
if 'instance_uuid' in exc.columns: