ec2-api/ec2api/db/sqlalchemy/api.py
Andrey Pavlov 2cc7ec92dc fix races
1) fix bug with filtering
filters were applied before unpaired items is added to result

2) fix races in default security group creation

3) add waiter for associate/disassociate address

4) fix security group classic test
it should choose default group group only from classic groups

5) fix describe_vpc_with_filters test
it can run in parallel with other test with same CIDR

6) fix networks list for instance run at subnet creation/deletion

7) fix selective decsribe by names
it should not delete valid items from db

Change-Id: Iadadefb8b4abebbb3b8efc0e536e5de30ed23dab
2015-12-15 09:22:50 +03:00

334 lines
10 KiB
Python

# Copyright 2013 Cloudscaling Group, Inc
#
# 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.
"""Implementation of SQLAlchemy backend."""
import copy
import functools
import json
import random
import sys
from oslo_config import cfg
from oslo_db import exception as db_exception
from oslo_db.sqlalchemy import session as db_session
from sqlalchemy import and_
from sqlalchemy import or_
from sqlalchemy.sql import bindparam
import ec2api.context
from ec2api.db.sqlalchemy import models
from ec2api import exception
CONF = cfg.CONF
_MASTER_FACADE = None
def _create_facade_lazily():
global _MASTER_FACADE
if _MASTER_FACADE is None:
_MASTER_FACADE = db_session.EngineFacade.from_config(CONF)
return _MASTER_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 sys.modules[__name__]
def require_context(f):
"""Decorator to require *any* user or admin context.
The first argument to the wrapped function must be the context.
"""
@functools.wraps(f)
def wrapper(*args, **kwargs):
ec2api.context.require_context(args[0])
return f(*args, **kwargs)
return wrapper
def model_query(context, model, *args, **kwargs):
"""Query helper that accounts for context's `read_deleted` field.
:param context: context to query under
:param session: if present, the session to use
"""
session = kwargs.get('session') or get_session()
return session.query(model, *args)
def _new_id(kind):
obj_id = "%(kind)s-%(id)08x" % {"kind": kind,
"id": random.randint(1, 0xffffffff)}
return obj_id
@require_context
def add_item(context, kind, data):
item_ref = models.Item()
item_ref.update({
"project_id": context.project_id,
"id": _new_id(kind),
})
item_ref.update(_pack_item_data(data))
try:
item_ref.save()
except db_exception.DBDuplicateEntry as ex:
if (models.ITEMS_OS_ID_INDEX_NAME not in ex.columns and
'os_id' not in ex.columns):
raise
item_ref = (model_query(context, models.Item).
filter_by(os_id=data["os_id"]).
filter(or_(models.Item.project_id == context.project_id,
models.Item.project_id.is_(None))).
filter(models.Item.id.like('%s-%%' % kind)).
one())
item_data = _unpack_item_data(item_ref)
item_data.update(data)
item_ref.update(_pack_item_data(item_data))
item_ref.project_id = context.project_id
item_ref.save()
return _unpack_item_data(item_ref)
@require_context
def add_item_id(context, kind, os_id, project_id=None):
item_ref = models.Item()
item_ref.update({
"id": _new_id(kind),
"os_id": os_id,
})
if project_id:
item_ref.project_id = project_id
try:
item_ref.save()
except db_exception.DBDuplicateEntry as ex:
if (models.ITEMS_OS_ID_INDEX_NAME not in ex.columns and
ex.columns != ['os_id']):
raise
item_ref = (model_query(context, models.Item).
filter_by(os_id=os_id).
one())
return item_ref.id
@require_context
def update_item(context, item):
item_ref = (model_query(context, models.Item).
filter_by(project_id=context.project_id,
id=item['id']).
one())
if item_ref.os_id and item_ref.os_id != item['os_id']:
raise exception.EC2DBInvalidOsIdUpdate(item_id=item['id'],
old_os_id=item_ref.os_id,
new_os_id=item['os_id'])
item_ref.update(_pack_item_data(item))
item_ref.save()
return _unpack_item_data(item_ref)
@require_context
def delete_item(context, item_id):
session = get_session()
deleted_count = (model_query(context, models.Item, session=session).
filter_by(project_id=context.project_id,
id=item_id).
delete(synchronize_session=False))
if not deleted_count:
return
try:
(model_query(context, models.Tag, session=session).
filter_by(project_id=context.project_id,
item_id=item_id).
delete(synchronize_session=False))
except Exception:
# NOTE(ft): ignore all exceptions because DB integrity is insignificant
# for tags
pass
@require_context
def restore_item(context, kind, data):
try:
item_ref = models.Item()
item_ref.update({
"project_id": context.project_id,
})
item_ref.id = data['id']
item_ref.update(_pack_item_data(data))
item_ref.save()
return _unpack_item_data(item_ref)
except db_exception.DBDuplicateEntry:
raise exception.EC2DBDuplicateEntry(id=data['id'])
@require_context
def get_items(context, kind):
return [_unpack_item_data(item)
for item in (model_query(context, models.Item).
filter_by(project_id=context.project_id).
filter(models.Item.id.like('%s-%%' % kind)).
all())]
@require_context
def get_item_by_id(context, item_id):
return (_unpack_item_data(model_query(context, models.Item).
filter_by(project_id=context.project_id,
id=item_id).
first()))
@require_context
def get_items_by_ids(context, item_ids):
if not item_ids:
return []
return [_unpack_item_data(item)
for item in (model_query(context, models.Item).
filter_by(project_id=context.project_id).
filter(models.Item.id.in_(item_ids)).
all())]
@require_context
def get_public_items(context, kind, item_ids=None):
query = (model_query(context, models.Item).
filter(models.Item.id.like('%s-%%' % kind)).
filter(models.Item.data.like('%"is_public": True%')))
if item_ids:
query = query.filter(models.Item.id.in_(item_ids))
return [_unpack_item_data(item)
for item in query.all()]
@require_context
def get_items_ids(context, kind, item_ids=None, item_os_ids=None):
query = (model_query(context, models.Item).
filter(models.Item.id.like('%s-%%' % kind)))
if item_ids:
query = query.filter(models.Item.id.in_(item_ids))
if item_os_ids:
query = query.filter(models.Item.os_id.in_(item_os_ids))
return [(item['id'], item['os_id'])
for item in query.all()]
@require_context
def add_tags(context, tags):
session = get_session()
get_query = (model_query(context, models.Tag, session=session).
filter_by(project_id=context.project_id,
# NOTE(ft): item_id param name is reserved for
# sqlalchemy internal use
item_id=bindparam('tag_item_id'),
key=bindparam('tag_key')))
with session.begin():
for tag in tags:
tag_ref = models.Tag(project_id=context.project_id,
item_id=tag['item_id'],
key=tag['key'],
value=tag['value'])
try:
with session.begin(nested=True):
tag_ref.save(session)
except db_exception.DBDuplicateEntry as ex:
if ('PRIMARY' not in ex.columns and
ex.columns != ['project_id', 'item_id', 'key']):
raise
(get_query.params(tag_item_id=tag['item_id'],
tag_key=tag['key']).
update({'value': tag['value']}))
@require_context
def delete_tags(context, item_ids, tag_pairs=None):
if not item_ids:
return
query = (model_query(context, models.Tag).
filter_by(project_id=context.project_id).
filter(models.Tag.item_id.in_(item_ids)))
if tag_pairs:
tag_fltr = None
for tag_pair in tag_pairs:
pair_fltr = None
for col in ('key', 'value'):
if col in tag_pair:
expr = getattr(models.Tag, col) == tag_pair[col]
pair_fltr = (expr if pair_fltr is None else
and_(pair_fltr, expr))
if pair_fltr is not None:
tag_fltr = (pair_fltr if tag_fltr is None else
or_(tag_fltr, pair_fltr))
if tag_fltr is not None:
query = query.filter(tag_fltr)
query.delete(synchronize_session=False)
@require_context
def get_tags(context, kinds=None, item_ids=None):
query = (model_query(context, models.Tag).
filter_by(project_id=context.project_id))
if kinds:
fltr = None
for kind in kinds:
expr = models.Tag.item_id.like('%s-%%' % kind)
fltr = expr if fltr is None else or_(fltr, expr)
query = query.filter(fltr)
if item_ids:
query = query.filter(models.Tag.item_id.in_(item_ids))
return [dict(item_id=tag.item_id,
key=tag.key,
value=tag.value)
for tag in query.all()]
def _pack_item_data(item_data):
data = copy.deepcopy(item_data)
data.pop("id", None)
return {
"os_id": data.pop("os_id", None),
"vpc_id": data.pop("vpc_id", None),
"data": json.dumps(data),
}
def _unpack_item_data(item_ref):
if item_ref is None:
return None
data = item_ref.data
data = json.loads(data) if data is not None else {}
data["id"] = item_ref.id
data["os_id"] = item_ref.os_id
data["vpc_id"] = item_ref.vpc_id
return data