From dab1d03d81c538966d03fb9318a4588a9e803b44 Mon Sep 17 00:00:00 2001 From: Sol Jerome Date: Sat, 24 Mar 2012 11:20:07 -0500 Subject: Allow to run directly from a git checkout (#1037) Signed-off-by: Sol Jerome --- src/lib/Bcfg2/Server/Snapshots/__init__.py | 31 +++ src/lib/Bcfg2/Server/Snapshots/model.py | 329 +++++++++++++++++++++++++++++ 2 files changed, 360 insertions(+) create mode 100644 src/lib/Bcfg2/Server/Snapshots/__init__.py create mode 100644 src/lib/Bcfg2/Server/Snapshots/model.py (limited to 'src/lib/Bcfg2/Server/Snapshots') diff --git a/src/lib/Bcfg2/Server/Snapshots/__init__.py b/src/lib/Bcfg2/Server/Snapshots/__init__.py new file mode 100644 index 000000000..7c901adb2 --- /dev/null +++ b/src/lib/Bcfg2/Server/Snapshots/__init__.py @@ -0,0 +1,31 @@ +__all__ = ['models', 'db_from_config', 'setup_session'] + +import sqlalchemy +import sqlalchemy.orm +# Compatibility import +from Bcfg2.Bcfg2Py3k import ConfigParser + + +def db_from_config(cfile): + cp = ConfigParser.ConfigParser() + cp.read([cfile]) + driver = cp.get('snapshots', 'driver') + if driver == 'sqlite': + path = cp.get('snapshots', 'database') + return 'sqlite:///%s' % path + elif driver in ['mysql', 'postgres']: + user = cp.get('snapshots', 'user') + password = cp.get('snapshots', 'password') + host = cp.get('snapshots', 'host') + db = cp.get('snapshots', 'database') + return '%s://%s:%s@%s/%s' % (driver, user, password, host, db) + else: + raise Exception("unsupported db driver %s" % driver) + + +def setup_session(cfile, debug=False): + engine = sqlalchemy.create_engine(db_from_config(cfile), + echo=debug) + Session = sqlalchemy.orm.sessionmaker() + Session.configure(bind=engine) + return Session() diff --git a/src/lib/Bcfg2/Server/Snapshots/model.py b/src/lib/Bcfg2/Server/Snapshots/model.py new file mode 100644 index 000000000..5d7973c16 --- /dev/null +++ b/src/lib/Bcfg2/Server/Snapshots/model.py @@ -0,0 +1,329 @@ +import sys +from sqlalchemy import Table, Column, Integer, Unicode, ForeignKey, Boolean, \ + DateTime, UnicodeText, desc +import datetime +import sqlalchemy.exceptions +from sqlalchemy.orm import relation, backref +from sqlalchemy.ext.declarative import declarative_base + + +# py3k compatibility +def u_str(string): + if sys.hexversion >= 0x03000000: + return string + else: + return unicode(string) + + +class Uniquer(object): + force_rt = True + + @classmethod + def by_value(cls, session, **kwargs): + if cls.force_rt: + try: + return session.query(cls).filter_by(**kwargs).one() + except sqlalchemy.exceptions.InvalidRequestError: + return cls(**kwargs) + else: + return cls(**kwargs) + + @classmethod + def from_record(cls, session, data): + return cls.by_value(session, **data) + +Base = declarative_base() + + +class Administrator(Uniquer, Base): + __tablename__ = 'administrator' + id = Column(Integer, primary_key=True) + name = Column(Unicode(20), unique=True) + email = Column(Unicode(64)) + +admin_client = Table('admin_client', Base.metadata, + Column('admin_id', + Integer, + ForeignKey('administrator.id')), + Column('client_id', + Integer, + ForeignKey('client.id'))) + +admin_group = Table('admin_group', Base.metadata, + Column('admin_id', + Integer, + ForeignKey('administrator.id')), + Column('group_id', + Integer, + ForeignKey('group.id'))) + + +class Client(Uniquer, Base): + __tablename__ = 'client' + id = Column(Integer, primary_key=True) + name = Column(Unicode(64), unique=True) + admins = relation("Administrator", secondary=admin_client, + backref='clients') + active = Column(Boolean, default=True) + online = Column(Boolean, default=True) + online_ts = Column(DateTime) + + +class Group(Uniquer, Base): + __tablename__ = 'group' + id = Column(Integer, primary_key=True) + name = Column(Unicode(32), unique=True) + admins = relation("Administrator", secondary=admin_group, + backref='groups') + + +class ConnectorKeyVal(Uniquer, Base): + __tablename__ = 'connkeyval' + id = Column(Integer, primary_key=True) + connector = Column(Unicode(16)) + key = Column(Unicode(32)) + value = Column(UnicodeText) + +meta_group = Table('meta_group', Base.metadata, + Column('metadata_id', + Integer, + ForeignKey('metadata.id')), + Column('group_id', + Integer, + ForeignKey('group.id'))) + +meta_conn = Table('meta_conn', Base.metadata, + Column('metadata_id', + Integer, + ForeignKey('metadata.id')), + Column('connkeyval_id', + Integer, + ForeignKey('connkeyval.id'))) + + +class Metadata(Base): + __tablename__ = 'metadata' + id = Column(Integer, primary_key=True) + client_id = Column(Integer, ForeignKey('client.id')) + client = relation(Client) + groups = relation("Group", secondary=meta_group) + keyvals = relation(ConnectorKeyVal, secondary=meta_conn) + timestamp = Column(DateTime) + + @classmethod + def from_metadata(cls, mysession, mymetadata): + client = Client.by_value(mysession, name=u_str(mymetadata.hostname)) + m = cls(client=client) + for group in mymetadata.groups: + m.groups.append(Group.by_value(mysession, name=u_str(group))) + for connector in mymetadata.connectors: + data = getattr(mymetadata, connector) + if not isinstance(data, dict): + continue + for key, value in list(data.items()): + if not isinstance(value, str): + continue + m.keyvals.append(ConnectorKeyVal.by_value(mysession, + connector=u_str(connector), + key=u_str(key), + value=u_str(value))) + return m + + +class Package(Base, Uniquer): + __tablename__ = 'package' + id = Column(Integer, primary_key=True) + name = Column(Unicode(24)) + type = Column(Unicode(16)) + version = Column(Unicode(16)) + verification_status = Column(Boolean) + + +class CorrespondenceType(object): + mtype = Package + + @classmethod + def from_record(cls, mysession, record): + (mod, corr, name, s_dict, e_dict) = record + if not s_dict: + start = None + else: + start = cls.mtype.by_value(mysession, name=name, **s_dict) + if s_dict != e_dict: + end = cls.mtype.by_value(mysession, name=name, **e_dict) + else: + end = start + return cls(start=start, end=end, modified=mod, correct=corr) + + +class PackageCorrespondence(Base, CorrespondenceType): + mtype = Package + __tablename__ = 'package_pair' + id = Column(Integer, primary_key=True) + start_id = Column(Integer, ForeignKey('package.id')) + start = relation(Package, primaryjoin=start_id == Package.id) + end_id = Column(Integer, ForeignKey('package.id'), nullable=True) + end = relation(Package, primaryjoin=end_id == Package.id) + modified = Column(Boolean) + correct = Column(Boolean) + +package_snap = Table('package_snap', Base.metadata, + Column('ppair_id', + Integer, + ForeignKey('package_pair.id')), + Column('snapshot_id', + Integer, + ForeignKey('snapshot.id'))) + + +class Service(Base, Uniquer): + __tablename__ = 'service' + id = Column(Integer, primary_key=True) + name = Column(Unicode(16)) + type = Column(Unicode(12)) + status = Column(Boolean) + + +class ServiceCorrespondence(Base, CorrespondenceType): + mtype = Service + __tablename__ = 'service_pair' + id = Column(Integer, primary_key=True) + start_id = Column(Integer, ForeignKey('service.id')) + start = relation(Service, primaryjoin=start_id == Service.id) + end_id = Column(Integer, ForeignKey('service.id'), nullable=True) + end = relation(Service, primaryjoin=end_id == Service.id) + modified = Column(Boolean) + correct = Column(Boolean) + +service_snap = Table('service_snap', Base.metadata, + Column('spair_id', + Integer, + ForeignKey('service_pair.id')), + Column('snapshot_id', + Integer, + ForeignKey('snapshot.id'))) + + +class File(Base, Uniquer): + __tablename__ = 'file' + id = Column(Integer, primary_key=True) + name = Column(UnicodeText) + type = Column(Unicode(12)) + owner = Column(Unicode(12)) + group = Column(Unicode(16)) + perms = Column(Integer) + contents = Column(UnicodeText) + + +class FileCorrespondence(Base, CorrespondenceType): + mtype = File + __tablename__ = 'file_pair' + id = Column(Integer, primary_key=True) + start_id = Column(Integer, ForeignKey('file.id')) + start = relation(File, primaryjoin=start_id == File.id) + end_id = Column(Integer, ForeignKey('file.id'), nullable=True) + end = relation(File, primaryjoin=end_id == File.id) + modified = Column(Boolean) + correct = Column(Boolean) + +file_snap = Table('file_snap', Base.metadata, + Column('fpair_id', + Integer, + ForeignKey('file_pair.id')), + Column('snapshot_id', + Integer, + ForeignKey('snapshot.id'))) + +extra_pkg_snap = Table('extra_pkg_snap', Base.metadata, + Column('package_id', + Integer, + ForeignKey('package.id')), + Column('snapshot_id', + Integer, + ForeignKey('snapshot.id'))) + +extra_file_snap = Table('extra_file_snap', Base.metadata, + Column('file_id', + Integer, + ForeignKey('file.id')), + Column('snapshot_id', + Integer, + ForeignKey('snapshot.id'))) + +extra_service_snap = Table('extra_service_snap', Base.metadata, + Column('service_id', + Integer, + ForeignKey('service.id')), + Column('snapshot_id', + Integer, + ForeignKey('snapshot.id'))) + + +class Action(Base): + __tablename__ = 'action' + id = Column(Integer, primary_key=True) + command = Column(UnicodeText) + return_code = Column(Integer) + output = Column(UnicodeText) + +action_snap = Table('action_snap', Base.metadata, + Column('action_id', Integer, ForeignKey('action.id')), + Column('snapshot_id', Integer, ForeignKey('snapshot.id'))) + + +class Snapshot(Base): + __tablename__ = 'snapshot' + id = Column(Integer, primary_key=True) + correct = Column(Boolean) + revision = Column(Unicode(36)) + metadata_id = Column(Integer, ForeignKey('metadata.id')) + client_metadata = relation(Metadata, primaryjoin=metadata_id == Metadata.id) + timestamp = Column(DateTime, default=datetime.datetime.now) + client_id = Column(Integer, ForeignKey('client.id')) + client = relation(Client, backref=backref('snapshots')) + packages = relation(PackageCorrespondence, secondary=package_snap) + services = relation(ServiceCorrespondence, secondary=service_snap) + files = relation(FileCorrespondence, secondary=file_snap) + actions = relation(Action, secondary=action_snap) + extra_packages = relation(Package, secondary=extra_pkg_snap) + extra_services = relation(Service, secondary=extra_service_snap) + extra_files = relation(File, secondary=extra_file_snap) + + c_dispatch = dict([('Package', ('packages', PackageCorrespondence)), + ('Service', ('services', ServiceCorrespondence)), + ('Path', ('files', FileCorrespondence))]) + e_dispatch = dict([('Package', ('extra_packages', Package)), + ('Service', ('extra_services', Service)), + ('Path', ('extra_files', File))]) + + @classmethod + def from_data(cls, session, correct, revision, metadata, entries, extra): + dbm = Metadata.from_metadata(session, metadata) + snap = cls(correct=correct, client_metadata=dbm, revision=revision, + timestamp=datetime.datetime.now(), client=dbm.client) + for (dispatch, data) in [(cls.c_dispatch, entries), + (cls.e_dispatch, extra)]: + for key in dispatch: + dest, ecls = dispatch[key] + for edata in list(data[key].values()): + getattr(snap, dest).append(ecls.from_record(session, edata)) + return snap + + @classmethod + def by_client(cls, session, clientname): + return session.query(cls).join(cls.client_metadata, + Metadata.client).filter(Client.name == clientname) + + @classmethod + def get_current(cls, session, clientname): + return session.query(Snapshot).join(Snapshot.client_metadata, + Metadata.client).filter(Client.name == clientname).order_by(desc(Snapshot.timestamp)).first() + + @classmethod + def get_by_date(cls, session, clientname, timestamp): + return session.query(Snapshot)\ + .join(Snapshot.client_metadata, Metadata.client)\ + .filter(Snapshot.timestamp < timestamp)\ + .filter(Client.name == clientname)\ + .order_by(desc(Snapshot.timestamp))\ + .first() -- cgit v1.2.3-1-g7c22