151 lines
5.0 KiB
Python
151 lines
5.0 KiB
Python
# -*- encoding: utf-8 -*-
|
|
#
|
|
# Copyright © 2014 eNovance
|
|
#
|
|
# Authors: Julien Danjou <julien@danjou.info>
|
|
#
|
|
# 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 __future__ import absolute_import
|
|
import operator
|
|
import uuid
|
|
|
|
from oslo.config import cfg
|
|
import sqlalchemy
|
|
from sqlalchemy.dialects import postgresql
|
|
from sqlalchemy.ext import declarative
|
|
from sqlalchemy import types
|
|
|
|
from gnocchi import indexer
|
|
from gnocchi.openstack.common.db import exception
|
|
from gnocchi.openstack.common.db.sqlalchemy import models
|
|
from gnocchi.openstack.common.db.sqlalchemy import session
|
|
|
|
|
|
cfg.CONF.import_opt('connection', 'gnocchi.openstack.common.db.options',
|
|
group='database')
|
|
|
|
|
|
Base = declarative.declarative_base()
|
|
|
|
|
|
class GUID(types.TypeDecorator):
|
|
"""Platform-independent GUID type.
|
|
|
|
Uses Postgresql's UUID type, otherwise uses
|
|
CHAR(32), storing as stringified hex values.
|
|
|
|
"""
|
|
impl = types.CHAR
|
|
|
|
def load_dialect_impl(self, dialect):
|
|
if dialect.name == 'postgresql':
|
|
return dialect.type_descriptor(postgresql.UUID())
|
|
return dialect.type_descriptor(types.CHAR(32))
|
|
|
|
def process_bind_param(self, value, dialect):
|
|
if value is None:
|
|
return value
|
|
elif dialect.name == 'postgresql':
|
|
return str(value)
|
|
if not isinstance(value, uuid.UUID):
|
|
return "%.32x" % uuid.UUID(value)
|
|
# hexstring
|
|
return "%.32x" % value
|
|
|
|
def process_result_value(self, value, dialect):
|
|
if value is None:
|
|
return value
|
|
return uuid.UUID(value)
|
|
|
|
|
|
ResourceEntity = sqlalchemy.Table(
|
|
'resource_entity',
|
|
Base.metadata,
|
|
sqlalchemy.Column('resource', GUID,
|
|
sqlalchemy.ForeignKey('resource.id',
|
|
ondelete="CASCADE")),
|
|
sqlalchemy.Column('entity', types.Text,
|
|
sqlalchemy.ForeignKey('entity.name',
|
|
ondelete="CASCADE"))
|
|
)
|
|
|
|
|
|
class Entity(Base, models.ModelBase):
|
|
__tablename__ = 'entity'
|
|
|
|
name = sqlalchemy.Column(types.Text, primary_key=True)
|
|
|
|
|
|
class Resource(Base, models.ModelBase):
|
|
__tablename__ = 'resource'
|
|
|
|
id = sqlalchemy.Column(GUID, primary_key=True)
|
|
entities = sqlalchemy.orm.relationship(
|
|
'Entity',
|
|
backref='resources',
|
|
secondary=ResourceEntity)
|
|
|
|
|
|
class SQLAlchemyIndexer(indexer.IndexerDriver):
|
|
def __init__(self, conf):
|
|
self.engine_facade = session.EngineFacade.from_config(
|
|
conf.database.connection, conf)
|
|
|
|
def upgrade(self):
|
|
engine = self.engine_facade.get_engine()
|
|
Base.metadata.create_all(engine)
|
|
|
|
def create_resource(self, uuid, entities=[]):
|
|
session = self.engine_facade.get_session()
|
|
with session.begin():
|
|
# FIXME(jd) Seriously, THERE IS NOT NEED TO DO THAT. But someone
|
|
# sucks, either me or the ORM. Please fix that so there's no
|
|
# need to select before inserting FFS. What needs to be done is
|
|
# an INSERT in resources and then an INSERT into ResourceEntity;
|
|
# that last one should fails if the entity does not exist, so we
|
|
# just have to raise back to the caller! I offer a pack of beer
|
|
# to whoever fix that.
|
|
loaded_entities = []
|
|
for e in entities:
|
|
entity = session.query(Entity).filter(Entity.name == e).first()
|
|
if not entity:
|
|
raise indexer.NoSuchEntity(e)
|
|
loaded_entities.append(entity)
|
|
r = Resource(id=uuid, entities=loaded_entities)
|
|
session.add(r)
|
|
return {"id": r['id'],
|
|
'entities': map(operator.attrgetter('name'),
|
|
r['entities'])}
|
|
|
|
def get_resource(self, uuid):
|
|
session = self.engine_facade.get_session()
|
|
with session.begin():
|
|
q = session.query(Resource).filter(Resource.id == uuid)
|
|
r = q.first()
|
|
return {"id": r['id'],
|
|
'entities': map(operator.attrgetter('name'),
|
|
r['entities'])}
|
|
|
|
def create_entity(self, name):
|
|
session = self.engine_facade.get_session()
|
|
try:
|
|
with session.begin():
|
|
session.add(Entity(name=name))
|
|
except exception.DBDuplicateEntry:
|
|
raise indexer.EntityAlreadyExists(name)
|
|
|
|
def delete_entity(self, name):
|
|
session = self.engine_facade.get_session()
|
|
with session.begin():
|
|
session.query(Entity).filter(Entity.name == name).delete()
|