Merge "modify events sql schema to reduce empty columns"

This commit is contained in:
Jenkins 2015-02-18 21:46:31 +00:00 committed by Gerrit Code Review
commit 3ad53cc903
6 changed files with 346 additions and 334 deletions

View File

@ -14,13 +14,11 @@
"""SQLAlchemy storage backend."""
from __future__ import absolute_import
import operator
import os
from oslo.db import exception as dbexc
from oslo.db.sqlalchemy import session as db_session
from oslo_config import cfg
import six
import sqlalchemy as sa
from ceilometer.event.storage import base
@ -28,7 +26,6 @@ from ceilometer.event.storage import models as api_models
from ceilometer.i18n import _
from ceilometer.openstack.common import log
from ceilometer.storage.sqlalchemy import models
from ceilometer.storage.sqlalchemy import utils as sql_utils
from ceilometer import utils
LOG = log.getLogger(__name__)
@ -44,6 +41,36 @@ AVAILABLE_STORAGE_CAPABILITIES = {
}
TRAIT_MAPLIST = [(api_models.Trait.NONE_TYPE, models.TraitText),
(api_models.Trait.TEXT_TYPE, models.TraitText),
(api_models.Trait.INT_TYPE, models.TraitInt),
(api_models.Trait.FLOAT_TYPE, models.TraitFloat),
(api_models.Trait.DATETIME_TYPE, models.TraitDatetime)]
TRAIT_ID_TO_MODEL = dict((x, y) for x, y in TRAIT_MAPLIST)
TRAIT_MODEL_TO_ID = dict((y, x) for x, y in TRAIT_MAPLIST)
trait_models_dict = {'string': models.TraitText,
'integer': models.TraitInt,
'datetime': models.TraitDatetime,
'float': models.TraitFloat}
def _build_trait_query(session, trait_type, key, value, op='eq'):
trait_model = trait_models_dict[trait_type]
op_dict = {'eq': (trait_model.value == value),
'lt': (trait_model.value < value),
'le': (trait_model.value <= value),
'gt': (trait_model.value > value),
'ge': (trait_model.value >= value),
'ne': (trait_model.value != value)}
conditions = [trait_model.key == key, op_dict[op]]
return (session.query(trait_model.event_id.label('ev_id'))
.filter(*conditions))
class Connection(base.Connection):
"""Put the event data into a SQLAlchemy database.
@ -61,21 +88,31 @@ class Connection(base.Connection):
generated = timestamp of event
event_type_id = event type -> eventtype.id
}
- Trait
- trait value
- TraitInt
- int trait value
- { event_id: event -> event.id
trait_type_id: trait type -> traittype.id
t_string: string value
t_float: float value
t_int: integer value
t_datetime: timestamp value
key: trait type
value: integer value
}
- TraitType
- trait definition
- { id: trait id
desc: description of trait
data_type: data type (integer that maps to datatype)
- TraitDatetime
- int trait value
- { event_id: event -> event.id
key: trait type
value: datetime value
}
- TraitText
- int trait value
- { event_id: event -> event.id
key: trait type
value: text value
}
- TraitFloat
- int trait value
- { event_id: event -> event.id
key: trait type
value: float value
}
"""
CAPABILITIES = utils.update_nested(base.Connection.CAPABILITIES,
AVAILABLE_CAPABILITIES)
@ -108,37 +145,6 @@ class Connection(base.Connection):
self._engine_facade._session_maker.close_all()
engine.dispose()
def _get_or_create_trait_type(self, trait_type, data_type, session=None):
"""Find if this trait already exists in the database.
If it does not, create a new entry in the trait type table.
"""
if session is None:
session = self._engine_facade.get_session()
with session.begin(subtransactions=True):
tt = session.query(models.TraitType).filter(
models.TraitType.desc == trait_type,
models.TraitType.data_type == data_type).first()
if not tt:
tt = models.TraitType(trait_type, data_type)
session.add(tt)
return tt
def _make_trait(self, trait_model, event, session=None):
"""Make a new Trait from a Trait model.
Doesn't flush or add to session.
"""
trait_type = self._get_or_create_trait_type(trait_model.name,
trait_model.dtype,
session)
value_map = models.Trait._value_map
values = {'t_string': None, 't_float': None,
't_int': None, 't_datetime': None}
value = trait_model.value
values[value_map[trait_model.dtype]] = value
return models.Trait(trait_type, event, **values)
def _get_or_create_event_type(self, event_type, session=None):
"""Check if an event type with the supplied name is already exists.
@ -154,27 +160,6 @@ class Connection(base.Connection):
session.add(et)
return et
def _record_event(self, session, event_model):
"""Store a single Event, including related Traits."""
with session.begin(subtransactions=True):
event_type = self._get_or_create_event_type(event_model.event_type,
session=session)
event = models.Event(event_model.message_id, event_type,
event_model.generated)
session.add(event)
new_traits = []
if event_model.traits:
for trait in event_model.traits:
t = self._make_trait(trait, event, session=session)
session.add(t)
new_traits.append(t)
# Note: we don't flush here, explicitly (unless a new trait or event
# does it). Otherwise, just wait until all the Events are staged.
return event, new_traits
def record_events(self, event_models):
"""Write the events to SQL database via sqlalchemy.
@ -188,22 +173,43 @@ class Connection(base.Connection):
TraitTypes are added along the way.
"""
session = self._engine_facade.get_session()
events = []
problem_events = []
for event_model in event_models:
event = None
try:
with session.begin():
event = self._record_event(session, event_model)
event_type = self._get_or_create_event_type(
event_model.event_type, session=session)
event = models.Event(event_model.message_id, event_type,
event_model.generated)
session.add(event)
session.flush()
if event_model.traits:
trait_map = {}
for trait in event_model.traits:
if trait_map.get(trait.dtype) is None:
trait_map[trait.dtype] = []
trait_map[trait.dtype].append(
{'event_id': event.id,
'key': trait.name,
'value': trait.value})
for dtype in trait_map.keys():
model = TRAIT_ID_TO_MODEL[dtype]
session.execute(model.__table__.insert(),
trait_map[dtype])
except dbexc.DBDuplicateEntry as e:
LOG.exception(_("Failed to record duplicated event: %s") % e)
problem_events.append((api_models.Event.DUPLICATE,
event_model))
except KeyError as e:
LOG.exception(_('Failed to record event: %s') % e)
problem_events.append((api_models.Event.INCOMPATIBLE_TRAIT,
event_model))
except Exception as e:
LOG.exception(_('Failed to record event: %s') % e)
problem_events.append((api_models.Event.UNKNOWN_PROBLEM,
event_model))
events.append(event)
return problem_events
def get_events(self, event_filter):
@ -212,10 +218,7 @@ class Connection(base.Connection):
:param event_filter: EventFilter instance
"""
start = event_filter.start_timestamp
end = event_filter.end_timestamp
session = self._engine_facade.get_session()
LOG.debug(_("Getting events that match filter: %s") % event_filter)
with session.begin():
event_query = session.query(models.Event)
@ -233,80 +236,98 @@ class Connection(base.Connection):
# Build up the where conditions
event_filter_conditions = []
if event_filter.message_id:
event_filter_conditions.append(models.Event.message_id ==
event_filter.message_id)
if start:
event_filter_conditions.append(models.Event.generated >= start)
if end:
event_filter_conditions.append(models.Event.generated <= end)
event_filter_conditions.append(
models.Event.message_id == event_filter.message_id)
if event_filter.start_timestamp:
event_filter_conditions.append(
models.Event.generated >= event_filter.start_timestamp)
if event_filter.end_timestamp:
event_filter_conditions.append(
models.Event.generated <= event_filter.end_timestamp)
if event_filter_conditions:
event_query = (event_query.
filter(sa.and_(*event_filter_conditions)))
event_models_dict = {}
trait_subq = None
# Build trait filter
if event_filter.traits_filter:
trait_qlist = []
for trait_filter in event_filter.traits_filter:
# Build a sub query that joins Trait to TraitType
# where the trait name matches
trait_name = trait_filter.pop('key')
key = trait_filter.pop('key')
op = trait_filter.pop('op', 'eq')
conditions = [models.Trait.trait_type_id ==
models.TraitType.id,
models.TraitType.desc == trait_name]
trait_qlist.append(_build_trait_query(
session, trait_filter.keys()[0], key,
trait_filter.values()[0], op))
trait_subq = trait_qlist.pop()
if trait_qlist:
trait_subq = trait_subq.intersect(*trait_qlist)
trait_subq = trait_subq.subquery()
for key, value in six.iteritems(trait_filter):
sql_utils.trait_op_condition(conditions,
key, value, op)
query = (session.query(models.Event.id)
.join(models.EventType,
sa.and_(*event_join_conditions)))
if trait_subq is not None:
query = query.join(trait_subq,
trait_subq.c.ev_id == models.Event.id)
if event_filter_conditions:
query = query.filter(sa.and_(*event_filter_conditions))
trait_query = (session.query(models.Trait.event_id).
join(models.TraitType,
sa.and_(*conditions)).subquery())
event_list = {}
# get a list of all events that match filters
for (id_, generated, message_id,
desc) in query.add_columns(
models.Event.generated, models.Event.message_id,
models.EventType.desc).order_by(
models.Event.generated).all():
event_list[id_] = api_models.Event(message_id, desc,
generated, [])
# Query all traits related to events.
# NOTE (gordc): cast is done because pgsql defaults to TEXT when
# handling unknown values such as null.
trait_q = (
query.join(
models.TraitDatetime,
models.TraitDatetime.event_id == models.Event.id)
.add_columns(
models.TraitDatetime.key, models.TraitDatetime.value,
sa.cast(sa.null(), sa.Integer),
sa.cast(sa.null(), sa.Float(53)),
sa.cast(sa.null(), sa.Text))
).union(
query.join(
models.TraitInt,
models.TraitInt.event_id == models.Event.id)
.add_columns(models.TraitInt.key, sa.null(),
models.TraitInt.value, sa.null(), sa.null()),
query.join(
models.TraitFloat,
models.TraitFloat.event_id == models.Event.id)
.add_columns(models.TraitFloat.key, sa.null(),
sa.null(), models.TraitFloat.value, sa.null()),
query.join(
models.TraitText,
models.TraitText.event_id == models.Event.id)
.add_columns(models.TraitText.key, sa.null(),
sa.null(), sa.null(), models.TraitText.value))
event_query = (event_query.
join(trait_query, models.Event.id ==
trait_query.c.event_id))
else:
# If there are no trait filters, grab the events from the db
query = (session.query(models.Event.id,
models.Event.generated,
models.Event.message_id,
models.EventType.desc).
join(models.EventType,
sa.and_(*event_join_conditions)))
if event_filter_conditions:
query = query.filter(sa.and_(*event_filter_conditions))
for (id_, generated, message_id, desc_) in query.all():
event_models_dict[id_] = api_models.Event(message_id,
desc_,
generated,
[])
for id_, key, t_date, t_int, t_float, t_text in trait_q.all():
if t_int:
dtype = api_models.Trait.INT_TYPE
val = t_int
elif t_float:
dtype = api_models.Trait.FLOAT_TYPE
val = t_float
elif t_date:
dtype = api_models.Trait.DATETIME_TYPE
val = t_date
else:
dtype = api_models.Trait.TEXT_TYPE
val = t_text
# Build event models for the events
event_query = event_query.subquery()
query = (session.query(models.Trait).
join(models.TraitType, models.Trait.trait_type_id ==
models.TraitType.id).
join(event_query, models.Trait.event_id ==
event_query.c.id))
trait_model = api_models.Trait(key, dtype, val)
event_list[id_].append_trait(trait_model)
# Now convert the sqlalchemy objects back into Models ...
for trait in query.all():
event = event_models_dict.get(trait.event_id)
if not event:
event = api_models.Event(
trait.event.message_id,
trait.event.event_type.desc,
trait.event.generated, [])
event_models_dict[trait.event_id] = event
trait_model = api_models.Trait(trait.trait_type.desc,
trait.trait_type.data_type,
trait.get_value())
event.append_trait(trait_model)
event_models = event_models_dict.values()
return sorted(event_models, key=operator.attrgetter('generated'))
return event_list.values()
def get_event_types(self):
"""Return all event types as an iterable of strings."""
@ -327,27 +348,21 @@ class Connection(base.Connection):
"""
session = self._engine_facade.get_session()
LOG.debug(_("Get traits for %s") % event_type)
with session.begin():
query = (session.query(models.TraitType.desc,
models.TraitType.data_type)
.join(models.Trait,
models.Trait.trait_type_id ==
models.TraitType.id)
.join(models.Event,
models.Event.id ==
models.Trait.event_id)
.join(models.EventType,
sa.and_(models.EventType.id ==
models.Event.id,
models.EventType.desc ==
event_type))
.group_by(models.TraitType.desc,
models.TraitType.data_type)
.distinct())
for trait_model in [models.TraitText, models.TraitInt,
models.TraitFloat, models.TraitDatetime]:
query = (session.query(trait_model.key)
.join(models.Event,
models.Event.id == trait_model.event_id)
.join(models.EventType,
sa.and_(models.EventType.id ==
models.Event.event_type_id,
models.EventType.desc == event_type))
.distinct())
for desc_, dtype in query.all():
yield {'name': desc_, 'data_type': dtype}
dtype = TRAIT_MODEL_TO_ID.get(trait_model)
for row in query.all():
yield {'name': row[0], 'data_type': dtype}
def get_traits(self, event_type, trait_type=None):
"""Return all trait instances associated with an event_type.
@ -359,22 +374,21 @@ class Connection(base.Connection):
session = self._engine_facade.get_session()
with session.begin():
trait_type_filters = [models.TraitType.id ==
models.Trait.trait_type_id]
if trait_type:
trait_type_filters.append(models.TraitType.desc == trait_type)
for trait_model in [models.TraitText, models.TraitInt,
models.TraitFloat, models.TraitDatetime]:
query = (session.query(trait_model.key, trait_model.value)
.join(models.Event,
models.Event.id == trait_model.event_id)
.join(models.EventType,
sa.and_(models.EventType.id ==
models.Event.event_type_id,
models.EventType.desc == event_type))
.order_by(trait_model.key))
if trait_type:
query = query.filter(trait_model.key == trait_type)
query = (session.query(models.Trait)
.join(models.TraitType, sa.and_(*trait_type_filters))
.join(models.Event,
models.Event.id == models.Trait.event_id)
.join(models.EventType,
sa.and_(models.EventType.id ==
models.Event.event_type_id,
models.EventType.desc == event_type)))
for trait in query.all():
type = trait.trait_type
yield api_models.Trait(name=type.desc,
dtype=type.data_type,
value=trait.get_value())
dtype = TRAIT_MODEL_TO_ID.get(trait_model)
for k, v in query.all():
yield api_models.Trait(name=k,
dtype=dtype,
value=v)

