Support for state tracing of modules and components

- DB migration scripts.
- Models: ComponentBuildTrace, ModuleBuildTrace.
- ModuleBuild.state_trace method for querying for a particular module's
state history.
- SQLAlchemy before commit session event handler for recording
module/component state changes.
- REST API verbose mode for getting state trace of a particular module.
- Tests use make_session, so that event handlers are in effect.
- Short info in README about verbose mode.
- Tests verifying whether state trace information about a module appears
in verbose mode.
- Other minor fixes (RidaBase -> MBSBase, PEP8...)
This commit is contained in:
Filip Valder
2017-02-15 14:46:10 +01:00
parent 7dfb647e08
commit 13b7bcd03e
9 changed files with 256 additions and 43 deletions

View File

@@ -29,7 +29,7 @@
import contextlib
from datetime import datetime
from sqlalchemy import engine_from_config, or_
from sqlalchemy import engine_from_config, event
from sqlalchemy.orm import validates, scoped_session, sessionmaker
import modulemd as _modulemd
@@ -75,6 +75,7 @@ def make_session(conf):
'sqlalchemy.url': conf.sqlalchemy_database_uri,
})
session = scoped_session(sessionmaker(bind=engine))()
event.listen(session, "before_commit", session_before_commit_handlers)
try:
yield session
session.commit()
@@ -86,12 +87,12 @@ def make_session(conf):
session.close()
class RidaBase(db.Model):
class MBSBase(db.Model):
# TODO -- we can implement functionality here common to all our model classes
__abstract__ = True
class ModuleBuild(RidaBase):
class ModuleBuild(MBSBase):
__tablename__ = "module_builds"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
@@ -195,6 +196,12 @@ class ModuleBuild(RidaBase):
if state_reason:
self.state_reason = state_reason
# record module's state change
mbt = ModuleBuildTrace(state_time=now,
state=self.state,
state_reason=state_reason)
self.module_builds_trace.append(mbt)
log.debug("%r, state %r->%r" % (self, old_state, self.state))
if old_state != self.state:
module_build_service.messaging.publish(
@@ -217,8 +224,8 @@ class ModuleBuild(RidaBase):
"""
tag = event.repo_tag.strip('-build')
query = session.query(cls)\
.filter(cls.koji_tag==tag)\
.filter(cls.state==BUILD_STATES["build"])
.filter(cls.koji_tag == tag)\
.filter(cls.state == BUILD_STATES["build"])
count = query.count()
if count > 1:
@@ -241,9 +248,16 @@ class ModuleBuild(RidaBase):
'time_submitted': self.time_submitted,
'time_modified': self.time_modified,
'time_completed': self.time_completed,
"tasks": self.tasks(),
# TODO, show their entire .json() ?
'component_builds': [build.id for build in self.component_builds],
'modulemd': self.modulemd,
'state_trace': [{'time': record.state_time,
'state': record.state,
'state_name': INVERSE_BUILD_STATES[record.state],
'reason': record.state_reason}
for record
in self.state_trace(self.id)]
}
@staticmethod
@@ -285,13 +299,43 @@ class ModuleBuild(RidaBase):
return tasks
def state_trace(self, module_id):
return ModuleBuildTrace.query.filter_by(
module_id=module_id).order_by(ModuleBuildTrace.state_time).all()
def __repr__(self):
return "<ModuleBuild %s, stream=%s, version=%s, state %r, batch %r, state_reason %r>" % (
self.name, self.stream, self.version,
INVERSE_BUILD_STATES[self.state], self.batch, self.state_reason)
class ComponentBuild(RidaBase):
class ModuleBuildTrace(MBSBase):
__tablename__ = "module_builds_trace"
id = db.Column(db.Integer, primary_key=True)
module_id = db.Column(db.Integer, db.ForeignKey('module_builds.id'), nullable=False)
state_time = db.Column(db.DateTime, nullable=False)
state = db.Column(db.Integer, nullable=True)
state_reason = db.Column(db.String, nullable=True)
module_build = db.relationship('ModuleBuild', backref='module_builds_trace', lazy=False)
def json(self):
retval = {
'id': self.id,
'module_id': self.module_id,
'state_time': self.state_time,
'state': self.state,
'state_reason': self.state_reason,
}
return retval
def __repr__(self):
return "<ModuleBuildTrace %s, module_id: %s, state_time: %r, state: %s, state_reason: %s>" % (
self.id, self.module_id, self.state_time, self.state, self.state_reason)
class ComponentBuild(MBSBase):
__tablename__ = "component_builds"
id = db.Column(db.Integer, primary_key=True)
package = db.Column(db.String, nullable=False)
@@ -361,3 +405,53 @@ class ComponentBuild(RidaBase):
def __repr__(self):
return "<ComponentBuild %s, %r, state: %r, task_id: %r, batch: %r, state_reason: %s>" % (
self.package, self.module_id, self.state, self.task_id, self.batch, self.state_reason)
class ComponentBuildTrace(MBSBase):
__tablename__ = "component_builds_trace"
id = db.Column(db.Integer, primary_key=True)
component_id = db.Column(db.Integer, db.ForeignKey('component_builds.id'), nullable=False)
state_time = db.Column(db.DateTime, nullable=False)
state = db.Column(db.Integer, nullable=True)
state_reason = db.Column(db.String, nullable=True)
task_id = db.Column(db.Integer, nullable=True)
component_build = db.relationship('ComponentBuild', backref='component_builds_trace', lazy=False)
def json(self):
retval = {
'id': self.id,
'component_id': self.component_id,
'state_time': self.state_time,
'state': self.state,
'state_reason': self.state_reason,
'task_id': self.task_id,
}
return retval
def __repr__(self):
return "<ComponentBuildTrace %s, component_id: %s, state_time: %r, state: %s, state_reason: %s, task_id: %s>" % (
self.id, self.component_id, self.state_time, self.state, self.state_reason, self.task_id)
def session_before_commit_handlers(session):
# new and updated items
for item in (set(session.new) | set(session.dirty)):
# handlers for module builds
if isinstance(item, ModuleBuild):
mbt = ModuleBuildTrace(
state_time=datetime.utcnow(),
state=item.state,
state_reason=item.state_reason)
item.module_builds_trace.append(mbt)
# handlers for component builds
elif isinstance(item, ComponentBuild):
cbt = ComponentBuildTrace(
state_time=datetime.utcnow(),
state=item.state,
state_reason=item.state_reason,
task_id=item.task_id)
item.component_builds_trace.append(cbt)