Add support for flavors in sqlalchemy

This adds a new FlavorsController for sqlalchemy. It follows closely
what mongodb's controller does and it was forked from sqlalchemy's pool
controller.

Change-Id: I22d01fc93d030fbd8c97c481616f0f76bd620505
Closes-bug: #1489883
This commit is contained in:
Flavio Percoco 2015-08-28 16:23:00 +02:00
parent eac5a25e13
commit b7bb8bca0d
4 changed files with 170 additions and 4 deletions

View File

@ -14,6 +14,7 @@
# the License.
from zaqar.storage.sqlalchemy import catalogue
from zaqar.storage.sqlalchemy import flavors
from zaqar.storage.sqlalchemy import pools
from zaqar.storage.sqlalchemy import queues
@ -21,3 +22,4 @@ from zaqar.storage.sqlalchemy import queues
QueueController = queues.QueueController
CatalogueController = catalogue.CatalogueController
PoolsController = pools.PoolsController
FlavorsController = flavors.FlavorsController

View File

@ -92,8 +92,7 @@ class ControlDriver(storage.ControlDriverBase):
@property
def flavors_controller(self):
# NOTE(flaper87): Needed to avoid `abc` errors.
pass
return controllers.FlavorsController(self)
@property
def subscriptions_controller(self):

View File

@ -0,0 +1,155 @@
# Copyright (c) 2015 OpenStack Foundation.
#
# 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.
"""pools: an implementation of the pool management storage
controller for sqlalchemy.
Schema:
'n': name :: six.text_type
'u': uri :: six.text_type
'w': weight :: int
'o': options :: dict
"""
import sqlalchemy as sa
from zaqar.storage import base
from zaqar.storage import errors
from zaqar.storage.sqlalchemy import tables
from zaqar.storage.sqlalchemy import utils
class FlavorsController(base.FlavorsBase):
def __init__(self, *args, **kwargs):
super(FlavorsController, self).__init__(*args, **kwargs)
self._conn = self.driver.connection
self._pools_ctrl = self.driver.pools_controller
@utils.raises_conn_error
def list(self, project=None, marker=None, limit=10, detailed=False):
marker = marker or ''
# TODO(cpp-cabrera): optimization - limit the columns returned
# when detailed=False by specifying them in the select()
# clause
stmt = sa.sql.select([tables.Flavors]).where(
sa.and_(tables.Flavors.c.name > marker,
tables.Flavors.c.project == project)
)
if limit > 0:
stmt = stmt.limit(limit)
cursor = self._conn.execute(stmt)
marker_name = {}
def it():
for cur in cursor:
marker_name['next'] = cur[0]
yield _normalize(cur, detailed=detailed)
yield it()
yield marker_name and marker_name['next']
@utils.raises_conn_error
def get(self, name, project=None, detailed=False):
stmt = sa.sql.select([tables.Flavors]).where(
sa.and_(tables.Flavors.c.name == name,
tables.Flavors.c.project == project)
)
flavor = self._conn.execute(stmt).fetchone()
if flavor is None:
raise errors.FlavorDoesNotExist(name)
return _normalize(flavor, detailed)
@utils.raises_conn_error
def create(self, name, pool, project=None, capabilities=None):
cap = None if capabilities is None else utils.json_encode(capabilities)
try:
stmt = sa.sql.expression.insert(tables.Flavors).values(
name=name, pool=pool, project=project, capabilities=cap
)
self._conn.execute(stmt)
except sa.exc.IntegrityError:
if not self._pools_ctrl.get_group(pool):
raise errors.PoolDoesNotExist(pool)
# TODO(flaper87): merge update/create into a single
# method with introduction of upsert
self.update(name, pool=pool,
project=project,
capabilities=cap)
@utils.raises_conn_error
def exists(self, name, project=None):
stmt = sa.sql.select([tables.Flavors.c.name]).where(
sa.and_(tables.Flavors.c.name == name,
tables.Flavors.c.project == project)
).limit(1)
return self._conn.execute(stmt).fetchone() is not None
@utils.raises_conn_error
def update(self, name, project=None, pool=None, capabilities=None):
fields = {}
if capabilities is not None:
fields['capabilities'] = capabilities
if pool is not None:
fields['pool'] = pool
assert fields, '`pool` or `capabilities` not found in kwargs'
if 'capabilities' in fields:
fields['capabilities'] = utils.json_encode(fields['capabilities'])
stmt = sa.sql.update(tables.Flavors).where(
sa.and_(tables.Flavors.c.name == name,
tables.Flavors.c.project == project)).values(**fields)
res = self._conn.execute(stmt)
if res.rowcount == 0:
raise errors.FlavorDoesNotExist(name)
@utils.raises_conn_error
def delete(self, name, project=None):
stmt = sa.sql.expression.delete(tables.Flavors).where(
sa.and_(tables.Flavors.c.name == name,
tables.Flavors.c.project == project)
)
self._conn.execute(stmt)
@utils.raises_conn_error
def drop_all(self):
stmt = sa.sql.expression.delete(tables.Flavors)
self._conn.execute(stmt)
def _normalize(flavor, detailed=False):
ret = {
'name': flavor[0],
'project': flavor[1],
'pool': flavor[2],
}
if detailed:
capabilities = flavor[3]
ret['capabilities'] = (utils.json_decode(capabilities)
if capabilities else {})
return ret

View File

@ -30,11 +30,21 @@ Queues = sa.Table('Queues', metadata,
Pools = sa.Table('Pools', metadata,
sa.Column('name', sa.String(64), primary_key=True),
sa.Column('group', sa.String(64), nullable=True),
sa.Column('uri', sa.String(255), nullable=False),
sa.Column('group', sa.String(64),
unique=True, nullable=True),
sa.Column('uri', sa.String(255),
unique=True, nullable=False),
sa.Column('weight', sa.INTEGER, nullable=False),
sa.Column('options', sa.BINARY))
Flavors = sa.Table('Flavors', metadata,
sa.Column('name', sa.String(64), primary_key=True),
sa.Column('project', sa.String(64)),
sa.Column('pool', sa.ForeignKey('Pools.group',
ondelete='CASCADE'),
nullable=False),
sa.Column('capabilities', sa.BINARY))
Catalogue = sa.Table('Catalogue', metadata,
sa.Column('pool', sa.String(64),