View File

@ -31,6 +31,7 @@ class Event(base.Model):
DUPLICATE = 1
UNKNOWN_PROBLEM = 2
INCOMPATIBLE_TRAIT = 3
def __init__(self, message_id, event_type, generated, traits):
"""Create a new event.

View File

@ -0,0 +1,107 @@
#
# 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.
import sqlalchemy as sa
from ceilometer.storage.sqlalchemy import models
tables = [('trait_text', sa.Text, True, 't_string', 1),
('trait_int', sa.Integer, False, 't_int', 2),
('trait_float', sa.Float, False, 't_float', 3),
('trait_datetime', models.PreciseTimestamp(),
False, 't_datetime', 4)]
def upgrade(migrate_engine):
meta = sa.MetaData(bind=migrate_engine)
trait = sa.Table('trait', meta, autoload=True)
event = sa.Table('event', meta, autoload=True)
trait_type = sa.Table('trait_type', meta, autoload=True)
for t_name, t_type, t_nullable, col_name, __ in tables:
t_table = sa.Table(
t_name, meta,
sa.Column('event_id', sa.Integer,
sa.ForeignKey(event.c.id), primary_key=True),
sa.Column('key', sa.String(255), primary_key=True),
sa.Column('value', t_type, nullable=t_nullable),
sa.Index('ix_%s_event_id_key' % t_name,
'event_id', 'key'),
mysql_engine='InnoDB',
mysql_charset='utf8',
)
t_table.create()
query = sa.select(
[trait.c.event_id,
trait_type.c.desc,
trait.c[col_name]]).select_from(
trait.join(trait_type,
trait.c.trait_type_id == trait_type.c.id)).where(
trait.c[col_name] != sa.null())
if query.alias().select().scalar() is not None:
t_table.insert().from_select(
['event_id', 'key', 'value'], query).execute()
trait.drop()
trait_type.drop()
def downgrade(migrate_engine):
meta = sa.MetaData(bind=migrate_engine)
event = sa.Table('event', meta, autoload=True)
trait_type = sa.Table(
'trait_type', meta,
sa.Column('id', sa.Integer, primary_key=True),
sa.Column('desc', sa.String(255)),
sa.Column('data_type', sa.Integer),
sa.UniqueConstraint('desc', 'data_type', name='tt_unique'),
mysql_engine='InnoDB',
mysql_charset='utf8',
)
trait_type.create()
trait = sa.Table(
'trait', meta,
sa.Column('id', sa.Integer, primary_key=True),
sa.Column('trait_type_id', sa.Integer, sa.ForeignKey(trait_type.c.id)),
sa.Column('event_id', sa.Integer, sa.ForeignKey(event.c.id)),
sa.Column('t_string', sa.String(255), nullable=True, default=None),
sa.Column('t_float', sa.Float(53), nullable=True, default=None),
sa.Column('t_int', sa.Integer, nullable=True, default=None),
sa.Column('t_datetime', models.PreciseTimestamp(), nullable=True,
default=None),
sa.Index('ix_trait_t_int', 't_int'),
sa.Index('ix_trait_t_string', 't_string'),
sa.Index('ix_trait_t_datetime', 't_datetime'),
sa.Index('ix_trait_t_float', 't_float'),
mysql_engine='InnoDB',
mysql_charset='utf8',
)
trait.create()
for t_name, __, __, col_name, type_id in tables:
table = sa.Table(t_name, meta, autoload=True)
trait_type.insert().from_select([trait_type.c.desc,
trait_type.c.data_type],
sa.select([table.c.key,
type_id])
.distinct()).execute()
trait.insert().from_select([trait.c['event_id'],
trait.c['trait_type_id'],
trait.c[col_name]],
sa.select([table.c.event_id,
trait_type.c.id,
table.c.value])
.select_from(
table.join(
trait_type,
table.c.key == trait_type.c.desc))
).execute()
table.drop()

View File

@ -24,12 +24,10 @@ from sqlalchemy import event, select
from sqlalchemy import Float, Boolean, Text, DateTime
from sqlalchemy.dialects.mysql import DECIMAL
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import backref
from sqlalchemy.orm import deferred
from sqlalchemy.orm import relationship
from sqlalchemy.types import TypeDecorator
from ceilometer.event.storage import models
from ceilometer import utils
@ -335,93 +333,49 @@ class Event(Base):
self.generated)
class TraitType(Base):
"""Types of event traits.
class TraitText(Base):
"""Event text traits."""
A trait type includes a description and a data type. Uniqueness is
enforced compositely on the data_type and desc fields. This is to
accommodate cases, such as 'generated', which, depending on the
corresponding event, could be a date, a boolean, or a float.
"""
__tablename__ = 'trait_type'
__tablename__ = 'trait_text'
__table_args__ = (
UniqueConstraint('desc', 'data_type', name='tt_unique'),
Index('ix_trait_type', 'desc')
Index('ix_trait_text_event_id_key', 'event_id', 'key'),
)
id = Column(Integer, primary_key=True)
desc = Column(String(255))
data_type = Column(Integer)
def __init__(self, desc, data_type):
self.desc = desc
self.data_type = data_type
def __repr__(self):
return "<TraitType: %s:%d>" % (self.desc, self.data_type)
event_id = Column(Integer, ForeignKey('event.id'), primary_key=True)
key = Column(Integer, primary_key=True)
value = Column(Text)
class Trait(Base):
__tablename__ = 'trait'
class TraitInt(Base):
"""Event integer traits."""
__tablename__ = 'trait_int'
__table_args__ = (
Index('ix_trait_t_int', 't_int'),
Index('ix_trait_t_string', 't_string'),
Index('ix_trait_t_datetime', 't_datetime'),
Index('ix_trait_t_float', 't_float'),
Index('ix_trait_int_event_id_key', 'event_id', 'key'),
)
id = Column(Integer, primary_key=True)
event_id = Column(Integer, ForeignKey('event.id'), primary_key=True)
key = Column(Integer, primary_key=True)
value = Column(Integer)
trait_type_id = Column(Integer, ForeignKey('trait_type.id'))
trait_type = relationship("TraitType", backref='traits')
t_string = Column(String(255), nullable=True, default=None)
t_float = Column(Float(53), nullable=True, default=None)
t_int = Column(Integer, nullable=True, default=None)
t_datetime = Column(PreciseTimestamp(), nullable=True, default=None)
class TraitFloat(Base):
"""Event float traits."""
event_id = Column(Integer, ForeignKey('event.id'))
event = relationship("Event", backref=backref('traits', order_by=id))
__tablename__ = 'trait_float'
__table_args__ = (
Index('ix_trait_float_event_id_key', 'event_id', 'key'),
)
event_id = Column(Integer, ForeignKey('event.id'), primary_key=True)
key = Column(Integer, primary_key=True)
value = Column(Float(53))
_value_map = {models.Trait.TEXT_TYPE: 't_string',
models.Trait.FLOAT_TYPE: 't_float',
models.Trait.INT_TYPE: 't_int',
models.Trait.DATETIME_TYPE: 't_datetime'}
def __init__(self, trait_type, event, t_string=None,
t_float=None, t_int=None, t_datetime=None):
self.trait_type = trait_type
self.t_string = t_string
self.t_float = t_float
self.t_int = t_int
self.t_datetime = t_datetime
self.event = event
class TraitDatetime(Base):
"""Event datetime traits."""
def get_value(self):
if self.trait_type is None:
dtype = None
else:
dtype = self.trait_type.data_type
if dtype == models.Trait.INT_TYPE:
return self.t_int
if dtype == models.Trait.FLOAT_TYPE:
return self.t_float
if dtype == models.Trait.DATETIME_TYPE:
return self.t_datetime
if dtype == models.Trait.TEXT_TYPE:
return self.t_string
return None
def __repr__(self):
name = self.trait_type.desc if self.trait_type else None
data_type = (self.trait_type.data_type if self.trait_type else
models.Trait.NONE_TYPE)
return "<Trait(%s) %d=%s/%s/%s/%s on %s>" % (name,
data_type,
self.t_string,
self.t_float,
self.t_int,
self.t_datetime,
self.event)
__tablename__ = 'trait_datetime'
__table_args__ = (
Index('ix_trait_datetime_event_id_key', 'event_id', 'key'),
)
event_id = Column(Integer, ForeignKey('event.id'), primary_key=True)
key = Column(Integer, primary_key=True)
value = Column(PreciseTimestamp())

View File

@ -120,18 +120,3 @@ class QueryTransformer(object):
def get_query(self):
return self.query
trait_models_dict = {'string': models.Trait.t_string,
'integer': models.Trait.t_int,
'datetime': models.Trait.t_datetime,
'float': models.Trait.t_float}
def trait_op_condition(conditions, trait_type, value, op='eq'):
trait_model = trait_models_dict[trait_type]
op_dict = {'eq': (trait_model == value), 'lt': (trait_model < value),
'le': (trait_model <= value), 'gt': (trait_model > value),
'ge': (trait_model >= value), 'ne': (trait_model != value)}
conditions.append(op_dict[op])
return conditions

View File

@ -43,39 +43,6 @@ class CeilometerBaseTest(tests_db.TestBase):
self.assertEqual('value', base['key'])
@tests_db.run_with('sqlite', 'mysql', 'pgsql')
class TraitTypeTest(tests_db.TestBase):
# TraitType is a construct specific to sqlalchemy.
# Not applicable to other drivers.
def test_trait_type_exists(self):
tt1 = self.event_conn._get_or_create_trait_type("foo", 0)
self.assertTrue(tt1.id >= 0)
tt2 = self.event_conn._get_or_create_trait_type("foo", 0)
self.assertEqual(tt2.id, tt1.id)
self.assertEqual(tt2.desc, tt1.desc)
self.assertEqual(tt2.data_type, tt1.data_type)
def test_new_trait_type(self):
tt1 = self.event_conn._get_or_create_trait_type("foo", 0)
self.assertTrue(tt1.id >= 0)
tt2 = self.event_conn._get_or_create_trait_type("blah", 0)
self.assertNotEqual(tt1.id, tt2.id)
self.assertNotEqual(tt1.desc, tt2.desc)
# Test the method __repr__ returns a string
self.assertTrue(repr.repr(tt2))
def test_trait_different_data_type(self):
tt1 = self.event_conn._get_or_create_trait_type("foo", 0)
self.assertTrue(tt1.id >= 0)
tt2 = self.event_conn._get_or_create_trait_type("foo", 1)
self.assertNotEqual(tt1.id, tt2.id)
self.assertEqual(tt2.desc, tt1.desc)
self.assertNotEqual(tt1.data_type, tt2.data_type)
# Test the method __repr__ returns a string
self.assertTrue(repr.repr(tt2))
@tests_db.run_with('sqlite', 'mysql', 'pgsql')
class EventTypeTest(tests_db.TestBase):
# EventType is a construct specific to sqlalchemy
@ -104,65 +71,49 @@ class MyException(Exception):
@tests_db.run_with('sqlite', 'mysql', 'pgsql')
class EventTest(tests_db.TestBase):
def _verify_data(self, trait, trait_table):
now = datetime.datetime.utcnow()
ev = models.Event('1', 'name', now, [trait])
self.event_conn.record_events([ev])
session = self.event_conn._engine_facade.get_session()
t_tables = [sql_models.TraitText, sql_models.TraitFloat,
sql_models.TraitInt, sql_models.TraitDatetime]
for table in t_tables:
if table == trait_table:
self.assertEqual(1, session.query(table).count())
else:
self.assertEqual(0, session.query(table).count())
def test_string_traits(self):
model = models.Trait("Foo", models.Trait.TEXT_TYPE, "my_text")
trait = self.event_conn._make_trait(model, None)
self.assertEqual(models.Trait.TEXT_TYPE, trait.trait_type.data_type)
self.assertIsNone(trait.t_float)
self.assertIsNone(trait.t_int)
self.assertIsNone(trait.t_datetime)
self.assertEqual("my_text", trait.t_string)
self.assertIsNotNone(trait.trait_type.desc)
self._verify_data(model, sql_models.TraitText)
def test_int_traits(self):
model = models.Trait("Foo", models.Trait.INT_TYPE, 100)
trait = self.event_conn._make_trait(model, None)
self.assertEqual(models.Trait.INT_TYPE, trait.trait_type.data_type)
self.assertIsNone(trait.t_float)
self.assertIsNone(trait.t_string)
self.assertIsNone(trait.t_datetime)
self.assertEqual(100, trait.t_int)
self.assertIsNotNone(trait.trait_type.desc)
self._verify_data(model, sql_models.TraitInt)
def test_float_traits(self):
model = models.Trait("Foo", models.Trait.FLOAT_TYPE, 123.456)
trait = self.event_conn._make_trait(model, None)
self.assertEqual(models.Trait.FLOAT_TYPE, trait.trait_type.data_type)
self.assertIsNone(trait.t_int)
self.assertIsNone(trait.t_string)
self.assertIsNone(trait.t_datetime)
self.assertEqual(123.456, trait.t_float)
self.assertIsNotNone(trait.trait_type.desc)
self._verify_data(model, sql_models.TraitFloat)
def test_datetime_traits(self):
now = datetime.datetime.utcnow()
model = models.Trait("Foo", models.Trait.DATETIME_TYPE, now)
trait = self.event_conn._make_trait(model, None)
self.assertEqual(models.Trait.DATETIME_TYPE,
trait.trait_type.data_type)
self.assertIsNone(trait.t_int)
self.assertIsNone(trait.t_string)
self.assertIsNone(trait.t_float)
self.assertEqual(now, trait.t_datetime)
self.assertIsNotNone(trait.trait_type.desc)
self._verify_data(model, sql_models.TraitDatetime)
def test_bad_event(self):
now = datetime.datetime.utcnow()
m = [models.Event("1", "Foo", now, []),
models.Event("2", "Zoo", now, [])]
with mock.patch.object(self.event_conn, "_record_event") as mock_save:
with mock.patch.object(self.event_conn,
"_get_or_create_event_type") as mock_save:
mock_save.side_effect = MyException("Boom")
problem_events = self.event_conn.record_events(m)
self.assertEqual(2, len(problem_events))
for bad, event in problem_events:
self.assertEqual(bad, models.Event.UNKNOWN_PROBLEM)
def test_get_none_value_traits(self):
model = sql_models.Trait(None, None, 5)
self.assertIsNone(model.get_value())
self.assertTrue(repr.repr(model))
def test_event_repr(self):
ev = sql_models.Event('msg_id', None, False)
ev.id = 100